1use super::{PersistentIdentifier, PersistentIdentifierParse};
3use crate::prelude::{format, vec, String, ToString, Vec};
4use crate::util::constants::{RE_PATENT, RE_PATENT_TEXT};
5use crate::util::regex_capture_lookup;
6use core::fmt;
7use derive_more::Display;
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10
11const PATENT_COUNTRY_CODES: [&str; 6] = ["US", "CN", "DE", "EP", "JP", "KR"];
12
13#[derive(Clone, Debug, Default, Display, Deserialize, Serialize, JsonSchema)]
15pub enum CountryCode {
16 #[default]
18 #[display("US")]
19 US,
20 #[display("CN")]
22 CN,
23 #[display("DE")]
25 DE,
26 #[display("EP")]
28 EP,
29 #[display("JP")]
31 JP,
32 #[display("KR")]
34 KR,
35}
36#[derive(Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize, JsonSchema)]
45pub enum KindCode {
46 #[display("A1")]
48 #[serde(rename = "A1")]
49 A1,
50 #[display("A2")]
52 #[serde(rename = "A2")]
53 A2,
54 #[display("A9")]
56 #[serde(rename = "A9")]
57 A9,
58 #[display("B1")]
60 #[serde(rename = "B1")]
61 B1,
62 #[display("B2")]
64 #[serde(rename = "B2")]
65 B2,
66 #[display("C1")]
68 #[serde(rename = "C1")]
69 C1,
70 #[display("C2")]
72 #[serde(rename = "C2")]
73 C2,
74 #[display("C3")]
76 #[serde(rename = "C3")]
77 C3,
78 #[display("E1")]
80 #[serde(rename = "E1", alias = "E")]
81 E1,
82 #[display("S1")]
84 #[serde(rename = "S1", alias = "S")]
85 S1,
86 #[display("P1")]
88 #[serde(rename = "P1")]
89 P1,
90 #[display("P2")]
92 #[serde(rename = "P2")]
93 P2,
94 #[display("P3")]
96 #[serde(rename = "P3")]
97 P3,
98 #[display("P4")]
100 #[serde(rename = "P4")]
101 P4,
102 #[display("P9")]
104 #[serde(rename = "P9")]
105 P9,
106 #[display("H1")]
110 #[serde(rename = "H1", alias = "H")]
111 H1,
112 #[display("Unknown")]
114 #[serde(rename = "Unknown")]
115 Unknown,
116}
117#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
119pub enum Patent {
120 Granted {
122 country_code: Option<CountryCode>,
124 kind_code: KindCode,
126 serial_number: String,
128 },
129 Application {
131 serial_number: String,
133 year: Option<String>,
135 },
136 Publication {
138 country_code: Option<CountryCode>,
140 kind_code: KindCode,
142 serial_number: String,
144 year: Option<String>,
146 },
147}
148impl From<&str> for CountryCode {
149 fn from(s: &str) -> Self {
150 match s.to_uppercase().as_str() {
151 | "CN" => CountryCode::CN,
152 | "DE" => CountryCode::DE,
153 | "EP" => CountryCode::EP,
154 | "JP" => CountryCode::JP,
155 | "KR" => CountryCode::KR,
156 | _ => CountryCode::US,
157 }
158 }
159}
160impl From<String> for CountryCode {
161 fn from(s: String) -> Self {
162 CountryCode::from(s.as_str())
163 }
164}
165impl From<&str> for KindCode {
166 fn from(s: &str) -> Self {
167 match s {
168 | "A1" => KindCode::A1,
169 | "A2" => KindCode::A2,
170 | "A9" => KindCode::A9,
171 | "B1" => KindCode::B1,
172 | "B2" => KindCode::B2,
173 | "C1" => KindCode::C1,
174 | "C2" => KindCode::C2,
175 | "C3" => KindCode::C3,
176 | "E" | "E1" => KindCode::E1,
177 | "S" | "S1" => KindCode::S1,
178 | "P1" => KindCode::P1,
179 | "P2" => KindCode::P2,
180 | "P3" => KindCode::P3,
181 | "P4" => KindCode::P4,
182 | "P9" => KindCode::P9,
183 | "H" | "H1" => KindCode::H1,
184 | _ => KindCode::Unknown,
185 }
186 }
187}
188impl From<String> for KindCode {
189 fn from(s: String) -> Self {
190 KindCode::from(s.as_str())
191 }
192}
193impl KindCode {
194 pub fn is_granted(&self) -> bool {
196 match self {
197 | KindCode::B1
198 | KindCode::B2
199 | KindCode::C1
200 | KindCode::C2
201 | KindCode::C3
202 | KindCode::E1
203 | KindCode::P2
204 | KindCode::P3
205 | KindCode::S1 => true,
206 | _ => false,
207 }
208 }
209}
210impl Default for Patent {
211 fn default() -> Self {
212 Patent::Granted {
213 country_code: Some(CountryCode::US),
214 kind_code: KindCode::Unknown,
215 serial_number: String::new(),
216 }
217 }
218}
219impl fmt::Display for Patent {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 match self {
222 | Patent::Granted {
223 country_code,
224 serial_number,
225 kind_code,
226 } => {
227 write!(f, "{} {serial_number} {kind_code}", country_code.clone().unwrap_or_default())
228 }
229 | Patent::Application { serial_number, year } => {
230 if let Some(year) = year {
231 write!(f, "{year}/{serial_number}")
232 } else {
233 write!(f, "{serial_number}")
234 }
235 }
236 | Patent::Publication {
237 country_code,
238 serial_number,
239 kind_code,
240 year,
241 } => {
242 let country = country_code.clone().unwrap_or_default();
243 if let Some(year) = year {
244 write!(f, "{country} {year}/{serial_number} {kind_code}")
245 } else {
246 write!(f, "{country} {serial_number} {kind_code}")
247 }
248 }
249 }
250 }
251}
252impl PersistentIdentifier for Patent {
253 fn new() -> Self {
254 Self::default()
255 }
256 fn schema_uri(&self) -> String {
257 String::new()
258 }
259 fn identifier(&self) -> String {
260 self.to_string()
261 }
262 fn prefix(&self) -> Option<String> {
263 match self {
264 | Self::Granted { country_code, .. } => country_code.as_ref().map(ToString::to_string),
265 | Self::Application { year, .. } => year.clone(),
266 | Self::Publication { country_code, year, .. } => {
267 let prefix = [country_code.as_ref().map(ToString::to_string), year.clone()]
268 .into_iter()
269 .flatten()
270 .collect::<Vec<_>>()
271 .join("/");
272 (!prefix.is_empty()).then_some(prefix)
273 }
274 }
275 }
276 fn suffix(&self) -> Option<String> {
277 match self {
278 | Self::Application { serial_number, .. } | Self::Granted { serial_number, .. } | Self::Publication { serial_number, .. } => {
279 (!serial_number.is_empty()).then(|| serial_number.clone())
280 }
281 }
282 }
283}
284impl PersistentIdentifierParse for Patent {
285 fn find_all(value: impl ToString) -> Vec<Self> {
286 let value = value.to_string();
287 match Self::parse(&value) {
288 | Some(identifier) => vec![identifier],
289 | None => Self::find_all(&value),
290 }
291 }
292 fn format(value: impl ToString) -> String {
293 Self::from_string(value).to_string()
294 }
295 fn from_string(value: impl ToString) -> Self {
296 Self::from(value.to_string())
297 }
298 fn is_valid(value: impl ToString) -> bool {
299 Self::is_valid(value.to_string())
300 }
301}
302impl From<&str> for Patent {
303 fn from(s: &str) -> Self {
304 Patent::parse(s).unwrap_or_default()
305 }
306}
307impl From<String> for Patent {
308 fn from(s: String) -> Self {
309 Patent::from(s.as_str())
310 }
311}
312impl Patent {
313 pub fn find_all(value: &str) -> Vec<Self> {
315 let re = &RE_PATENT;
316 re.find_iter(value)
317 .filter_map(Result::ok)
318 .filter_map(|m| Patent::parse(m.as_str()))
319 .collect::<Vec<_>>()
320 }
321 pub fn is_valid<S>(value: S) -> bool
323 where
324 S: AsRef<str>,
325 {
326 match Patent::parse(value.as_ref()) {
327 | Some(result) => match result {
328 | Patent::Application { serial_number, .. } => !serial_number.is_empty(),
329 | Patent::Granted {
330 serial_number, kind_code, ..
331 }
332 | Patent::Publication {
333 serial_number, kind_code, ..
334 } => !serial_number.is_empty() && kind_code != KindCode::Unknown,
335 },
336 | None => false,
337 }
338 }
339 pub fn parse<S>(value: S) -> Option<Self>
343 where
344 S: Into<String> + Clone,
345 {
346 let s: String = value.clone().into().chars().take(2).collect::<String>();
347 match CountryCode::from(s) {
348 | CountryCode::US => {
349 fn preprocess<S>(value: S) -> String
350 where
351 S: Into<String>,
352 {
353 let compact = value.into().replace(" ", "").replace(",", "").replace("-", "").to_uppercase();
354 let with_kind_code = match compact.chars().last() {
355 | Some(kind @ ('E' | 'H' | 'S')) => format!("{}{kind}1", compact.trim_end_matches(kind)),
356 | _ => compact,
357 };
358 let is_known_country = PATENT_COUNTRY_CODES.into_iter().any(|country| with_kind_code.starts_with(country));
359 match is_known_country {
360 | true => with_kind_code,
361 | false => format!("US{with_kind_code}"),
362 }
363 }
364 let pattern = format!("^{RE_PATENT_TEXT}$");
365 let s: String = preprocess(value);
366 let groups = ["country_code", "year", "serial_number", "kind_code"]
367 .into_iter()
368 .map(String::from)
369 .collect::<Vec<_>>();
370 let lookup = regex_capture_lookup(pattern, s, groups);
371 let country_code = lookup.get("country_code").cloned().map(CountryCode::from);
372 let serial_number = lookup.get("serial_number").cloned().unwrap_or_default();
373 let kind_code = match lookup.get("kind_code").cloned() {
374 | Some(value) => KindCode::from(value),
375 | None => KindCode::Unknown,
376 };
377 match kind_code {
378 | KindCode::B1
379 | KindCode::B2
380 | KindCode::C1
381 | KindCode::C2
382 | KindCode::C3
383 | KindCode::E1
384 | KindCode::P2
385 | KindCode::P3
386 | KindCode::S1 => Some(Patent::Granted {
387 country_code,
388 serial_number,
389 kind_code,
390 }),
391 | KindCode::A1 | KindCode::A2 | KindCode::A9 | KindCode::P1 | KindCode::P4 | KindCode::P9 => {
392 let year = lookup.get("year").cloned();
393 Some(Patent::Publication {
394 country_code,
395 serial_number,
396 kind_code,
397 year,
398 })
399 }
400 | _ => None,
401 }
402 }
403 | _ => None,
404 }
405 }
406}
407
408#[cfg(test)]
409mod tests;