├── .gitignore ├── src ├── main │ ├── scripts │ │ ├── demo.sh │ │ └── demo.bat │ ├── java │ │ └── com │ │ │ └── innovatrics │ │ │ └── mrz │ │ │ ├── types │ │ │ ├── MrzSex.java │ │ │ ├── MrzDocumentCode.java │ │ │ ├── MrzDate.java │ │ │ └── MrzFormat.java │ │ │ ├── MrzParseException.java │ │ │ ├── MrzRange.java │ │ │ ├── Demo.java │ │ │ ├── records │ │ │ ├── SlovakId2_34.java │ │ │ ├── MrvB.java │ │ │ ├── MrvA.java │ │ │ ├── MrtdTd2.java │ │ │ ├── FrenchIdCard.java │ │ │ ├── MRP.java │ │ │ └── MrtdTd1.java │ │ │ ├── MrzRecord.java │ │ │ └── MrzParser.java │ └── assembly │ │ └── zip.xml └── test │ └── java │ └── com │ └── innovatrics │ └── mrz │ ├── records │ ├── MrvBTest.java │ ├── MrvATest.java │ ├── MrtdTd2Test.java │ ├── FrenchIdCardTest.java │ ├── MRPTest.java │ ├── SlovakId2_34Test.java │ └── MrtdTd1Test.java │ ├── types │ └── MrzDateTest.java │ └── MrzParserTest.java ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | build.gradle 3 | proguard-rules.pro 4 | src/main/AndroidManifest.xml 5 | src/main/res/ 6 | /target 7 | /.idea 8 | *.iml -------------------------------------------------------------------------------- /src/main/scripts/demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Java parser for the MRZ records, as specified by the ICAO organization. 3 | # Copyright (C) 2011 Innovatrics s.r.o. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 2.1 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library; if not, write to the Free Software 17 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | # 19 | 20 | CP=`ls lib/*.jar|tr '\n' ':'` 21 | java $JAVA_OPTS -cp $CP com.innovatrics.mrz.Demo "$@" 22 | 23 | -------------------------------------------------------------------------------- /src/main/scripts/demo.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem 3 | rem Java parser for the MRZ records, as specified by the ICAO organization. 4 | rem Copyright (C) 2011 Innovatrics s.r.o. 5 | rem 6 | rem This library is free software; you can redistribute it and/or 7 | rem modify it under the terms of the GNU Lesser General Public 8 | rem License as published by the Free Software Foundation; either 9 | rem version 2.1 of the License, or (at your option) any later version. 10 | rem 11 | rem This library is distributed in the hope that it will be useful, 12 | rem but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | rem Lesser General Public License for more details. 15 | rem 16 | rem You should have received a copy of the GNU Lesser General Public 17 | rem License along with this library; if not, write to the Free Software 18 | rem Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | rem 20 | 21 | if "%OS%"=="Windows_NT" @setlocal 22 | SET CLSPATH=. 23 | set JAVA_OPTS=-Xmx1024m 24 | rem Add platform runtime libraries 25 | for %%i in (lib\*.jar) do call :cpappend %%i 26 | rem Launch the runtime 27 | java %JAVA_OPTS% -classpath "%CLSPATH%" com.innovatrics.mrz.Demo %1 %2 %3 %4 %5 %6 %7 %8 %9 28 | if "%OS%"=="Windows_NT" @endlocal 29 | goto :EOF 30 | 31 | :cpappend 32 | set CLSPATH=%CLSPATH%;%1 33 | goto :EOF 34 | 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MRZ 2 | 3 | Machine-Readable Zone (MRZ, see http://en.wikipedia.org/wiki/Machine-readable_passport ) parser for Java, as defined by ICAO: http://www.icao.int/ 4 | 5 | ## Branches 6 | 7 | Branch "master" is for hotfixes only. For enhancements and minor bugfixes, please use branch "development". 8 | 9 | ## Example 10 | 11 | See the https://github.com/ZsBT/mrz-java/blob/master/src/main/java/com/innovatrics/mrz/Demo.java file for example on usage. Simple usage: 12 | 13 | ```java 14 | final MrzRecord record = MrzParser.parse("I 2 | 22 | 23 | zip 24 | 25 | zip 26 | 27 | false 28 | 29 | 30 | 31 | /lib 32 | false 33 | 34 | 35 | 36 | 37 | 38 | src/main/scripts 39 | / 40 | 41 | *.sh 42 | 43 | 44 | 45 | src/main/scripts 46 | / 47 | 0755 48 | 49 | *.sh 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/main/java/com/innovatrics/mrz/MrzParseException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz; 20 | 21 | import com.innovatrics.mrz.types.MrzFormat; 22 | 23 | /** 24 | * Thrown when a MRZ parse fails. 25 | * @author Martin Vysny 26 | */ 27 | public class MrzParseException extends RuntimeException { 28 | 29 | private static final long serialVersionUID = 1L; 30 | /** 31 | * The MRZ string being parsed. 32 | */ 33 | public final String mrz; 34 | /** 35 | * Range containing problematic characters. 36 | */ 37 | public final MrzRange range; 38 | /** 39 | * Expected MRZ format. 40 | */ 41 | public final MrzFormat format; 42 | 43 | public MrzParseException(String message, String mrz, MrzRange range, MrzFormat format) { 44 | super("Failed to parse MRZ " + format + " " + mrz + " at " + range + ": " + message); 45 | this.mrz = mrz; 46 | this.format = format; 47 | this.range = range; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/innovatrics/mrz/MrzRange.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz; 20 | 21 | import java.io.Serializable; 22 | 23 | /** 24 | * Represents a text selection range. 25 | * @author Martin Vysny 26 | */ 27 | public class MrzRange implements Serializable { 28 | private static final long serialVersionUID = 1L; 29 | 30 | /** 31 | * 0-based index of first character in the range. 32 | */ 33 | public final int column; 34 | /** 35 | * 0-based index of a character after last character in the range. 36 | */ 37 | public final int columnTo; 38 | /** 39 | * 0-based row. 40 | */ 41 | public final int row; 42 | 43 | /** 44 | * Creates new MRZ range object. 45 | * @param column 0-based index of first character in the range. 46 | * @param columnTo 0-based index of a character after last character in the range. 47 | * @param row 0-based row. 48 | */ 49 | public MrzRange(int column, int columnTo, int row) { 50 | if (column > columnTo) { 51 | throw new IllegalArgumentException("Parameter column: invalid value " + column + ": must be less than " + columnTo); 52 | } 53 | this.column = column; 54 | this.columnTo = columnTo; 55 | this.row = row; 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "" + column + "-" + columnTo + "," + row; 61 | } 62 | 63 | /** 64 | * Returns length of this range. 65 | * @return number of characters, which this range covers. 66 | */ 67 | public int length() { 68 | return columnTo - column; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/com/innovatrics/mrz/records/MrvBTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz.records; 20 | import com.innovatrics.mrz.MrzParser; 21 | import com.innovatrics.mrz.types.MrzDate; 22 | import com.innovatrics.mrz.types.MrzDocumentCode; 23 | import com.innovatrics.mrz.types.MrzSex; 24 | import org.junit.Test; 25 | import static org.junit.Assert.*; 26 | 27 | /** 28 | * Tests {@link MrvB}. 29 | * @author Martin Vysny 30 | */ 31 | public class MrvBTest { 32 | @Test 33 | public void testMrvVisaBCardParsing() { 34 | final MrvB r = (MrvB) MrzParser.parse("V pos) { 40 | if (row == currentRow && currentCol == col) { 41 | return pos; 42 | } 43 | if (text.charAt(pos) == '\n') { 44 | currentRow++; 45 | currentCol = 0; 46 | } else { 47 | currentCol++; 48 | } 49 | pos++; 50 | } 51 | return -1; 52 | } 53 | 54 | /** 55 | * MRZ demo. 56 | * @param args 57 | */ 58 | public static void main(String[] args) { 59 | final JFrame frame = new JFrame("MRZDemo"); 60 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 61 | final JTextArea mrz = new JTextArea(5, 44); 62 | final JButton parse = new JButton("Parse"); 63 | parse.addActionListener(new ActionListener() { 64 | 65 | public void actionPerformed(ActionEvent e) { 66 | final String m = mrz.getText(); 67 | try { 68 | final MrzRecord record = MrzParser.parse(m); 69 | JOptionPane.showMessageDialog(frame, "Parse successfull: " + record); 70 | } catch (Exception ex) { 71 | JOptionPane.showMessageDialog(frame, "Parse failed: " + ex); 72 | if (ex instanceof MrzParseException) { 73 | final MrzParseException mpe = (MrzParseException) ex; 74 | final MrzRange r = mpe.range; 75 | mrz.select(toPos(r.column, r.row, m), toPos(r.columnTo, r.row, m)); 76 | } 77 | } 78 | } 79 | }); 80 | frame.getContentPane().add(mrz, BorderLayout.CENTER); 81 | frame.getContentPane().add(parse, BorderLayout.SOUTH); 82 | frame.pack(); 83 | frame.setVisible(true); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/com/innovatrics/mrz/types/MrzDateTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz.types; 20 | 21 | import org.junit.Test; 22 | import static org.junit.Assert.*; 23 | 24 | /** 25 | * 26 | * @author Martin Vysny 27 | */ 28 | public class MrzDateTest { 29 | 30 | @Test 31 | public void testCompareTo() { 32 | assertEquals(0, new MrzDate(0, 1, 1).compareTo(new MrzDate(0, 1, 1))); 33 | assertEquals(0, new MrzDate(55, 4, 31).compareTo(new MrzDate(55, 4, 31))); 34 | assertTrue(new MrzDate(55, 4, 31).compareTo(new MrzDate(55, 4, 30)) > 0); 35 | assertTrue(new MrzDate(55, 4, 31).compareTo(new MrzDate(54, 4, 31)) > 0); 36 | assertTrue(new MrzDate(55, 4, 31).compareTo(new MrzDate(55, 3, 31)) > 0); 37 | assertTrue(new MrzDate(55, 4, 30).compareTo(new MrzDate(55, 4, 31)) < 0); 38 | assertTrue(new MrzDate(55, 3, 31).compareTo(new MrzDate(55, 4, 31)) < 0); 39 | assertTrue(new MrzDate(54, 4, 31).compareTo(new MrzDate(55, 4, 31)) < 0); 40 | } 41 | 42 | @Test 43 | public void testEquals() { 44 | assertEquals(new MrzDate(0, 1, 1), new MrzDate(0, 1, 1)); 45 | assertEquals(new MrzDate(55, 4, 31), new MrzDate(55, 4, 31)); 46 | assertFalse(new MrzDate(55, 4, 31).equals(new MrzDate(55, 4, 30))); 47 | assertFalse(new MrzDate(55, 4, 31).equals(new MrzDate(54, 4, 31))); 48 | assertFalse(new MrzDate(55, 4, 31).equals(new MrzDate(55, 3, 31))); 49 | assertFalse(new MrzDate(55, 4, 30).equals(new MrzDate(55, 4, 31))); 50 | assertFalse(new MrzDate(55, 3, 31).equals(new MrzDate(55, 4, 31))); 51 | assertFalse(new MrzDate(54, 4, 31).equals(new MrzDate(55, 4, 31))); 52 | } 53 | 54 | @Test 55 | public void testToMrz() { 56 | assertEquals("550431", new MrzDate(55, 4, 31).toMrz()); 57 | assertEquals("081201", new MrzDate(8, 12, 1).toMrz()); 58 | assertEquals("880941", new MrzDate(88, 9, 41).toMrz()); 59 | assertEquals("BB1201", new MrzDate(-1, 12, 1, "BB1201").toMrz()); 60 | } 61 | 62 | @Test 63 | public void testInvalidDate() { 64 | MrzDate date = new MrzDate(88, 9, 41); 65 | assertEquals(88, date.year); 66 | assertEquals(9, date.month); 67 | assertEquals(41, date.day); 68 | assertEquals(false, date.isDateValid()); 69 | } 70 | 71 | @Test 72 | public void testValidDate() { 73 | MrzDate date = new MrzDate(88, 9, 30); 74 | assertEquals(88, date.year); 75 | assertEquals(9, date.month); 76 | assertEquals(30, date.day); 77 | assertEquals(true, date.isDateValid()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/innovatrics/mrz/records/SlovakId2_34.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz.records; 20 | 21 | import com.innovatrics.mrz.MrzParser; 22 | import com.innovatrics.mrz.MrzRange; 23 | import com.innovatrics.mrz.MrzRecord; 24 | import com.innovatrics.mrz.types.MrzFormat; 25 | 26 | /** 27 | * Unknown 2 line/34 characters per line format, used with old Slovak ID cards. 28 | * @author Martin Vysny 29 | */ 30 | public class SlovakId2_34 extends MrzRecord { 31 | private static final long serialVersionUID = 1L; 32 | 33 | public SlovakId2_34() { 34 | super(MrzFormat.SLOVAK_ID_234); 35 | } 36 | /** 37 | * For use of the issuing State or 38 | organization. Unused character positions 39 | shall be completed with filler characters (<) 40 | repeated up to position 35 as required. 41 | */ 42 | public String optional; 43 | 44 | @Override 45 | public void fromMrz(String mrz) { 46 | super.fromMrz(mrz); 47 | final MrzParser p = new MrzParser(mrz); 48 | setName(p.parseName(new MrzRange(5, 34, 0))); 49 | documentNumber = p.parseString(new MrzRange(0, 9, 1)); 50 | validDocumentNumber = p.checkDigit(9, 1, new MrzRange(0, 9, 1), "document number"); 51 | nationality = p.parseString(new MrzRange(10, 13, 1)); 52 | dateOfBirth = p.parseDate(new MrzRange(13, 19, 1)); 53 | validDateOfBirth = p.checkDigit(19, 1, new MrzRange(13, 19, 1), "date of birth") && dateOfBirth.isDateValid(); 54 | sex = p.parseSex(20, 1); 55 | expirationDate = p.parseDate(new MrzRange(21, 27, 1)); 56 | validExpirationDate = p.checkDigit(27, 1, new MrzRange(21, 27, 1), "expiration date") && expirationDate.isDateValid(); 57 | optional = p.parseString(new MrzRange(28, 34, 1)); 58 | // TODO validComposite missing? (final MRZ check digit) 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return "SlovakId2x34{" + super.toString() + ", optional=" + optional + '}'; 64 | } 65 | 66 | @Override 67 | public String toMrz() { 68 | // first line 69 | final StringBuilder sb = new StringBuilder(); 70 | sb.append(code1); 71 | sb.append(code2); 72 | sb.append(MrzParser.toMrz(issuingCountry, 3)); 73 | sb.append(MrzParser.nameToMrz(surname, givenNames, 29)); 74 | sb.append('\n'); 75 | // second line 76 | sb.append(MrzParser.toMrz(documentNumber, 9)); 77 | sb.append(MrzParser.computeCheckDigitChar(MrzParser.toMrz(documentNumber, 9))); 78 | sb.append(MrzParser.toMrz(nationality, 3)); 79 | sb.append(dateOfBirth.toMrz()); 80 | sb.append(MrzParser.computeCheckDigitChar(dateOfBirth.toMrz())); 81 | sb.append(sex.mrz); 82 | sb.append(expirationDate.toMrz()); 83 | sb.append(MrzParser.computeCheckDigitChar(expirationDate.toMrz())); 84 | sb.append(MrzParser.toMrz(optional, 6)); 85 | sb.append('\n'); 86 | return sb.toString(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/innovatrics/mrz/records/MrvB.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz.records; 20 | 21 | import com.innovatrics.mrz.MrzParser; 22 | import com.innovatrics.mrz.MrzRange; 23 | import com.innovatrics.mrz.MrzRecord; 24 | import com.innovatrics.mrz.types.MrzDocumentCode; 25 | import com.innovatrics.mrz.types.MrzFormat; 26 | 27 | /** 28 | * MRV type-B format: A two lines long, 36 characters per line format 29 | * @author Jeremy Le Berre 30 | */ 31 | public class MrvB extends MrzRecord { 32 | 33 | private static final long serialVersionUID = 1L; 34 | 35 | public MrvB() { 36 | super(MrzFormat.MRV_VISA_B); 37 | code1 = 'V'; 38 | code2 = '<'; 39 | code = MrzDocumentCode.TypeV; 40 | } 41 | /** 42 | * Optional data at the discretion of the issuing State 43 | */ 44 | public String optional; 45 | 46 | @Override 47 | public void fromMrz(String mrz) { 48 | super.fromMrz(mrz); 49 | final MrzParser parser = new MrzParser(mrz); 50 | setName(parser.parseName(new MrzRange(5, 36, 0))); 51 | documentNumber = parser.parseString(new MrzRange(0, 9, 1)); 52 | validDocumentNumber = parser.checkDigit(9, 1, new MrzRange(0, 9, 1), "passport number"); 53 | nationality = parser.parseString(new MrzRange(10, 13, 1)); 54 | dateOfBirth = parser.parseDate(new MrzRange(13, 19, 1)); 55 | validDateOfBirth = parser.checkDigit(19, 1, new MrzRange(13, 19, 1), "date of birth") && dateOfBirth.isDateValid(); 56 | sex = parser.parseSex(20, 1); 57 | expirationDate = parser.parseDate(new MrzRange(21, 27, 1)); 58 | validExpirationDate = parser.checkDigit(27, 1, new MrzRange(21, 27, 1), "expiration date") && expirationDate.isDateValid(); 59 | optional = parser.parseString(new MrzRange(28, 36, 1)); 60 | // TODO validComposite missing? (full MRZ line) 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return "MRV-B{" + super.toString() + ", optional=" + optional + '}'; 66 | } 67 | 68 | @Override 69 | public String toMrz() { 70 | final StringBuilder sb = new StringBuilder("V<"); 71 | sb.append(MrzParser.toMrz(issuingCountry, 3)); 72 | sb.append(MrzParser.nameToMrz(surname, givenNames, 31)); 73 | sb.append('\n'); 74 | // second line 75 | sb.append(MrzParser.toMrz(documentNumber, 9)); 76 | sb.append(MrzParser.computeCheckDigitChar(MrzParser.toMrz(documentNumber, 9))); 77 | sb.append(MrzParser.toMrz(nationality, 3)); 78 | sb.append(dateOfBirth.toMrz()); 79 | sb.append(MrzParser.computeCheckDigitChar(dateOfBirth.toMrz())); 80 | sb.append(sex.mrz); 81 | sb.append(expirationDate.toMrz()); 82 | sb.append(MrzParser.computeCheckDigitChar(expirationDate.toMrz())); 83 | sb.append(MrzParser.toMrz(optional, 8)); 84 | sb.append('\n'); 85 | return sb.toString(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/innovatrics/mrz/records/MrvA.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz.records; 20 | 21 | import com.innovatrics.mrz.MrzParser; 22 | import com.innovatrics.mrz.MrzRange; 23 | import com.innovatrics.mrz.MrzRecord; 24 | import com.innovatrics.mrz.types.MrzDocumentCode; 25 | import com.innovatrics.mrz.types.MrzFormat; 26 | 27 | /** 28 | * MRV type-A format: A two lines long, 44 characters per line format 29 | * @author Jeremy Le Berre 30 | */ 31 | public class MrvA extends MrzRecord { 32 | 33 | private static final long serialVersionUID = 1L; 34 | 35 | public MrvA() { 36 | super(MrzFormat.MRV_VISA_A); 37 | code1 = 'V'; 38 | code2 = '<'; 39 | code = MrzDocumentCode.TypeV; 40 | } 41 | /** 42 | * Optional data at the discretion of the issuing State 43 | */ 44 | public String optional; 45 | 46 | @Override 47 | public void fromMrz(String mrz) { 48 | super.fromMrz(mrz); 49 | final MrzParser parser = new MrzParser(mrz); 50 | setName(parser.parseName(new MrzRange(5, 44, 0))); 51 | documentNumber = parser.parseString(new MrzRange(0, 9, 1)); 52 | validDocumentNumber = parser.checkDigit(9, 1, new MrzRange(0, 9, 1), "passport number"); 53 | nationality = parser.parseString(new MrzRange(10, 13, 1)); 54 | dateOfBirth = parser.parseDate(new MrzRange(13, 19, 1)); 55 | validDateOfBirth = parser.checkDigit(19, 1, new MrzRange(13, 19, 1), "date of birth") && dateOfBirth.isDateValid(); 56 | sex = parser.parseSex(20, 1); 57 | expirationDate = parser.parseDate(new MrzRange(21, 27, 1)); 58 | validExpirationDate = parser.checkDigit(27, 1, new MrzRange(21, 27, 1), "expiration date") && expirationDate.isDateValid(); 59 | optional = parser.parseString(new MrzRange(28, 44, 1)); 60 | // TODO validComposite missing? (final MRZ check digit) 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return "MRV-A{" + super.toString() + ", optional=" + optional + '}'; 66 | } 67 | 68 | @Override 69 | public String toMrz() { 70 | // first line 71 | final StringBuilder sb = new StringBuilder("V<"); 72 | sb.append(MrzParser.toMrz(issuingCountry, 3)); 73 | sb.append(MrzParser.nameToMrz(surname, givenNames, 39)); 74 | sb.append('\n'); 75 | // second line 76 | sb.append(MrzParser.toMrz(documentNumber, 9)); 77 | sb.append(MrzParser.computeCheckDigitChar(MrzParser.toMrz(documentNumber, 9))); 78 | sb.append(MrzParser.toMrz(nationality, 3)); 79 | sb.append(dateOfBirth.toMrz()); 80 | sb.append(MrzParser.computeCheckDigitChar(dateOfBirth.toMrz())); 81 | sb.append(sex.mrz); 82 | sb.append(expirationDate.toMrz()); 83 | sb.append(MrzParser.computeCheckDigitChar(expirationDate.toMrz())); 84 | sb.append(MrzParser.toMrz(optional, 16)); 85 | sb.append('\n'); 86 | return sb.toString(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/innovatrics/mrz/records/MrtdTd2.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz.records; 20 | 21 | import com.innovatrics.mrz.MrzParser; 22 | import com.innovatrics.mrz.MrzRange; 23 | import com.innovatrics.mrz.MrzRecord; 24 | import com.innovatrics.mrz.types.MrzFormat; 25 | 26 | /** 27 | * MRTD td2 format: A two line long, 36 characters per line format. 28 | * @author Martin Vysny 29 | */ 30 | public class MrtdTd2 extends MrzRecord { 31 | private static final long serialVersionUID = 1L; 32 | 33 | public MrtdTd2() { 34 | super(MrzFormat.MRTD_TD2); 35 | } 36 | /** 37 | * For use of the issuing State or 38 | organization. Unused character positions 39 | shall be completed with filler characters (<) 40 | repeated up to position 35 as required. 41 | */ 42 | public String optional; 43 | 44 | @Override 45 | public void fromMrz(String mrz) { 46 | super.fromMrz(mrz); 47 | final MrzParser p = new MrzParser(mrz); 48 | setName(p.parseName(new MrzRange(5, 36, 0))); 49 | documentNumber = p.parseString(new MrzRange(0, 9, 1)); 50 | validDocumentNumber = p.checkDigit(9, 1, new MrzRange(0, 9, 1), "document number"); 51 | nationality = p.parseString(new MrzRange(10, 13, 1)); 52 | dateOfBirth = p.parseDate(new MrzRange(13, 19, 1)); 53 | validDateOfBirth = p.checkDigit(19, 1, new MrzRange(13, 19, 1), "date of birth") && dateOfBirth.isDateValid(); 54 | sex = p.parseSex(20, 1); 55 | expirationDate = p.parseDate(new MrzRange(21, 27, 1)); 56 | validExpirationDate = p.checkDigit(27, 1, new MrzRange(21, 27, 1), "expiration date") && expirationDate.isDateValid(); 57 | optional = p.parseString(new MrzRange(28, 35, 1)); 58 | validComposite = p.checkDigit(35, 1, p.rawValue(new MrzRange(0, 10, 1), new MrzRange(13, 20, 1), new MrzRange(21, 35, 1)), "mrz"); 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return "MRTD-TD2{" + super.toString() + ", optional=" + optional + '}'; 64 | } 65 | 66 | @Override 67 | public String toMrz() { 68 | // first line 69 | final StringBuilder sb = new StringBuilder(); 70 | sb.append(code1); 71 | sb.append(code2); 72 | sb.append(MrzParser.toMrz(issuingCountry, 3)); 73 | sb.append(MrzParser.nameToMrz(surname, givenNames, 31)); 74 | sb.append('\n'); 75 | // second line 76 | final String dn = MrzParser.toMrz(documentNumber, 9) + MrzParser.computeCheckDigitChar(MrzParser.toMrz(documentNumber, 9)); 77 | sb.append(dn); 78 | sb.append(MrzParser.toMrz(nationality, 3)); 79 | final String dob = dateOfBirth.toMrz() + MrzParser.computeCheckDigitChar(dateOfBirth.toMrz()); 80 | sb.append(dob); 81 | sb.append(sex.mrz); 82 | final String ed = expirationDate.toMrz() + MrzParser.computeCheckDigitChar(expirationDate.toMrz()); 83 | sb.append(ed); 84 | sb.append(MrzParser.toMrz(optional, 7)); 85 | sb.append(MrzParser.computeCheckDigitChar(dn + dob + ed + MrzParser.toMrz(optional, 7))); 86 | sb.append('\n'); 87 | return sb.toString(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/innovatrics/mrz/records/FrenchIdCard.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz.records; 20 | 21 | import com.innovatrics.mrz.MrzParser; 22 | import com.innovatrics.mrz.MrzRange; 23 | import com.innovatrics.mrz.MrzRecord; 24 | import com.innovatrics.mrz.types.MrzDocumentCode; 25 | import com.innovatrics.mrz.types.MrzFormat; 26 | 27 | /** 28 | * Format used for French ID Cards. 29 | *

30 | * The structure of the card: 31 | * 2 lines of 36 characters : 32 |

First line : IDFRA{name}{many < to complete line}{6 numbers unknown}
33 | Second line : {card number on 12 numbers}{Check digit}{given names separated by "<<" and maybe troncated if too long}{date of birth YYMMDD}{Check digit}{sex M/F}{1 number checksum}
34 | * @author Pierrick Martin, Marin Moulinier 35 | */ 36 | public class FrenchIdCard extends MrzRecord { 37 | 38 | private static final long serialVersionUID = 1L; 39 | 40 | public FrenchIdCard() { 41 | super(MrzFormat.FRENCH_ID); 42 | code = MrzDocumentCode.TypeI; 43 | code1 = 'I'; 44 | code2 = 'D'; 45 | } 46 | /** 47 | * For use of the issuing State or 48 | organization. 49 | */ 50 | public String optional; 51 | 52 | @Override 53 | public void fromMrz(String mrz) { 54 | super.fromMrz(mrz); 55 | final MrzParser p = new MrzParser(mrz); 56 | //Special because surname and firstname not on the same line 57 | String[] name = new String[]{"", ""}; 58 | name[0] = p.parseString(new MrzRange(5, 30, 0)); 59 | name[1] = p.parseString(new MrzRange(13, 27, 1)); 60 | setName(name); 61 | nationality = p.parseString(new MrzRange(2, 5, 0)); 62 | optional = p.parseString(new MrzRange(30, 36, 0)); 63 | documentNumber = p.parseString(new MrzRange(0, 12, 1)); 64 | validDocumentNumber = p.checkDigit(12, 1, new MrzRange(0, 12, 1), "document number"); 65 | dateOfBirth = p.parseDate(new MrzRange(27, 33, 1)); 66 | validDateOfBirth = p.checkDigit(33, 1, new MrzRange(27, 33, 1), "date of birth") && dateOfBirth.isDateValid(); 67 | sex = p.parseSex(34, 1); 68 | final String finalChecksum = mrz.toString().replace("\n","").substring(0, 36 + 35); 69 | validComposite = p.checkDigit(35, 1, finalChecksum, "final checksum"); 70 | // TODO expirationDate is missing 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return "FrenchIdCard{" + super.toString() + ", optional=" + optional + '}'; 76 | } 77 | 78 | @Override 79 | public String toMrz() { 80 | final StringBuilder sb = new StringBuilder("IDFRA"); 81 | // first row 82 | sb.append(MrzParser.toMrz(surname, 25)); 83 | sb.append(MrzParser.toMrz(optional, 6)); 84 | sb.append('\n'); 85 | // second row 86 | sb.append(MrzParser.toMrz(documentNumber, 12)); 87 | sb.append(MrzParser.computeCheckDigitChar(MrzParser.toMrz(documentNumber, 12))); 88 | sb.append(MrzParser.toMrz(givenNames, 14)); 89 | sb.append(dateOfBirth.toMrz()); 90 | sb.append(MrzParser.computeCheckDigitChar(dateOfBirth.toMrz())); 91 | sb.append(sex.mrz); 92 | sb.append(MrzParser.computeCheckDigitChar(sb.toString().replace("\n",""))); 93 | sb.append('\n'); 94 | return sb.toString(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/innovatrics/mrz/records/MRP.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz.records; 20 | 21 | import com.innovatrics.mrz.MrzParser; 22 | import com.innovatrics.mrz.MrzRange; 23 | import com.innovatrics.mrz.MrzRecord; 24 | import com.innovatrics.mrz.types.MrzFormat; 25 | 26 | /** 27 | * MRP Passport format: A two line long, 44 characters per line format. 28 | * @author Martin Vysny 29 | */ 30 | public class MRP extends MrzRecord { 31 | private static final long serialVersionUID = 1L; 32 | 33 | public MRP() { 34 | super(MrzFormat.PASSPORT); 35 | } 36 | /** 37 | * personal number (may be used by the issuing country as it desires), 14 characters long. 38 | */ 39 | public String personalNumber; 40 | 41 | public boolean validPersonalNumber; 42 | 43 | @Override 44 | public void fromMrz(String mrz) { 45 | super.fromMrz(mrz); 46 | final MrzParser parser = new MrzParser(mrz); 47 | setName(parser.parseName(new MrzRange(5, 44, 0))); 48 | documentNumber = parser.parseString(new MrzRange(0, 9, 1)); 49 | validDocumentNumber = parser.checkDigit(9, 1, new MrzRange(0, 9, 1), "passport number"); 50 | nationality = parser.parseString(new MrzRange(10, 13, 1)); 51 | dateOfBirth = parser.parseDate(new MrzRange(13, 19, 1)); 52 | validDateOfBirth = parser.checkDigit(19, 1, new MrzRange(13, 19, 1), "date of birth") && dateOfBirth.isDateValid(); 53 | sex = parser.parseSex(20, 1); 54 | expirationDate = parser.parseDate(new MrzRange(21, 27, 1)); 55 | validExpirationDate = parser.checkDigit(27, 1, new MrzRange(21, 27, 1), "expiration date") && expirationDate.isDateValid(); 56 | personalNumber = parser.parseString(new MrzRange(28, 42, 1)); 57 | validPersonalNumber = parser.checkDigit(42, 1, new MrzRange(28, 42, 1), "personal number"); 58 | validComposite = parser.checkDigit(43, 1, parser.rawValue(new MrzRange(0, 10, 1), new MrzRange(13, 20, 1), new MrzRange(21, 43, 1)), "mrz"); 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return "MRP{" + super.toString() + ", personalNumber=" + personalNumber + '}'; 64 | } 65 | 66 | @Override 67 | public String toMrz() { 68 | // first line 69 | final StringBuilder sb = new StringBuilder(); 70 | sb.append(code1); 71 | sb.append(code2); 72 | sb.append(MrzParser.toMrz(issuingCountry, 3)); 73 | sb.append(MrzParser.nameToMrz(surname, givenNames, 39)); 74 | sb.append('\n'); 75 | // second line 76 | final String docNum = MrzParser.toMrz(documentNumber, 9) + MrzParser.computeCheckDigitChar(MrzParser.toMrz(documentNumber, 9)); 77 | sb.append(docNum); 78 | sb.append(MrzParser.toMrz(nationality, 3)); 79 | final String dob = dateOfBirth.toMrz() + MrzParser.computeCheckDigitChar(dateOfBirth.toMrz()); 80 | sb.append(dob); 81 | sb.append(sex.mrz); 82 | final String edpn = expirationDate.toMrz() + MrzParser.computeCheckDigitChar(expirationDate.toMrz()) + MrzParser.toMrz(personalNumber, 14) + MrzParser.computeCheckDigitChar(MrzParser.toMrz(personalNumber, 14)); 83 | sb.append(edpn); 84 | sb.append(MrzParser.computeCheckDigitChar(docNum + dob + edpn)); 85 | sb.append('\n'); 86 | return sb.toString(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/innovatrics/mrz/records/MrtdTd1.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz.records; 20 | 21 | import com.innovatrics.mrz.MrzParser; 22 | import com.innovatrics.mrz.MrzRange; 23 | import com.innovatrics.mrz.MrzRecord; 24 | import com.innovatrics.mrz.types.MrzFormat; 25 | 26 | /** 27 | * MRTD TD1 format: A three line long, 30 characters per line format. 28 | * @author Martin Vysny 29 | */ 30 | public class MrtdTd1 extends MrzRecord { 31 | private static final long serialVersionUID = 1L; 32 | 33 | public MrtdTd1() { 34 | super(MrzFormat.MRTD_TD1); 35 | } 36 | /** 37 | * Optional data at the discretion 38 | of the issuing State. May contain 39 | an extended document number 40 | as per 6.7, note (j). 41 | */ 42 | public String optional; 43 | /** 44 | * optional (for U.S. passport holders, 21-29 may be corresponding passport number) 45 | */ 46 | public String optional2; 47 | 48 | @Override 49 | public void fromMrz(String mrz) { 50 | super.fromMrz(mrz); 51 | final MrzParser p = new MrzParser(mrz); 52 | documentNumber = p.parseString(new MrzRange(5, 14, 0)); 53 | validDocumentNumber = p.checkDigit(14, 0, new MrzRange(5, 14, 0), "document number"); 54 | optional = p.parseString(new MrzRange(15, 30, 0)); 55 | dateOfBirth = p.parseDate(new MrzRange(0, 6, 1)); 56 | validDateOfBirth = p.checkDigit(6, 1, new MrzRange(0, 6, 1), "date of birth") && dateOfBirth.isDateValid(); 57 | sex = p.parseSex(7, 1); 58 | expirationDate = p.parseDate(new MrzRange(8, 14, 1)); 59 | validExpirationDate = p.checkDigit(14, 1, new MrzRange(8, 14, 1), "expiration date") && expirationDate.isDateValid(); 60 | nationality = p.parseString(new MrzRange(15, 18, 1)); 61 | optional2 = p.parseString(new MrzRange(18, 29, 1)); 62 | validComposite = p.checkDigit(29, 1, p.rawValue(new MrzRange(5, 30, 0), new MrzRange(0, 7, 1), new MrzRange(8, 15, 1), new MrzRange(18, 29, 1)), "mrz"); 63 | setName(p.parseName(new MrzRange(0, 30, 2))); 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "MRTD-TD1{" + super.toString() + ", optional=" + optional + ", optional2=" + optional2 + '}'; 69 | } 70 | 71 | @Override 72 | public String toMrz() { 73 | // first line 74 | final StringBuilder sb = new StringBuilder(); 75 | sb.append(code1); 76 | sb.append(code2); 77 | sb.append(MrzParser.toMrz(issuingCountry, 3)); 78 | final String dno = MrzParser.toMrz(documentNumber, 9) + MrzParser.computeCheckDigitChar(MrzParser.toMrz(documentNumber, 9)) + MrzParser.toMrz(optional, 15); 79 | sb.append(dno); 80 | sb.append('\n'); 81 | // second line 82 | final String dob = dateOfBirth.toMrz() + MrzParser.computeCheckDigitChar(dateOfBirth.toMrz()); 83 | sb.append(dob); 84 | sb.append(sex.mrz); 85 | final String ed = expirationDate.toMrz() + MrzParser.computeCheckDigitChar(expirationDate.toMrz()); 86 | sb.append(ed); 87 | sb.append(MrzParser.toMrz(nationality, 3)); 88 | sb.append(MrzParser.toMrz(optional2, 11)); 89 | sb.append(MrzParser.computeCheckDigitChar(dno + dob + ed + MrzParser.toMrz(optional2, 11))); 90 | sb.append('\n'); 91 | // third line 92 | sb.append(MrzParser.nameToMrz(surname, givenNames, 30)); 93 | sb.append('\n'); 94 | return sb.toString(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/innovatrics/mrz/types/MrzDate.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz.types; 20 | 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import java.io.Serializable; 25 | 26 | 27 | /** 28 | * Holds a MRZ date type. 29 | * @author Martin Vysny 30 | */ 31 | public class MrzDate implements Serializable, Comparable { 32 | private static final long serialVersionUID = 1L; 33 | 34 | private static Logger log = LoggerFactory.getLogger(MrzDate.class); 35 | 36 | 37 | /** 38 | * Year, 00-99. 39 | *

40 | * Note: I am unable to find a specification of conversion of this value to a full year value. 41 | */ 42 | public final int year; 43 | /** 44 | * Month, 1-12. 45 | */ 46 | public final int month; 47 | /** 48 | * Day, 1-31. 49 | */ 50 | public final int day; 51 | 52 | private final String mrz; 53 | 54 | /** 55 | * Is the date valid or not 56 | */ 57 | private final boolean isValidDate; 58 | 59 | public MrzDate(int year, int month, int day) { 60 | this.year = year; 61 | this.month = month; 62 | this.day = day; 63 | isValidDate = check(); 64 | this.mrz = null; 65 | } 66 | 67 | public MrzDate(int year, int month, int day, String raw) { 68 | this.year = year; 69 | this.month = month; 70 | this.day = day; 71 | isValidDate = check(); 72 | this.mrz = raw; 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return "{" + day + "/" + month + "/" + year + '}'; 78 | } 79 | 80 | public String toMrz() { 81 | if(mrz != null) { 82 | return mrz; 83 | } else { 84 | return String.format("%02d%02d%02d", year, month, day); 85 | } 86 | } 87 | 88 | private boolean check() { 89 | if (year < 0 || year > 99) { 90 | log.debug("Parameter year: invalid value " + year + ": must be 0..99"); 91 | return false; 92 | } 93 | if (month < 1 || month > 12) { 94 | log.debug("Parameter month: invalid value " + month + ": must be 1..12"); 95 | return false; 96 | } 97 | if (day < 1 || day > 31) { 98 | log.debug("Parameter day: invalid value " + day + ": must be 1..31"); 99 | return false; 100 | } 101 | 102 | return true; 103 | } 104 | 105 | @Override 106 | public boolean equals(Object obj) { 107 | if (obj == null) { 108 | return false; 109 | } 110 | if (getClass() != obj.getClass()) { 111 | return false; 112 | } 113 | final MrzDate other = (MrzDate) obj; 114 | if (this.year != other.year) { 115 | return false; 116 | } 117 | if (this.month != other.month) { 118 | return false; 119 | } 120 | if (this.day != other.day) { 121 | return false; 122 | } 123 | return true; 124 | } 125 | 126 | @Override 127 | public int hashCode() { 128 | int hash = 7; 129 | hash = 11 * hash + this.year; 130 | hash = 11 * hash + this.month; 131 | hash = 11 * hash + this.day; 132 | return hash; 133 | } 134 | 135 | public int compareTo(MrzDate o) { 136 | return Integer.valueOf(year * 10000 + month * 100 + day).compareTo(o.year * 10000 + o.month * 100 + o.day); 137 | } 138 | 139 | /** 140 | * Returns the date validity 141 | * @return Returns a boolean true if the parsed date is valid, false otherwise 142 | */ 143 | public boolean isDateValid() { 144 | return isValidDate; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 4.0.0 24 | com.innovatrics.mrz 25 | mrz-java 26 | jar 27 | 0.6-SNAPSHOT 28 | MRZ Java Parser 29 | https://github.com/ZsBT/mrz-java 30 | 2011 31 | Provides Java MRZ parser 32 | 33 | Innovatrics 34 | http://www.innovatrics.com 35 | 36 | 37 | scm:hg:ssh://git@github.com/ZsBT/mrz-java 38 | 39 | 40 | Github 41 | https://github.com/ZsBT/mrz-java/issues 42 | 43 | 44 | 45 | The GNU Lesser General Public License, version 2.1 (LGPL-2.1) 46 | http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html 47 | 48 | 49 | 50 | 51 | mvy 52 | Martin Vysny 53 | vysny@baka.sk 54 | 1 55 | 56 | developer 57 | 58 | 59 | 60 | 61 | 62 | 63 | maven-compiler-plugin 64 | 3.0 65 | 66 | 1.7 67 | 1.7 68 | 69 | 70 | 71 | maven-source-plugin 72 | 2.2.1 73 | 74 | 75 | package 76 | 77 | jar 78 | 79 | 80 | 81 | 82 | 83 | maven-javadoc-plugin 84 | 2.9 85 | 86 | 87 | -Xdoclint:none 88 | 89 | 90 | 91 | package 92 | 93 | jar 94 | 95 | 96 | 97 | 98 | 99 | maven-assembly-plugin 100 | 101 | 102 | src/main/assembly/zip.xml 103 | 104 | 105 | 106 | 107 | make-assembly 108 | package 109 | 110 | single 111 | 112 | 113 | 114 | 115 | 116 | maven-release-plugin 117 | 118 | true 119 | 120 | 121 | 122 | 123 | 124 | 125 | junit 126 | junit 127 | 4.13.1 128 | test 129 | 130 | 131 | org.slf4j 132 | slf4j-api 133 | 1.7.25 134 | 135 | 136 | ch.qos.logback 137 | logback-classic 138 | 1.2.13 139 | test 140 | 141 | 142 | 143 | UTF-8 144 | 145 | 146 | -------------------------------------------------------------------------------- /src/main/java/com/innovatrics/mrz/types/MrzFormat.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz.types; 20 | 21 | import com.innovatrics.mrz.MrzParseException; 22 | import com.innovatrics.mrz.MrzRange; 23 | import com.innovatrics.mrz.MrzRecord; 24 | import com.innovatrics.mrz.records.FrenchIdCard; 25 | import com.innovatrics.mrz.records.MRP; 26 | import com.innovatrics.mrz.records.MrtdTd1; 27 | import com.innovatrics.mrz.records.MrtdTd2; 28 | import com.innovatrics.mrz.records.MrvA; 29 | import com.innovatrics.mrz.records.MrvB; 30 | import com.innovatrics.mrz.records.SlovakId2_34; 31 | 32 | /** 33 | * Lists all supported MRZ formats. Note that the order of the enum constants are important, see for example {@link #FRENCH_ID}. 34 | * @author Martin Vysny, Pierrick Martin 35 | */ 36 | public enum MrzFormat { 37 | 38 | /** 39 | * MRTD td1 format: A three line long, 30 characters per line format. 40 | */ 41 | MRTD_TD1(3, 30, MrtdTd1.class), 42 | /** 43 | * French 2 line/36 characters per line format, used with French ID cards. 44 | * Need to occur before the {@link #MRTD_TD2} enum constant because of the same values for row/column. 45 | * See below for the "if" test. 46 | */ 47 | FRENCH_ID(2, 36, FrenchIdCard.class) { 48 | 49 | public boolean isFormatOf(String[] mrzRows) { 50 | if (!super.isFormatOf(mrzRows)) { 51 | return false; 52 | } 53 | return mrzRows[0].substring(0, 5).equals("IDFRA"); 54 | } 55 | }, 56 | /** 57 | * MRV type-B format: A two lines long, 36 characters per line format. 58 | * Need to occur before the {@link #MRTD_TD2} enum constant because of the same values for row/column. 59 | * See below for the "if" test. 60 | */ 61 | MRV_VISA_B(2, 36, MrvB.class) { 62 | 63 | public boolean isFormatOf(String[] mrzRows) { 64 | if (!super.isFormatOf(mrzRows)) { 65 | return false; 66 | } 67 | return mrzRows[0].substring(0, 1).equals("V"); 68 | } 69 | }, 70 | /** 71 | * MRTD td2 format: A two line long, 36 characters per line format. 72 | */ 73 | MRTD_TD2(2, 36, MrtdTd2.class), 74 | /** 75 | * MRV type-A format: A two lines long, 44 characters per line format 76 | * Need to occur before {@link #PASSPORT} constant because of the same values for row/column. 77 | * See below for the "if" test. 78 | */ 79 | MRV_VISA_A(2, 44, MrvA.class) { 80 | 81 | public boolean isFormatOf(String[] mrzRows) { 82 | if (!super.isFormatOf(mrzRows)) { 83 | return false; 84 | } 85 | return mrzRows[0].substring(0, 1).equals("V"); 86 | } 87 | }, 88 | /** 89 | * MRP Passport format: A two line long, 44 characters per line format. 90 | */ 91 | PASSPORT(2, 44, MRP.class), 92 | /** 93 | * Unknown 2 line/34 characters per line format, used with old Slovak ID cards. 94 | */ 95 | SLOVAK_ID_234(2, 34, SlovakId2_34.class); 96 | public final int rows; 97 | public final int columns; 98 | private final Class recordClass; 99 | 100 | private MrzFormat(int rows, int columns, Class recordClass) { 101 | this.rows = rows; 102 | this.columns = columns; 103 | this.recordClass = recordClass; 104 | } 105 | 106 | /** 107 | * Checks if this format is able to parse given serialized MRZ record. 108 | * @param mrzRows MRZ record, separated into rows. 109 | * @return true if given MRZ record is of this type, false otherwise. 110 | */ 111 | public boolean isFormatOf(String[] mrzRows) { 112 | return rows == mrzRows.length && columns == mrzRows[0].length(); 113 | } 114 | 115 | /** 116 | * Detects given MRZ format. 117 | * @param mrz the MRZ string. 118 | * @return the format, never null. 119 | */ 120 | public static final MrzFormat get(String mrz) { 121 | final String[] rows = mrz.split("\n"); 122 | final int cols = rows[0].length(); 123 | for (int i = 1; i < rows.length; i++) { 124 | if (rows[i].length() != cols) { 125 | throw new MrzParseException("Different row lengths: 0: " + cols + " and " + i + ": " + rows[i].length(), mrz, new MrzRange(0, 0, 0), null); 126 | } 127 | } 128 | for (final MrzFormat f : values()) { 129 | if (f.isFormatOf(rows)) { 130 | return f; 131 | } 132 | } 133 | throw new MrzParseException("Unknown format / unsupported number of cols/rows: " + cols + "/" + rows.length, mrz, new MrzRange(0, 0, 0), null); 134 | } 135 | 136 | /** 137 | * Creates new record instance with this type. 138 | * @return never null record instance. 139 | */ 140 | public final MrzRecord newRecord() { 141 | try { 142 | return recordClass.newInstance(); 143 | } catch (Exception ex) { 144 | throw new RuntimeException(ex); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/innovatrics/mrz/MrzRecord.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz; 20 | 21 | import com.innovatrics.mrz.types.MrzDate; 22 | import com.innovatrics.mrz.types.MrzFormat; 23 | import com.innovatrics.mrz.types.MrzDocumentCode; 24 | import com.innovatrics.mrz.types.MrzSex; 25 | import java.io.Serializable; 26 | 27 | /** 28 | * An abstract MRZ record, contains basic information present in all MRZ record types. 29 | * @author Martin Vysny 30 | */ 31 | public abstract class MrzRecord implements Serializable { 32 | 33 | 34 | /** 35 | * The document code. 36 | */ 37 | public MrzDocumentCode code; 38 | /** 39 | * Document code, see {@link MrzDocumentCode} for details on allowed values. 40 | */ 41 | public char code1; 42 | /** 43 | * For MRTD: Type, at discretion of states, but 1-2 should be IP for passport card, AC for crew member and IV is not allowed. 44 | * For MRP: Type (for countries that distinguish between different types of passports) 45 | */ 46 | public char code2; 47 | 48 | 49 | 50 | /** 51 | * An ISO 3166-1 alpha-3 country code of issuing country, with additional allowed values (according to article on Wikipedia): 52 | *

  • D: Germany
  • 53 |
  • GBD: British dependent territories citizen(note: the country code of the overseas territory is presently used to indicate issuing authority and nationality of BOTC)
  • 54 |
  • GBN: British National (Overseas)
  • 55 |
  • GBO: British Overseas citizen
  • 56 |
  • GBP: British protected person
  • 57 |
  • GBS: British subject
  • 58 |
  • UNA: specialized agency of the United Nations
  • 59 |
  • UNK: resident of Kosovo to whom a travel document has been issued by the United Nations Interim Administration Mission in Kosovo (UNMIK)
  • 60 |
  • UNO: United Nations Organization
  • 61 |
  • XOM: Sovereign Military Order of Malta
  • 62 |
  • XXA: stateless person, as per the 1954 Convention Relating to the Status of Stateless Persons
  • 63 |
  • XXB: refugee, as per the 1951 Convention Relating to the Status of Refugees
  • 64 |
  • XXC: refugee, other than defined above
  • 65 |
  • XXX: unspecified nationality
66 | */ 67 | public String issuingCountry; 68 | /** 69 | * Document number, e.g. passport number. 70 | */ 71 | public String documentNumber; 72 | /** 73 | * The surname in uppercase. 74 | */ 75 | public String surname; 76 | /** 77 | * The given names in uppercase, separated by spaces. 78 | */ 79 | public String givenNames; 80 | /** 81 | * Date of birth. 82 | */ 83 | public MrzDate dateOfBirth; 84 | /** 85 | * Sex 86 | */ 87 | public MrzSex sex; 88 | /** 89 | * expiration date of passport 90 | */ 91 | public MrzDate expirationDate; 92 | /** 93 | * An ISO 3166-1 alpha-3 country code of nationality. 94 | * See {@link #issuingCountry} for additional allowed values. 95 | */ 96 | public String nationality; 97 | /** 98 | * Detected MRZ format. 99 | */ 100 | public final MrzFormat format; 101 | 102 | 103 | /** 104 | * check digits, usually common in every document. 105 | */ 106 | public boolean validDocumentNumber = true; 107 | public boolean validDateOfBirth = true; 108 | public boolean validExpirationDate = true; 109 | public boolean validComposite = true; 110 | 111 | 112 | protected MrzRecord(MrzFormat format) { 113 | this.format = format; 114 | } 115 | 116 | @Override 117 | public String toString() { 118 | return "MrzRecord{" + "code=" + code + "[" + code1 + code2 + "], issuingCountry=" + issuingCountry + ", documentNumber=" + documentNumber 119 | + ", surname=" + surname + ", givenNames=" + givenNames + ", dateOfBirth=" + dateOfBirth + ", sex=" + sex + ", expirationDate=" 120 | + expirationDate + ", nationality=" + nationality + '}'; 121 | } 122 | 123 | /** 124 | * Parses the MRZ record. 125 | * @param mrz the mrz record, not null, separated by \n 126 | * @throws MrzParseException when a problem occurs. 127 | */ 128 | public void fromMrz(String mrz) throws MrzParseException { 129 | if (format != MrzFormat.get(mrz)) { 130 | throw new MrzParseException("invalid format: " + MrzFormat.get(mrz), mrz, new MrzRange(0, 0, 0), format); 131 | } 132 | code = MrzDocumentCode.parse(mrz); 133 | code1 = mrz.charAt(0); 134 | code2 = mrz.charAt(1); 135 | issuingCountry = new MrzParser(mrz).parseString(new MrzRange(2, 5, 0)); 136 | } 137 | 138 | /** 139 | * Helper method to set the full name. Changes both {@link #surname} and {@link #givenNames}. 140 | * @param name expected array of length 2, in the form of [surname, first_name]. Must not be null. 141 | */ 142 | protected final void setName(String[] name) { 143 | surname = name[0]; 144 | givenNames = name[1]; 145 | } 146 | 147 | /** 148 | * Serializes this record to a valid MRZ record. 149 | * @return a valid MRZ record, not null, separated by \n 150 | */ 151 | public abstract String toMrz(); 152 | } 153 | -------------------------------------------------------------------------------- /src/test/java/com/innovatrics/mrz/MrzParserTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java parser for the MRZ records, as specified by the ICAO organization. 3 | * Copyright (C) 2011 Innovatrics s.r.o. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | package com.innovatrics.mrz; 20 | 21 | import com.innovatrics.mrz.types.MrzDate; 22 | import org.junit.Test; 23 | import static org.junit.Assert.*; 24 | 25 | /** 26 | * Tests the parser. 27 | * @author Martin Vysny 28 | */ 29 | public class MrzParserTest { 30 | 31 | /** 32 | * Test of computeCheckDigit method, of class MrzRecord. 33 | */ 34 | @Test 35 | public void testComputeCheckDigit() { 36 | assertEquals(3, MrzParser.computeCheckDigit("520727")); 37 | assertEquals(2, MrzParser.computeCheckDigit("D231458907<<<<<<<<<<<<<<<34071279507122<<<<<<<<<<")); 38 | assertEquals('3', MrzParser.computeCheckDigitChar("520727")); 39 | assertEquals('2', MrzParser.computeCheckDigitChar("D231458907<<<<<<<<<<<<<<<34071279507122<<<<<<<<<<")); 40 | } 41 | 42 | @Test 43 | public void testValidCheckDigit(){ 44 | 45 | String CzechPassport = "P 34 | * All parse methods throws {@link MrzParseException} unless stated otherwise. 35 | * @author Martin Vysny 36 | */ 37 | public class MrzParser { 38 | 39 | /** 40 | * The MRZ record, not null. 41 | */ 42 | public final String mrz; 43 | /** 44 | * The MRZ record separated into rows. 45 | */ 46 | public final String[] rows; 47 | /** 48 | * MRZ record format. 49 | */ 50 | public final MrzFormat format; 51 | 52 | /** 53 | * Creates new parser which parses given MRZ record. 54 | * @param mrz the MRZ record, not null. 55 | */ 56 | public MrzParser(String mrz) { 57 | this.mrz = mrz; 58 | this.rows = mrz.split("\n"); 59 | this.format = MrzFormat.get(mrz); 60 | } 61 | 62 | /** 63 | * @author jllarraz@github 64 | * Parses the MRZ name in form of SURNAME<1){ 83 | surname = parseString(new MrzRange(range.column, range.column + names[0].length(), range.row)); 84 | givenNames = parseString(new MrzRange(range.column + names[0].length() + 2, range.column + str.length(), range.row)); 85 | } 86 | return new String[]{surname, givenNames}; 87 | } 88 | 89 | /** 90 | * Returns a raw MRZ value from given range. If multiple ranges are specified, the value is concatenated. 91 | * @param range the ranges, not null. 92 | * @return raw value, never null, may be empty. 93 | */ 94 | public String rawValue(MrzRange... range) { 95 | final StringBuilder sb = new StringBuilder(); 96 | for (MrzRange r : range) { 97 | sb.append(rows[r.row].substring(r.column, r.columnTo)); 98 | } 99 | return sb.toString(); 100 | } 101 | 102 | /** 103 | * Checks that given range contains valid characters. 104 | * @param range the range to check. 105 | */ 106 | public void checkValidCharacters(MrzRange range) { 107 | final String str = rawValue(range); 108 | for (int i = 0; i < str.length(); i++) { 109 | final char c = str.charAt(i); 110 | if (c != FILLER && (c < '0' || c > '9') && (c < 'A' || c > 'Z')) { 111 | throw new MrzParseException("Invalid character in MRZ record: " + c, mrz, new MrzRange(range.column + i, range.column + i + 1, range.row), format); 112 | } 113 | } 114 | } 115 | 116 | /** 117 | * Parses a string in given range. << are replaced with ", ", < is replaced by space. 118 | * @param range the range 119 | * @return parsed string. 120 | */ 121 | public String parseString(MrzRange range) { 122 | checkValidCharacters(range); 123 | String str = rawValue(range); 124 | while (str.endsWith("<")) { 125 | str = str.substring(0, str.length() - 1); 126 | } 127 | return str.replace("" + FILLER + FILLER, ", ").replace(FILLER, ' '); 128 | } 129 | 130 | /** 131 | * Verifies the check digit. 132 | * @param col the 0-based column of the check digit. 133 | * @param row the 0-based column of the check digit. 134 | * @param strRange the range for which the check digit is computed. 135 | * @param fieldName (optional) field name. Used only when validity check fails. 136 | * @return true if check digit is valid, false if not 137 | */ 138 | public boolean checkDigit(int col, int row, MrzRange strRange, String fieldName) { 139 | return checkDigit(col, row, rawValue(strRange), fieldName); 140 | } 141 | 142 | /** 143 | * Verifies the check digit. 144 | * @param col the 0-based column of the check digit. 145 | * @param row the 0-based column of the check digit. 146 | * @param str the raw MRZ substring. 147 | * @param fieldName (optional) field name. Used only when validity check fails. 148 | * @return true if check digit is valid, false if not 149 | */ 150 | public boolean checkDigit(int col, int row, String str, String fieldName) { 151 | 152 | /** 153 | * If the check digit validation fails, this will contain the location. 154 | */ 155 | MrzRange invalidCheckdigit = null; 156 | 157 | final char digit = (char) (computeCheckDigit(str) + '0'); 158 | char checkDigit = rows[row].charAt(col); 159 | if (checkDigit == FILLER) { 160 | checkDigit = '0'; 161 | } 162 | if (digit != checkDigit) { 163 | invalidCheckdigit = new MrzRange(col, col + 1, row); 164 | System.out.println("Check digit verification failed for " + fieldName + ": expected " + digit + " but got " + checkDigit); 165 | } 166 | return invalidCheckdigit==null; 167 | } 168 | 169 | private static Logger log = LoggerFactory.getLogger(MrzParser.class); 170 | 171 | /** 172 | * Parses MRZ date. 173 | * @param range the range containing the date, in the YYMMDD format. The range must be 6 characters long. 174 | * @return parsed date 175 | * @throws IllegalArgumentException if the range is not 6 characters long. 176 | */ 177 | public MrzDate parseDate(MrzRange range) { 178 | if (range.length() != 6) { 179 | throw new IllegalArgumentException("Parameter range: invalid value " + range + ": must be 6 characters long"); 180 | } 181 | MrzRange r; 182 | r = new MrzRange(range.column, range.column + 2, range.row); 183 | int year; 184 | try { 185 | year = Integer.parseInt(rawValue(r)); 186 | } catch (NumberFormatException ex) { 187 | year = -1; 188 | log.debug("Failed to parse MRZ date year " + rawValue(range) + ": " + ex, mrz, r); 189 | } 190 | if (year < 0 || year > 99) { 191 | log.debug("Invalid year value " + year + ": must be 0..99"); 192 | } 193 | r = new MrzRange(range.column + 2, range.column + 4, range.row); 194 | int month; 195 | try { 196 | month = Integer.parseInt(rawValue(r)); 197 | } catch (NumberFormatException ex) { 198 | month = -1; 199 | log.debug("Failed to parse MRZ date month " + rawValue(range) + ": " + ex, mrz, r); 200 | } 201 | if (month < 1 || month > 12) { 202 | log.debug("Invalid month value " + month + ": must be 1..12"); 203 | } 204 | r = new MrzRange(range.column + 4, range.column + 6, range.row); 205 | int day; 206 | try { 207 | day = Integer.parseInt(rawValue(r)); 208 | } catch (NumberFormatException ex) { 209 | day = -1; 210 | log.debug("Failed to parse MRZ date month " + rawValue(range) + ": " + ex, mrz, r); 211 | } 212 | if (day < 1 || day > 31) { 213 | log.debug("Invalid day value " + day + ": must be 1..31"); 214 | } 215 | return new MrzDate(year, month, day, rawValue(range)); 216 | 217 | } 218 | 219 | /** 220 | * Parses the "sex" value from given column/row. 221 | * @param col the 0-based column 222 | * @param row the 0-based row 223 | * @return sex, never null. 224 | */ 225 | public MrzSex parseSex(int col, int row) { 226 | return MrzSex.fromMrz(rows[row].charAt(col)); 227 | } 228 | private static final int[] MRZ_WEIGHTS = new int[]{7, 3, 1}; 229 | 230 | /** 231 | * Checks if given character is valid in MRZ. 232 | * @param c the character. 233 | * @return true if the character is valid, false otherwise. 234 | */ 235 | private static boolean isValid(char c) { 236 | return ((c == FILLER) || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')); 237 | } 238 | 239 | private static int getCharacterValue(char c) { 240 | if (c == FILLER) { 241 | return 0; 242 | } 243 | if (c >= '0' && c <= '9') { 244 | return c - '0'; 245 | } 246 | if (c >= 'A' && c <= 'Z') { 247 | return c - 'A' + 10; 248 | } 249 | throw new RuntimeException("Invalid character in MRZ record: " + c); 250 | } 251 | 252 | /** 253 | * Computes MRZ check digit for given string of characters. 254 | * @param str the string 255 | * @return check digit in range of 0..9, inclusive. See MRTD documentation part 15 for details. 256 | */ 257 | public static int computeCheckDigit(String str) { 258 | int result = 0; 259 | for (int i = 0; i < str.length(); i++) { 260 | result += getCharacterValue(str.charAt(i)) * MRZ_WEIGHTS[i % MRZ_WEIGHTS.length]; 261 | } 262 | return result % 10; 263 | } 264 | 265 | /** 266 | * Computes MRZ check digit for given string of characters. 267 | * @param str the string 268 | * @return check digit in range of 0..9, inclusive. See MRTD documentation part 15 for details. 269 | */ 270 | public static char computeCheckDigitChar(String str) { 271 | return (char) ('0' + computeCheckDigit(str)); 272 | } 273 | 274 | /** 275 | * Factory method, which parses the MRZ and returns appropriate record class. 276 | * @param mrz MRZ to parse. 277 | * @return record class. 278 | */ 279 | public static MrzRecord parse(String mrz) { 280 | final MrzRecord result = MrzFormat.get(mrz).newRecord(); 281 | result.fromMrz(mrz); 282 | return result; 283 | } 284 | 285 | 286 | private static final Map EXPAND_CHARACTERS = new HashMap(); 287 | 288 | static { 289 | EXPAND_CHARACTERS.put("\u00C4", "AE"); // Ä 290 | EXPAND_CHARACTERS.put("\u00E4", "AE"); // ä 291 | EXPAND_CHARACTERS.put("\u00C5", "AA"); // Å 292 | EXPAND_CHARACTERS.put("\u00E5", "AA"); // å 293 | EXPAND_CHARACTERS.put("\u00C6", "AE"); // Æ 294 | EXPAND_CHARACTERS.put("\u00E6", "AE"); // æ 295 | EXPAND_CHARACTERS.put("\u0132", "IJ"); // IJ 296 | EXPAND_CHARACTERS.put("\u0133", "IJ"); // ij 297 | EXPAND_CHARACTERS.put("\u00D6", "OE"); // Ö 298 | EXPAND_CHARACTERS.put("\u00F6", "OE"); // ö 299 | EXPAND_CHARACTERS.put("\u00D8", "OE"); // Ø 300 | EXPAND_CHARACTERS.put("\u00F8", "OE"); // ø 301 | EXPAND_CHARACTERS.put("\u00DC", "UE"); // Ü 302 | EXPAND_CHARACTERS.put("\u00FC", "UE"); // ü 303 | EXPAND_CHARACTERS.put("\u00DF", "SS"); // ß 304 | EXPAND_CHARACTERS.put("\u00D0", "D"); // Ð 305 | EXPAND_CHARACTERS.put("\u00F0", "d"); // ð 306 | EXPAND_CHARACTERS.put("\u00DE", "TH"); // Þ 307 | EXPAND_CHARACTERS.put("\u00FE", "th"); // þ 308 | } 309 | 310 | /** 311 | * Converts given string to a MRZ string: removes all accents, converts the string to upper-case and replaces all spaces and invalid characters with '<'. 312 | *

313 | * Several characters are expanded: 314 | * 315 | * 316 | * 317 | * 318 | * 319 | * 320 | * 321 | * 322 | * 323 | * 324 | * 325 | *
CharacterExpand to
ÄAE
ÅAA
ÆAE
IJIJ
IJIJ
ÖOE
ØOE
ÜUE
ßSS
326 | *

327 | * Examples:

    328 | *
  • toMrz("Sedím na konári", 20) yields "SEDIM<NA<KONARI<<<<<"
  • 329 | *
  • toMrz("Pat, Mat", 8) yields "PAT<<MAT"
  • 330 | *
  • toMrz("foo/bar baz", 4) yields "FOO<"
  • 331 | *
  • toMrz("*$()&/\", 8) yields "<<<<<<<<"
  • 332 | *
333 | * @param string the string to convert. Passing null is the same as passing in an empty string. 334 | * @param length required length of the string. If given string is longer, it is truncated. If given string is shorter than given length, '<' characters are appended at the end. If -1, the string is neither truncated nor enlarged. 335 | * @return MRZ-valid string. 336 | */ 337 | public static String toMrz(String string, int length) { 338 | if (string == null) { 339 | string = ""; 340 | } 341 | for (final Map.Entry e : EXPAND_CHARACTERS.entrySet()) { 342 | string = string.replace(e.getKey(), e.getValue()); 343 | } 344 | string = string.replace("’", ""); 345 | string = string.replace("'", ""); 346 | string = deaccent(string).toUpperCase(); 347 | if (length >= 0 && string.length() > length) { 348 | string = string.substring(0, length); 349 | } 350 | final StringBuilder sb = new StringBuilder(string); 351 | for (int i = 0; i < sb.length(); i++) { 352 | if (!isValid(sb.charAt(i))) { 353 | sb.setCharAt(i, FILLER); 354 | } 355 | } 356 | while (sb.length() < length) { 357 | sb.append(FILLER); 358 | } 359 | return sb.toString(); 360 | } 361 | 362 | private static boolean isBlank(String str) { 363 | return str == null || str.trim().length() == 0; 364 | } 365 | 366 | /** 367 | * Converts a surname and given names to a MRZ string, shortening them as per Doc 9303 Part 3 Vol 1 Section 6.7 of the MRZ specification when necessary. 368 | * @param surname the surname, not blank. 369 | * @param givenNames given names, not blank. 370 | * @param length required length of the string. If given string is longer, it is shortened. If given string is shorter than given length, '<' characters are appended at the end. 371 | * @return name, properly converted to MRZ format of SURNAME<<GIVENNAMES<..., with the exact length of given length. 372 | */ 373 | public static String nameToMrz(String surname, String givenNames, int length) { 374 | if (isBlank(surname)) { 375 | throw new IllegalArgumentException("Parameter surname: invalid value " + surname + ": blank"); 376 | } 377 | if (isBlank(givenNames)) { 378 | throw new IllegalArgumentException("Parameter givenNames: invalid value " + givenNames + ": blank"); 379 | } 380 | if (length <= 0) { 381 | throw new IllegalArgumentException("Parameter length: invalid value " + length + ": not positive"); 382 | } 383 | surname = surname.replace(", ", " "); 384 | givenNames = givenNames.replace(", ", " "); 385 | final String[] surnames = surname.trim().split("[ \n\t\f\r]+"); 386 | final String[] given = givenNames.trim().split("[ \n\t\f\r]+"); 387 | for (int i = 0; i < surnames.length; i++) { 388 | surnames[i] = toMrz(surnames[i], -1); 389 | } 390 | for (int i = 0; i < given.length; i++) { 391 | given[i] = toMrz(given[i], -1); 392 | } 393 | // truncate 394 | int nameSize = getNameSize(surnames, given); 395 | String[] currentlyTruncating = given; 396 | int currentlyTruncatingIndex = given.length - 1; 397 | while (nameSize > length) { 398 | final String ct = currentlyTruncating[currentlyTruncatingIndex]; 399 | final int ctsize = ct.length(); 400 | if (nameSize - ctsize + 1 <= length) { 401 | currentlyTruncating[currentlyTruncatingIndex] = ct.substring(0, ctsize - (nameSize - length)); 402 | } else { 403 | currentlyTruncating[currentlyTruncatingIndex] = ct.substring(0, 1); 404 | currentlyTruncatingIndex--; 405 | if (currentlyTruncatingIndex < 0) { 406 | if (currentlyTruncating == surnames) { 407 | throw new IllegalArgumentException("Cannot truncate name " + surname + " " + givenNames + ": length too small: " + length + "; truncated to " + toName(surnames, given)); 408 | } 409 | currentlyTruncating = surnames; 410 | currentlyTruncatingIndex = currentlyTruncating.length - 1; 411 | } 412 | } 413 | nameSize = getNameSize(surnames, given); 414 | } 415 | return toMrz(toName(surnames, given), length); 416 | } 417 | /** 418 | * The filler character, '<'. 419 | */ 420 | public static final char FILLER = '<'; 421 | 422 | private static String toName(String[] surnames, String[] given) { 423 | final StringBuilder sb = new StringBuilder(); 424 | boolean first = true; 425 | for (String s : surnames) { 426 | if (first) { 427 | first = false; 428 | } else { 429 | sb.append(FILLER); 430 | } 431 | sb.append(s); 432 | } 433 | sb.append(FILLER); 434 | for (String s : given) { 435 | sb.append(FILLER); 436 | sb.append(s); 437 | } 438 | return sb.toString(); 439 | } 440 | 441 | private static int getNameSize(final String[] surnames, final String[] given) { 442 | int result = 0; 443 | for (String s : surnames) { 444 | result += s.length() + 1; 445 | } 446 | for (String s : given) { 447 | result += s.length() + 1; 448 | } 449 | return result; 450 | } 451 | 452 | private static String deaccent(String str) { 453 | String n = Normalizer.normalize(str, Normalizer.Form.NFD); 454 | return n.replaceAll("[^\\p{ASCII}]", "").toLowerCase(); 455 | } 456 | } 457 | --------------------------------------------------------------------------------