Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 BEL 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