rs_mrz_parser/
lib.rs

1use crate::constants::mrz_utils::{
2    MRZ_TYPE1, MRZ_TYPE2, MRZ_TYPE3, TYPE1_NUMBER_OF_CHARACTERS_PER_LINE, TYPE1_TOTAL_NUMBER_OF_CHARACTERS,
3    TYPE2_NUMBER_OF_CHARACTERS_PER_LINE, TYPE2_TOTAL_NUMBER_OF_CHARACTERS, TYPE3_NUMBER_OF_CHARACTERS_PER_LINE,
4};
5use crate::parser::parser::{IMRZParser, MRZResult};
6use crate::parser::td1::TD1;
7use crate::parser::td2::TD2;
8use crate::parser::td3::TD3;
9use crate::utils::utils::check_same;
10
11mod utils;
12mod parser;
13pub mod constants;
14
15pub struct MRZParser {
16    mrz_type: usize,
17    components: Vec<String>,
18}
19
20impl MRZParser {
21    // Create a new MRZParser from a single MRZ string with lines separated by a newline character
22    pub fn new_mrz_string_parser(mrz_str: &str) -> Self {
23        let mrz_type: usize = 0;
24        let components: Vec<String> = if mrz_str.contains('\n') {
25            mrz_str.lines().map(String::from).collect()
26        } else if mrz_str.len() == TYPE1_TOTAL_NUMBER_OF_CHARACTERS {
27            vec![
28                mrz_str[..TYPE1_NUMBER_OF_CHARACTERS_PER_LINE].to_string(),
29                mrz_str[TYPE1_NUMBER_OF_CHARACTERS_PER_LINE..2 * TYPE1_NUMBER_OF_CHARACTERS_PER_LINE].to_string(),
30                mrz_str[2 * TYPE1_NUMBER_OF_CHARACTERS_PER_LINE..].to_string(),
31            ]
32        } else if mrz_str.len() == TYPE2_TOTAL_NUMBER_OF_CHARACTERS {
33            vec![
34                mrz_str[..TYPE2_NUMBER_OF_CHARACTERS_PER_LINE].to_string(),
35                mrz_str[TYPE2_NUMBER_OF_CHARACTERS_PER_LINE..].to_string(),
36            ]
37        } else {
38            vec![
39                mrz_str[..TYPE3_NUMBER_OF_CHARACTERS_PER_LINE].to_string(),
40                mrz_str[TYPE3_NUMBER_OF_CHARACTERS_PER_LINE..].to_string(),
41            ]
42        };
43
44        MRZParser { mrz_type, components }
45    }
46
47    // Create a new MRZParser from a vector of MRZ lines
48    pub fn new_mrz_line_parser(mrz_lines: Vec<String>) -> Self {
49        let mrz_type: usize = 0;
50        MRZParser {
51            mrz_type,
52            components: mrz_lines,
53        }
54    }
55
56    // Return the MRZ type
57    pub fn get_mrz_type(&mut self) -> Result<usize, &'static str> {
58        self.validate()?;
59        Ok(self.mrz_type)
60    }
61
62    // Parse the MRZ information
63    pub fn parse(&mut self) -> Result<MRZResult, &'static str> {
64        self.validate()?;
65
66        let mrz_parser: Box<dyn IMRZParser> = match self.mrz_type {
67            MRZ_TYPE1 => Box::new(TD1::new()),
68            MRZ_TYPE2 => Box::new(TD2::new()),
69            MRZ_TYPE3 => Box::new(TD3::new()),
70            _ => return Err("invalid mrz type"),
71        };
72
73        mrz_parser.parse(&self.components)
74    }
75
76    // Validate the input MRZ for formatting errors
77    fn validate(&mut self) -> Result<(), &'static str> {
78        let mut mrz_type: usize = 0;
79
80        match self.components.len() {
81            3 => {
82                for line in &self.components {
83                    if line.len() != TYPE1_NUMBER_OF_CHARACTERS_PER_LINE {
84                        return Err("invalid TD1 format line length");
85                    }
86                }
87                mrz_type = MRZ_TYPE1;
88            }
89            2 => {
90                let mut character_count = Vec::new();
91                for line in &self.components {
92                    if line.len() != TYPE2_NUMBER_OF_CHARACTERS_PER_LINE
93                        && line.len() != TYPE3_NUMBER_OF_CHARACTERS_PER_LINE
94                    {
95                        return Err("invalid mrz line length");
96                    }
97                    character_count.push(line.len());
98                }
99
100                if check_same(&character_count) && character_count[0] == TYPE2_NUMBER_OF_CHARACTERS_PER_LINE {
101                    mrz_type = MRZ_TYPE2;
102                }
103                if check_same(&character_count) && character_count[0] == TYPE3_NUMBER_OF_CHARACTERS_PER_LINE {
104                    mrz_type = MRZ_TYPE3;
105                }
106            }
107            _ => return Err("invalid mrz line length"),
108        }
109
110        self.mrz_type = mrz_type;
111        Ok(())
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_td1_vec() {
121        let mrz_string: Vec<String> = vec![
122            "I<UTOD231458907<<<<<<<<<<<<<<<".to_string(),
123            "7408122F1204159UTO<<<<<<<<<<<6".to_string(),
124            "ERIKSSON<<ANNA<MARIA<<<<<<<<<<".to_string(),
125        ];
126
127        let mut parser = MRZParser::new_mrz_line_parser(mrz_string);
128        let result = parser.parse().unwrap();
129        assert_eq!(result.is_valid, true);
130        println!("{:?}", result)
131    }
132
133    #[test]
134    fn test_td1_str() {
135        let mrz_string: &str = "\
136        I<UTOD231458907<<<<<<<<<<<<<<<\n\
137        7408122F1204159UTO<<<<<<<<<<<6\n\
138        ERIKSSON<<ANNA<MARIA<<<<<<<<<<";
139        let mut parser = MRZParser::new_mrz_string_parser(mrz_string);
140        let result = parser.parse().unwrap();
141        assert_eq!(result.is_valid, true);
142        println!("{:?}", result)
143    }
144}