Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const parse = require('mrz').parse;

const mrz = [
'I<UTOD23145890<1233<<<<<<<<<<<',
'7408122F1204159UTO<<<<<<<<<<<6',
'7408122F1204159UTO<<<<<<<<<<<2',
'ERIKSSON<<ANNA<MARIA<<<<<<<<<<',
];

Expand Down
253 changes: 246 additions & 7 deletions src/parse/__tests__/td1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,192 @@ describe('parse TD1', () => {
});
});

it('Portuguese ID - valid', () => {
// source: https://media.timeout.com/images/106144795/image.jpg
const MRZ = [
'I<PRT007777779<ZZ92<<<<<<<<<<<',
'8303143M3405282PRT<<<<<<<<<<<2',
'CACADOR<DE<ARAUJO<<ANDRE<ESTEV',
];

const result = parse(MRZ);

expect(result).toMatchObject({
format: 'TD1',
valid: true,
documentNumber: result.fields.documentNumber,
});

expect(result.fields).toStrictEqual({
documentCode: 'I',
issuingState: 'PRT',
documentNumber: '007777779ZZ9',
documentNumberCheckDigit: '2',
birthDate: '830314',
birthDateCheckDigit: '3',
sex: 'male',
expirationDate: '340528',
expirationDateCheckDigit: '2',
nationality: 'PRT',
optional1: 'ZZ92',
optional2: '',
compositeCheckDigit: '2',
lastName: 'CACADOR DE ARAUJO',
firstName: 'ANDRE ESTEV',
});

const optional1Details = result.details.find(
(f) => f.field === 'optional1',
);

expect(optional1Details).toMatchObject({
value: 'ZZ92',
line: 0,
start: 15,
end: 19,
});
});

it('Portuguese ID PRT-BO-04001 - valid', () => {
// source: https://www.consilium.europa.eu/prado/en/PRT-BO-04001/index.html
const MRZ = [
'I<PRT007666667<ZZ00<<<<<<<<<<<',
'8303143M3405293PRT<<<<<<<<<<<4',
'CACADOR<DE<ARAUJO<<ANDRE<ESTEV',
];

const result = parse(MRZ);

expect(result).toMatchObject({
format: 'TD1',
valid: true,
documentNumber: result.fields.documentNumber,
});

expect(result.fields).toStrictEqual({
documentCode: 'I',
issuingState: 'PRT',
documentNumber: '007666667ZZ0',
documentNumberCheckDigit: '0',
birthDate: '830314',
birthDateCheckDigit: '3',
sex: 'male',
expirationDate: '340529',
expirationDateCheckDigit: '3',
nationality: 'PRT',
optional1: 'ZZ00',
optional2: '',
compositeCheckDigit: '4',
lastName: 'CACADOR DE ARAUJO',
firstName: 'ANDRE ESTEV',
});

const optional1Details = result.details.find(
(f) => f.field === 'optional1',
);

expect(optional1Details).toMatchObject({
value: 'ZZ00',
line: 0,
start: 15,
end: 19,
});
});

it('Belgium ID BEL-BO-11005 - valid', () => {
// source: https://www.consilium.europa.eu/prado/en/BEL-BO-11005/index.html
// Changed the country code from UTO to PRT to make it pass the country validation,
// but that this part does not affect any check digit checks.
const MRZ = [
'IDBEL600001795<0152<<<<<<<<<<<',
'1301014F2311207BEL130101987398',
'SPECIMEN<<SPECIMEN<<<<<<<<<<<<',
];

const result = parse(MRZ);

expect(result).toMatchObject({
format: 'TD1',
valid: true,
documentNumber: result.fields.documentNumber,
});

expect(result.fields).toStrictEqual({
documentCode: 'ID',
issuingState: 'BEL',
documentNumber: '600001795015',
documentNumberCheckDigit: '2',
birthDate: '130101',
birthDateCheckDigit: '4',
sex: 'female',
expirationDate: '231120',
expirationDateCheckDigit: '7',
nationality: 'BEL',
optional1: '0152',
optional2: '13010198739',
compositeCheckDigit: '8',
lastName: 'SPECIMEN',
firstName: 'SPECIMEN',
});

const optional1Details = result.details.find(
(f) => f.field === 'optional1',
);

expect(optional1Details).toMatchObject({
value: '0152',
line: 0,
start: 15,
end: 19,
});
});

it('Finland ID FIN-BO-12001 - valid', () => {
// source: https://www.consilium.europa.eu/prado/en/FIN-BO-12001/index.html
const MRZ = [
'I<FINXA10000585010195<112X<<<<',
'9501016F2803135FIN<<<<<<<<<<<7',
'SPECIMEN<TRAVEL<<VILMA<SOFIA<<',
];

const result = parse(MRZ);

expect(result).toMatchObject({
format: 'TD1',
valid: true,
documentNumber: result.fields.documentNumber,
});

expect(result.fields).toStrictEqual({
documentCode: 'I',
issuingState: 'FIN',
documentNumber: 'XA1000058',
documentNumberCheckDigit: '5',
birthDate: '950101',
birthDateCheckDigit: '6',
sex: 'female',
expirationDate: '280313',
expirationDateCheckDigit: '5',
nationality: 'FIN',
optional1: '010195 112X',
optional2: '',
compositeCheckDigit: '7',
lastName: 'SPECIMEN TRAVEL',
firstName: 'VILMA SOFIA',
});

const optional1Details = result.details.find(
(f) => f.field === 'optional1',
);

expect(optional1Details).toMatchObject({
value: '010195 112X',
line: 0,
start: 15,
end: 26,
});
});

it('Utopia example', () => {
const MRZ = [
'I<UTOD231458907ABC<<<<<<<<<<<<',
Expand Down Expand Up @@ -107,8 +293,8 @@ describe('parse TD1', () => {

it('parse document number', () => {
const MRZ = [
'I<UTOD23145890<1240<XYZ<<<<<<<',
'7408122F1204159UTO<<<<<<<<<<<8',
'I<UTOD23145890<1233<<<<<<<<<<<',
'7408122F1204159UTO<<<<<<<<<<<2',
'ERIKSSON<<ANNA<MARIA<<<<<<<<<<',
];

Expand All @@ -129,20 +315,73 @@ describe('parse TD1', () => {
expect(documentNumberDetails).toStrictEqual({
label: 'Document number',
field: 'documentNumber',
value: 'D23145890124',
value: 'D23145890123',
valid: true,
ranges: [
{ line: 0, start: 5, end: 14, raw: 'D23145890' },
{ line: 0, start: 14, end: 15, raw: '<' },
{ line: 0, start: 15, end: 30, raw: '1233<<<<<<<<<<<' },
],
line: 0,
start: 5,
end: 18,
autocorrect: [],
});
expect(result.fields.documentNumber).toBe('D23145890123');
expect(result.fields.documentNumberCheckDigit).toBe('3');

const documentNumberCheckDigitDetails = result.details.find(
(d) => d.field === 'documentNumberCheckDigit',
);

expect(documentNumberCheckDigitDetails).toMatchObject({
line: 0,
start: 18,
end: 19,
value: '3',
});
});

it('parse document number ICAO Doc 9303 Sample', () => {
// source: https://www.icao.int/sites/default/files/publications/DocSeries/9303_p11_cons_en.pdf
// page 88, D.2 DERIVATION OF DOCUMENT BASIC ACCESS KEYS
const MRZ = [
'I<UTOD23145890<7349<<<<<<<<<<<',
'3407127M9507122UTO<<<<<<<<<<<2',
'STEVENSON<<PETER<JOHN<<<<<<<<<',
];

const result = parse(MRZ);

expect(result).toMatchObject({
format: 'TD1',
valid: false,
documentNumber: result.fields.documentNumber,
});

expect(result.details.filter((f) => !f.valid)).toHaveLength(2);

const documentNumberDetails = result.details.find(
(d) => d.field === 'documentNumber',
);

expect(documentNumberDetails).toStrictEqual({
label: 'Document number',
field: 'documentNumber',
value: 'D23145890734',
valid: true,
ranges: [
{ line: 0, start: 5, end: 14, raw: 'D23145890' },
{ line: 0, start: 14, end: 15, raw: '<' },
{ line: 0, start: 15, end: 30, raw: '1240<XYZ<<<<<<<' },
{ line: 0, start: 15, end: 30, raw: '7349<<<<<<<<<<<' },
],
line: 0,
start: 5,
end: 18,
autocorrect: [],
});
expect(result.fields.documentNumber).toBe('D23145890124');
expect(result.fields.documentNumberCheckDigit).toBe('0');
expect(result.fields.documentNumber).toBe('D23145890734');
expect(result.fields.documentNumberCheckDigit).toBe('9');

const documentNumberCheckDigitDetails = result.details.find(
(d) => d.field === 'documentNumberCheckDigit',
Expand All @@ -152,7 +391,7 @@ describe('parse TD1', () => {
line: 0,
start: 18,
end: 19,
value: '0',
value: '9',
});
});

Expand Down
4 changes: 4 additions & 0 deletions src/parsers/__tests__/check.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@ import { check } from '../check.ts';
test('check digits', () => {
expect(() => check('592166117<231', 8)).not.toThrow();
expect(() => check('592166111<773', 5)).not.toThrow();
expect(() => check('007666667<ZZ0', 0)).not.toThrow();
expect(() => check('007666667ZZ0', 0)).not.toThrow();
expect(() => check('007777779ZZ9', 2)).not.toThrow();
expect(() => check('600001795015', 2)).not.toThrow();
expect(() => check('592166111<773', 4)).toThrow(/invalid check digit/);
});
2 changes: 1 addition & 1 deletion src/parsers/parseDocumentNumberCheckDigit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function parseDocumentNumberCheckDigit(
if (checkDigit === '<' && optional) {
const firstFiller = optional.indexOf('<');
const tail = optional.slice(0, firstFiller - 1);
source = `${source}<${tail}`;
source = `${source}${tail}`;
checkDigit = optional.charAt(firstFiller - 1);
check(source, checkDigit);
return {
Expand Down