1use super::PersistentIdentifierParse;
4use crate::prelude::*;
5use crate::util::constants::{RE_PATENT, RE_PATENT_TEXT};
6use crate::util::regex_capture_lookup;
7use core::fmt;
8use derive_more::Display;
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12const PATENT_COUNTRY_CODES: [&str; 6] = ["US", "CN", "DE", "EP", "JP", "KR"];
13
14#[derive(Clone, Debug, Default, Display, Deserialize, Serialize, JsonSchema)]
16pub enum CountryCode {
17 #[default]
19 #[display("US")]
20 US,
21 #[display("CN")]
23 CN,
24 #[display("DE")]
26 DE,
27 #[display("EP")]
29 EP,
30 #[display("JP")]
32 JP,
33 #[display("KR")]
35 KR,
36}
37#[derive(Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize, JsonSchema)]
46pub enum KindCode {
47 #[display("A1")]
49 #[serde(rename = "A1")]
50 A1,
51 #[display("A2")]
53 #[serde(rename = "A2")]
54 A2,
55 #[display("A9")]
57 #[serde(rename = "A9")]
58 A9,
59 #[display("B1")]
61 #[serde(rename = "B1")]
62 B1,
63 #[display("B2")]
65 #[serde(rename = "B2")]
66 B2,
67 #[display("C1")]
69 #[serde(rename = "C1")]
70 C1,
71 #[display("C2")]
73 #[serde(rename = "C2")]
74 C2,
75 #[display("C3")]
77 #[serde(rename = "C3")]
78 C3,
79 #[display("E1")]
81 #[serde(rename = "E1", alias = "E")]
82 E1,
83 #[display("S1")]
85 #[serde(rename = "S1", alias = "S")]
86 S1,
87 #[display("P1")]
89 #[serde(rename = "P1")]
90 P1,
91 #[display("P2")]
93 #[serde(rename = "P2")]
94 P2,
95 #[display("P3")]
97 #[serde(rename = "P3")]
98 P3,
99 #[display("P4")]
101 #[serde(rename = "P4")]
102 P4,
103 #[display("P9")]
105 #[serde(rename = "P9")]
106 P9,
107 #[display("H1")]
111 #[serde(rename = "H1", alias = "H")]
112 H1,
113 #[display("Unknown")]
115 #[serde(rename = "Unknown")]
116 Unknown,
117}
118#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
120pub enum Patent {
121 Granted {
123 country_code: Option<CountryCode>,
125 kind_code: KindCode,
127 serial_number: String,
129 },
130 Application {
132 serial_number: String,
134 year: Option<String>,
136 },
137 Publication {
139 country_code: Option<CountryCode>,
141 kind_code: KindCode,
143 serial_number: String,
145 year: Option<String>,
147 },
148}
149impl From<&str> for CountryCode {
150 fn from(s: &str) -> Self {
151 match s.to_uppercase().as_str() {
152 | "CN" => CountryCode::CN,
153 | "DE" => CountryCode::DE,
154 | "EP" => CountryCode::EP,
155 | "JP" => CountryCode::JP,
156 | "KR" => CountryCode::KR,
157 | _ => CountryCode::US,
158 }
159 }
160}
161impl From<String> for CountryCode {
162 fn from(s: String) -> Self {
163 CountryCode::from(s.as_str())
164 }
165}
166impl From<&str> for KindCode {
167 fn from(s: &str) -> Self {
168 match s {
169 | "A1" => KindCode::A1,
170 | "A2" => KindCode::A2,
171 | "A9" => KindCode::A9,
172 | "B1" => KindCode::B1,
173 | "B2" => KindCode::B2,
174 | "C1" => KindCode::C1,
175 | "C2" => KindCode::C2,
176 | "C3" => KindCode::C3,
177 | "E" | "E1" => KindCode::E1,
178 | "S" | "S1" => KindCode::S1,
179 | "P1" => KindCode::P1,
180 | "P2" => KindCode::P2,
181 | "P3" => KindCode::P3,
182 | "P4" => KindCode::P4,
183 | "P9" => KindCode::P9,
184 | "H" | "H1" => KindCode::H1,
185 | _ => KindCode::Unknown,
186 }
187 }
188}
189impl From<String> for KindCode {
190 fn from(s: String) -> Self {
191 KindCode::from(s.as_str())
192 }
193}
194impl KindCode {
195 pub fn is_granted(&self) -> bool {
197 match self {
198 | KindCode::B1
199 | KindCode::B2
200 | KindCode::C1
201 | KindCode::C2
202 | KindCode::C3
203 | KindCode::E1
204 | KindCode::P2
205 | KindCode::P3
206 | KindCode::S1 => true,
207 | _ => false,
208 }
209 }
210}
211impl Default for Patent {
212 fn default() -> Self {
213 Patent::Granted {
214 country_code: Some(CountryCode::US),
215 kind_code: KindCode::Unknown,
216 serial_number: String::new(),
217 }
218 }
219}
220impl fmt::Display for Patent {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 match self {
223 | Patent::Granted {
224 country_code,
225 serial_number,
226 kind_code,
227 } => {
228 write!(f, "{} {serial_number} {kind_code}", country_code.clone().unwrap_or_default())
229 }
230 | Patent::Application { serial_number, year } => {
231 if let Some(year) = year {
232 write!(f, "{year}/{serial_number}")
233 } else {
234 write!(f, "{serial_number}")
235 }
236 }
237 | Patent::Publication {
238 country_code,
239 serial_number,
240 kind_code,
241 year,
242 } => {
243 let country = country_code.clone().unwrap_or_default();
244 if let Some(year) = year {
245 write!(f, "{country} {year}/{serial_number} {kind_code}")
246 } else {
247 write!(f, "{country} {serial_number} {kind_code}")
248 }
249 }
250 }
251 }
252}
253impl From<&str> for Patent {
254 fn from(s: &str) -> Self {
255 Patent::parse(s).unwrap_or_default()
256 }
257}
258impl From<String> for Patent {
259 fn from(s: String) -> Self {
260 Patent::from(s.as_str())
261 }
262}
263impl Patent {
264 pub fn find_all(value: &str) -> Vec<Self> {
266 let re = &RE_PATENT;
267 re.find_iter(value)
268 .filter_map(Result::ok)
269 .filter_map(|m| Patent::parse(m.as_str()))
270 .collect::<Vec<_>>()
271 }
272 pub fn is_valid<S>(value: S) -> bool
274 where
275 S: AsRef<str>,
276 {
277 match Patent::parse(value.as_ref()) {
278 | Some(result) => match result {
279 | Patent::Application { serial_number, .. } => !serial_number.is_empty(),
280 | Patent::Granted {
281 serial_number, kind_code, ..
282 }
283 | Patent::Publication {
284 serial_number, kind_code, ..
285 } => !serial_number.is_empty() && kind_code != KindCode::Unknown,
286 },
287 | None => false,
288 }
289 }
290 pub fn parse<S>(value: S) -> Option<Self>
294 where
295 S: Into<String> + Clone,
296 {
297 let s: String = value.clone().into().chars().take(2).collect::<String>();
298 match CountryCode::from(s) {
299 | CountryCode::US => {
300 fn preprocess<S>(value: S) -> String
301 where
302 S: Into<String>,
303 {
304 let compact = value.into().replace(" ", "").replace(",", "").replace("-", "").to_uppercase();
305 let with_kind_code = match compact.chars().last() {
306 | Some(kind @ ('E' | 'H' | 'S')) => format!("{}{kind}1", compact.trim_end_matches(kind)),
307 | _ => compact,
308 };
309 let is_known_country = PATENT_COUNTRY_CODES.into_iter().any(|country| with_kind_code.starts_with(country));
310 match is_known_country {
311 | true => with_kind_code,
312 | false => format!("US{with_kind_code}"),
313 }
314 }
315 let pattern = format!("^{RE_PATENT_TEXT}$");
316 let s: String = preprocess(value);
317 let groups = ["country_code", "year", "serial_number", "kind_code"]
318 .into_iter()
319 .map(String::from)
320 .collect::<Vec<_>>();
321 let lookup = regex_capture_lookup(pattern, s, groups);
322 let country_code = lookup.get("country_code").cloned().map(CountryCode::from);
323 let serial_number = lookup.get("serial_number").cloned().unwrap_or_default();
324 let kind_code = match lookup.get("kind_code").cloned() {
325 | Some(value) => KindCode::from(value),
326 | None => KindCode::Unknown,
327 };
328 match kind_code {
329 | KindCode::B1
330 | KindCode::B2
331 | KindCode::C1
332 | KindCode::C2
333 | KindCode::C3
334 | KindCode::E1
335 | KindCode::P2
336 | KindCode::P3
337 | KindCode::S1 => Some(Patent::Granted {
338 country_code,
339 serial_number,
340 kind_code,
341 }),
342 | KindCode::A1 | KindCode::A2 | KindCode::A9 | KindCode::P1 | KindCode::P4 | KindCode::P9 => {
343 let year = lookup.get("year").cloned();
344 Some(Patent::Publication {
345 country_code,
346 serial_number,
347 kind_code,
348 year,
349 })
350 }
351 | _ => None,
352 }
353 }
354 | _ => None,
355 }
356 }
357}
358impl PersistentIdentifierParse for Patent {
359 fn find_all(value: impl ToString) -> Vec<Self> {
360 let value = value.to_string();
361 match Self::parse(&value) {
362 | Some(identifier) => vec![identifier],
363 | None => Self::find_all(&value),
364 }
365 }
366 fn format(value: impl ToString) -> String {
367 Self::from_string(value).to_string()
368 }
369 fn from_string(value: impl ToString) -> Self {
370 Self::from(value.to_string())
371 }
372 fn is_valid(value: impl ToString) -> bool {
373 Self::is_valid(value.to_string())
374 }
375}