├── .gitignore
├── tests
├── bootstrap.php
└── Deft
│ └── MrzParser
│ └── Tests
│ └── ParserTest.php
├── src
└── Deft
│ └── MrzParser
│ ├── Exception
│ ├── Exception.php
│ ├── ParseException.php
│ └── UnsupportedDocumentException.php
│ ├── Document
│ ├── Sex.php
│ ├── TravelDocumentType.php
│ ├── TravelDocumentInterface.php
│ └── TravelDocument.php
│ ├── Parser
│ ├── ParserInterface.php
│ ├── ParserFactory.php
│ ├── AbstractParser.php
│ ├── PassportParser.php
│ └── TravelDocumentType1Parser.php
│ ├── MrzParserInterface.php
│ └── MrzParser.php
├── phpunit.xml.dist
├── composer.json
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | add('Deft\\', __DIR__);
5 |
--------------------------------------------------------------------------------
/src/Deft/MrzParser/Exception/Exception.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./tests
6 |
7 |
8 |
9 |
10 | ./
11 |
12 | ./tests
13 | ./vendor
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "deft/mrz-parser",
3 | "description": "Library to parse machine readable zones (MRZ) of passports and ID cards",
4 | "keywords": ["mrz", "parsing", "passport", "identity document"],
5 | "authors": [
6 | {
7 | "name": "Jan Kramer",
8 | "email": "jan@jankramer.eu"
9 | }
10 | ],
11 | "minimum-stability": "dev",
12 | "require": {
13 | },
14 | "autoload": {
15 | "psr-0": {
16 | "Deft": "src/"
17 | }
18 | },
19 | "extra": {
20 | "branch-alias": {
21 | "dev-master": "1.0.x-dev"
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Deft/MrzParser/MrzParserInterface.php:
--------------------------------------------------------------------------------
1 | parseString($mrz);
17 |
18 | // Use getters to access the parsed information
19 | print $travelDocument->getDocumentNumber(); // Will print 'L898902C'
20 |
21 | // Parse array of lines
22 | $mrz = [
23 | "PparseLines($mrz);
27 | ```
--------------------------------------------------------------------------------
/src/Deft/MrzParser/MrzParser.php:
--------------------------------------------------------------------------------
1 | parserFactory = new Parser\ParserFactory();
18 | }
19 |
20 | /**
21 | * Parse a MRZ string, i.e. the lines of a document are concatenated
22 | *
23 | * @param $string
24 | * @return TravelDocumentInterface
25 | */
26 | public function parseString($string)
27 | {
28 | $parser = $this->parserFactory->create($string);
29 |
30 | return $parser->parse($string);
31 | }
32 |
33 | /**
34 | * Parse an array of MRZ lines of one document
35 | *
36 | * @param string[] $lines
37 | * @return TravelDocumentInterface
38 | */
39 | public function parseLines(array $lines)
40 | {
41 | return $this->parseString(implode('', $lines));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Deft/MrzParser/Parser/AbstractParser.php:
--------------------------------------------------------------------------------
1 | getToken($string, $start, $start + 5);
26 |
27 | $centennialBorder = new \DateTime("+15 year");
28 | $dateToken = (substr($dateToken, 0, 2) > $centennialBorder->format('y') ? '19' : 20) . $dateToken;
29 |
30 | return \DateTime::createFromFormat('Ymd', $dateToken);
31 | }
32 |
33 | protected function getNames($string, $start, $finish)
34 | {
35 | $token = substr($string, ($start-1), ($finish - $start)+1);
36 | $nameParts = explode('<<', $token);
37 | if (count($nameParts) < 2) throw new ParseException("Names could not be parsed.");
38 | return array_slice(
39 | array_map(function ($str) { return $this->clean($str); }, $nameParts),
40 | 0,
41 | 2
42 | );
43 | }
44 |
45 | protected function clean($string)
46 | {
47 | return trim(str_replace('<', ' ', $string));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Deft/MrzParser/Document/TravelDocumentInterface.php:
--------------------------------------------------------------------------------
1 | getToken($string, 1) != 'P') {
42 | throw new ParseException("First character is not 'P'");
43 | }
44 |
45 | $fields = [
46 | 'type' => TravelDocumentType::PASSPORT,
47 | 'issuingCountry' => $this->getToken($string, 3, 5),
48 | 'documentNumber' => $this->getToken($string, 45, 53),
49 | 'nationality' => $this->getToken($string, 55, 57),
50 | 'dateOfBirth' => $this->getDateToken($string, 58),
51 | 'sex' => $this->getToken($string, 65),
52 | 'dateOfExpiry' => $this->getDateToken($string, 66),
53 | 'personalNumber' => $this->getToken($string, 73, 86)
54 | ];
55 |
56 | $names = $this->getNames($string, 6, 44);
57 | $fields['primaryIdentifier'] = $names[0];
58 | $fields['secondaryIdentifier'] = $names[1];
59 |
60 | return new TravelDocument($fields);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Deft/MrzParser/Parser/TravelDocumentType1Parser.php:
--------------------------------------------------------------------------------
1 | getToken($string, 1), ['I', 'A', 'C'])) {
43 | throw new ParseException("First character is not 'I', 'A', or 'C'");
44 | }
45 |
46 | $fields = [
47 | 'type' => TravelDocumentType::ID_CARD,
48 | 'issuingCountry' => $this->getToken($string, 3, 5),
49 | 'documentNumber' => $this->getToken($string, 6, 14),
50 | 'personalNumber' => $this->getToken($string, 16, 29),
51 | 'dateOfBirth' => $this->getDateToken($string, 31),
52 | 'sex' => $this->getToken($string, 38),
53 | 'dateOfExpiry' => $this->getDateToken($string, 39),
54 | 'nationality' => $this->getToken($string, 46, 48)
55 | ];
56 |
57 | $names = $this->getNames($string, 61, 90);
58 | $fields['primaryIdentifier'] = $names[0];
59 | $fields['secondaryIdentifier'] = $names[1];
60 |
61 | return new TravelDocument($fields);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Deft/MrzParser/Document/TravelDocument.php:
--------------------------------------------------------------------------------
1 | $value) {
24 | $this->{$fieldName} = $value;
25 | }
26 | }
27 |
28 | /**
29 | * The primary type of this document, one of the constants in TravelDocumentType.
30 | *
31 | * @return string
32 | */
33 | public function getType()
34 | {
35 | return $this->type;
36 | }
37 |
38 | /**
39 | * The number that identifies this document.
40 | *
41 | * @return string
42 | */
43 | public function getDocumentNumber()
44 | {
45 | return $this->documentNumber;
46 | }
47 |
48 | /**
49 | * The country that issued this travel document as 3-letter country code (ISO 3166-1 alpha-3).
50 | *
51 | * @return string
52 | */
53 | public function getIssuingCountry()
54 | {
55 | return $this->issuingCountry;
56 | }
57 |
58 | /**
59 | * The date this document expires.
60 | *
61 | * @return \DateTime
62 | */
63 | public function getDateOfExpiry()
64 | {
65 | return $this->dateOfExpiry;
66 | }
67 |
68 | /**
69 | * The primary identifier of the document holder, typically its surname or family name.
70 | *
71 | * @return string
72 | */
73 | public function getPrimaryIdentifier()
74 | {
75 | return $this->primaryIdentifier;
76 | }
77 |
78 | /**
79 | * The secondary identifier of the document holder, commonly its given names.
80 | *
81 | * @return string
82 | */
83 | public function getSecondaryIdentifier()
84 | {
85 | return $this->secondaryIdentifier;
86 | }
87 |
88 | /**
89 | * The sex of the document holder, one of the constants in Gender.
90 | *
91 | * @return string
92 | */
93 | public function getSex()
94 | {
95 | return $this->sex;
96 | }
97 |
98 | /**
99 | * The date of birth of the document holder.
100 | *
101 | * @return \DateTime
102 | */
103 | public function getDateOfBirth()
104 | {
105 | return $this->dateOfBirth;
106 | }
107 |
108 | /**
109 | * The nationality of the document holder as 3-letter country code (ISO 3166-1 alpha-3).
110 | *
111 | * @return string
112 | */
113 | public function getNationality()
114 | {
115 | return $this->nationality;
116 | }
117 |
118 | /**
119 | * The personal number that identifies the document holder in its state.
120 | *
121 | * @return strinmg
122 | */
123 | public function getPersonalNumber()
124 | {
125 | return $this->personalNumber;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/tests/Deft/MrzParser/Tests/ParserTest.php:
--------------------------------------------------------------------------------
1 | parser = new MrzParser();
20 | }
21 |
22 | public function testParse_passportString()
23 | {
24 | $mrzString = "Pparser->parseString($mrzString);
27 | $fields = [
28 | 'type' => TravelDocumentType::PASSPORT,
29 | 'number' => 'L898902C',
30 | 'issuingCountry' => 'UTO',
31 | 'dateOfExpiry' => '23-06-1994',
32 | 'primaryIdentifier' => 'ERIKSSON',
33 | 'secondaryIdentifier' => 'ANNA MARIA',
34 | 'sex' => Sex::FEMALE,
35 | 'dateOfBirth' => '06-08-1969',
36 | 'nationality' => 'UTO',
37 | 'personalNumber' => 'ZE184226B'
38 | ];
39 | $this->assertValidDocument($document, $fields);
40 | }
41 |
42 | public function testParse_idcardArray()
43 | {
44 | $mrzString = [
45 | "Iparser->parseLines($mrzString);
51 | $fields = [
52 | 'type' => TravelDocumentType::ID_CARD,
53 | 'number' => 'IU4FC08Q1',
54 | 'issuingCountry' => 'UTO',
55 | 'dateOfExpiry' => '16-03-2016',
56 | 'primaryIdentifier' => 'DOE',
57 | 'secondaryIdentifier' => 'JOHN',
58 | 'sex' => Sex::MALE,
59 | 'dateOfBirth' => '03-06-1993',
60 | 'nationality' => 'UTO',
61 | 'personalNumber' => '219340341'
62 | ];
63 | $this->assertValidDocument($document, $fields);
64 | }
65 |
66 | protected function assertValidDocument($document, $fields)
67 | {
68 | $this->assertEquals($fields['type'], $document->getType(), "Incorrect document type");
69 | $this->assertEquals($fields['number'], $document->getDocumentNumber(), "Incorrect document number");
70 | $this->assertEquals($fields['issuingCountry'], $document->getIssuingCountry(), "Incorrect issuing country");
71 | $this->assertInstanceOf('DateTime', $document->getDateOfExpiry(), "Date of expiry is not a DateTime");
72 | $this->assertEquals($fields['dateOfExpiry'], $document->getDateOfExpiry()->format('d-m-Y'), "Invalid date of expiry");
73 | $this->assertEquals($fields['primaryIdentifier'], $document->getPrimaryIdentifier(), "Invalid primary identifier");
74 | $this->assertEquals($fields['secondaryIdentifier'], $document->getSecondaryIdentifier(), "Invalid secondary identifier");
75 | $this->assertEquals($fields['sex'], $document->getSex(), "Invalid sex");
76 | $this->assertInstanceOf('DateTime', $document->getDateOfBirth(), "Date of birth is not a DateTime");
77 | $this->assertEquals($fields['dateOfBirth'], $document->getDateOfBirth()->format('d-m-Y'), "Invalid date of birth");
78 | $this->assertEquals($fields['nationality'], $document->getNationality(), "Invalid nationality");
79 | $this->assertEquals($fields['personalNumber'], $document->getPersonalNumber(), "Invalid personal number");
80 | }
81 | }
82 |
--------------------------------------------------------------------------------