Skip to main content

mrz_parser/
country_patterns.rs

1use once_cell::sync::Lazy;
2use regex::Regex;
3
4/// Pattern information for a specific country to help extract document numbers
5/// from MRZ first-line content.
6#[derive(Debug)]
7pub struct MRZCountryPattern {
8    pub country_code: &'static str,
9    pub id_prefix: &'static str,
10    pub document_number_pattern: Regex,
11    pub document_number_start_index: usize,
12}
13
14/// Static list of country patterns, held in a lazily-initialized `Vec` so the
15/// contained `Regex` instances are compiled once at runtime.
16static PATTERNS: Lazy<Vec<MRZCountryPattern>> = Lazy::new(|| {
17    vec![
18        MRZCountryPattern {
19            country_code: "AUT",
20            id_prefix: "IDAUT",
21            document_number_pattern: Regex::new(r"^([0-9]{10})").unwrap(),
22            document_number_start_index: 5,
23        },
24        MRZCountryPattern {
25            country_code: "BEL",
26            id_prefix: "IDBEL",
27            document_number_pattern: Regex::new(r"^([A-Z][0-9]{3}[A-Z][0-9]{6})").unwrap(),
28            document_number_start_index: 5,
29        },
30        MRZCountryPattern {
31            country_code: "BGR",
32            id_prefix: "IDBGR",
33            document_number_pattern: Regex::new(r"^([0-9]{10})").unwrap(),
34            document_number_start_index: 5,
35        },
36        MRZCountryPattern {
37            country_code: "CYP",
38            id_prefix: "IDCYP",
39            document_number_pattern: Regex::new(r"^([0-9]{9})").unwrap(),
40            document_number_start_index: 5,
41        },
42        MRZCountryPattern {
43            country_code: "CZE",
44            id_prefix: "IDCZE",
45            document_number_pattern: Regex::new(r"^([A-Z]?[0-9]{8,9})").unwrap(),
46            document_number_start_index: 5,
47        },
48        MRZCountryPattern {
49            country_code: "DEU",
50            id_prefix: "IDDEU",
51            document_number_pattern: Regex::new(r"^([A-Z0-9]{9})").unwrap(),
52            document_number_start_index: 5,
53        },
54        MRZCountryPattern {
55            country_code: "DNK",
56            id_prefix: "IDDNK",
57            document_number_pattern: Regex::new(r"^([0-9]{9})").unwrap(),
58            document_number_start_index: 5,
59        },
60        MRZCountryPattern {
61            country_code: "ESP",
62            id_prefix: "IDESP",
63            document_number_pattern: Regex::new(r"^([0-9]{8}[A-Z])").unwrap(),
64            document_number_start_index: 5,
65        },
66        MRZCountryPattern {
67            country_code: "EST",
68            id_prefix: "IDEST",
69            document_number_pattern: Regex::new(r"^([A-Z]{0,2}[0-9]{8})").unwrap(),
70            document_number_start_index: 5,
71        },
72        MRZCountryPattern {
73            country_code: "FIN",
74            id_prefix: "IDFIN",
75            document_number_pattern: Regex::new(r"^([0-9]{14})").unwrap(),
76            document_number_start_index: 5,
77        },
78        MRZCountryPattern {
79            country_code: "FRA",
80            id_prefix: "IDFRA",
81            document_number_pattern: Regex::new(r"^([A-Z0-9]{12})").unwrap(),
82            document_number_start_index: 5,
83        },
84        MRZCountryPattern {
85            country_code: "GBR",
86            id_prefix: "IDGBR",
87            document_number_pattern: Regex::new(r"^([0-9]{13})").unwrap(),
88            document_number_start_index: 5,
89        },
90        MRZCountryPattern {
91            country_code: "GRC",
92            id_prefix: "IDGRC",
93            document_number_pattern: Regex::new(r"^([A-Z]{0,2}[0-9]{8,10})").unwrap(),
94            document_number_start_index: 5,
95        },
96        MRZCountryPattern {
97            country_code: "HRV",
98            id_prefix: "IDHRV",
99            document_number_pattern: Regex::new(r"^([0-9]{11})").unwrap(),
100            document_number_start_index: 5,
101        },
102        MRZCountryPattern {
103            country_code: "HUN",
104            id_prefix: "IDHUN",
105            document_number_pattern: Regex::new(r"^([0-9]{6}[A-Z]{2})").unwrap(),
106            document_number_start_index: 5,
107        },
108        MRZCountryPattern {
109            country_code: "IRL",
110            id_prefix: "IDIRL",
111            document_number_pattern: Regex::new(r"^([A-Z0-9]{12})").unwrap(),
112            document_number_start_index: 5,
113        },
114        MRZCountryPattern {
115            country_code: "ITA",
116            id_prefix: "IDITA",
117            document_number_pattern: Regex::new(r"^([A-Z0-9]{10})").unwrap(),
118            document_number_start_index: 5,
119        },
120        MRZCountryPattern {
121            country_code: "LTU",
122            id_prefix: "IDLTU",
123            document_number_pattern: Regex::new(r"^([0-9]{8})").unwrap(),
124            document_number_start_index: 5,
125        },
126        MRZCountryPattern {
127            country_code: "LUX",
128            id_prefix: "IDLUX",
129            document_number_pattern: Regex::new(r"^([A-Z]{0,2}[0-9]{8,10})").unwrap(),
130            document_number_start_index: 5,
131        },
132        MRZCountryPattern {
133            country_code: "LVA",
134            id_prefix: "IDLVA",
135            document_number_pattern: Regex::new(r"^([A-Z]?[0-9]{8,9})").unwrap(),
136            document_number_start_index: 5,
137        },
138        MRZCountryPattern {
139            country_code: "MLT",
140            id_prefix: "IDMLT",
141            document_number_pattern: Regex::new(r"^([A-Z]?[0-9]{8,9})").unwrap(),
142            document_number_start_index: 5,
143        },
144        MRZCountryPattern {
145            country_code: "NLD",
146            id_prefix: "IDNLD",
147            document_number_pattern: Regex::new(r"^([A-Z]{2}[0-9]{7})").unwrap(),
148            document_number_start_index: 5,
149        },
150        MRZCountryPattern {
151            country_code: "POL",
152            id_prefix: "IDPOL",
153            document_number_pattern: Regex::new(r"^([A-Z]{0,3}[0-9]{8,9})").unwrap(),
154            document_number_start_index: 5,
155        },
156        MRZCountryPattern {
157            country_code: "PRT",
158            id_prefix: "IDPRT",
159            document_number_pattern: Regex::new(r"^([A-Z]{0,2}[0-9]{8})").unwrap(),
160            document_number_start_index: 5,
161        },
162        MRZCountryPattern {
163            country_code: "ROU",
164            id_prefix: "IDROU",
165            document_number_pattern: Regex::new(r"^([A-Z]{2}[0-9]{8,9})").unwrap(),
166            document_number_start_index: 5,
167        },
168        MRZCountryPattern {
169            country_code: "SVK",
170            id_prefix: "IDSVK",
171            document_number_pattern: Regex::new(r"^([0-9]{6}[A-Z][0-9]{3})").unwrap(),
172            document_number_start_index: 5,
173        },
174        MRZCountryPattern {
175            country_code: "SVN",
176            id_prefix: "IDSVN",
177            document_number_pattern: Regex::new(r"^([A-Z]?[0-9]{9,10})").unwrap(),
178            document_number_start_index: 5,
179        },
180        MRZCountryPattern {
181            country_code: "SWE",
182            id_prefix: "IDSWE",
183            document_number_pattern: Regex::new(r"^([A-Z]?[0-9]{8,9})").unwrap(),
184            document_number_start_index: 5,
185        },
186    ]
187});
188
189/// Return a reference to the first matching country pattern for `first_line`,
190/// based on whether `first_line` starts with the pattern's `id_prefix`.
191pub fn get_country_pattern(first_line: &str) -> Option<&'static MRZCountryPattern> {
192    PATTERNS
193        .iter()
194        .find(|p| first_line.starts_with(p.id_prefix))
195}
196
197/// Return all available patterns as a slice.
198pub fn patterns() -> &'static [MRZCountryPattern] {
199    PATTERNS.as_slice()
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn find_known_prefix() {
208        let first = "IDGBR123456789012";
209        let p = get_country_pattern(first).expect("pattern found");
210        assert_eq!(p.country_code, "GBR");
211    }
212
213    #[test]
214    fn no_match_returns_none() {
215        let first = "UNKNOWNPREFIX";
216        assert!(get_country_pattern(first).is_none());
217    }
218}