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
36 changes: 36 additions & 0 deletions src/parse/__tests__/td1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,4 +470,40 @@ describe('parse TD1', () => {
],
]);
});

it('Belgium ID BEL-BO-03003 with embedded document check digit', () => {
// source: https://www.consilium.europa.eu/prado/en/BEL-BO-03003/index.html
// This Belgian ID has the document number check digit embedded in optional1
const MRZ = [
'IDBEL000590240<6013<<<<<<<<<<<',
'8512017F1311048BEL851201002007',
'REINARTZ<<ULRIKE<KATIA<E<<<<<<',
];

const result = parse(MRZ);

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

expect(result.fields).toStrictEqual({
documentCode: 'ID',
issuingState: 'BEL',
documentNumber: '000590240601',
documentNumberCheckDigit: '3',
birthDate: '851201',
birthDateCheckDigit: '7',
sex: 'female',
expirationDate: '131104',
expirationDateCheckDigit: '8',
nationality: 'BEL',
optional1: '6013',
optional2: '85120100200',
compositeCheckDigit: '7',
lastName: 'REINARTZ',
firstName: 'ULRIKE KATIA E',
});
});
});
7 changes: 6 additions & 1 deletion src/parsers/__tests__/check.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect, test } from 'vitest';

import { check } from '../check.ts';
import { check, computeCheckDigit } from '../check.ts';

test('check digits', () => {
expect(() => check('592166117<231', 8)).not.toThrow();
Expand All @@ -11,3 +11,8 @@ test('check digits', () => {
expect(() => check('600001795015', 2)).not.toThrow();
expect(() => check('592166111<773', 4)).toThrow(/invalid check digit/);
});

test('compute embedded TD1 check digit', () => {
expect(computeCheckDigit('123456789AAB')).toBe(8);
expect(computeCheckDigit('123456789<AAB')).toBe(4);
});
8 changes: 6 additions & 2 deletions src/parsers/check.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function check(string: string, value: string | number) {
export function computeCheckDigit(string: string) {
let code = 0;
const factors = [7, 3, 1];
for (let i = 0; i < string.length; i++) {
Expand All @@ -10,7 +10,11 @@ export function check(string: string, value: string | number) {
charCode *= factors[i % 3];
code += charCode;
}
code %= 10;
return code % 10;
}

export function check(string: string, value: string | number) {
const code = computeCheckDigit(string);
if (code !== Number(value)) {
throw new Error(`invalid check digit: ${value}. Must be ${code}`);
}
Expand Down
21 changes: 18 additions & 3 deletions src/parsers/parseDocumentNumberCheckDigit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { check } from './check.ts';
import { check, computeCheckDigit } from './check.ts';

export default function parseDocumentNumberCheckDigit(
checkDigit: string,
Expand All @@ -8,8 +8,23 @@ export default function parseDocumentNumberCheckDigit(
if (checkDigit === '<' && optional) {
const firstFiller = optional.indexOf('<');
const tail = optional.slice(0, firstFiller - 1);
source = `${source}${tail}`;
checkDigit = optional.charAt(firstFiller - 1);
// Handle older non-compliant documents (e.g., PRT and BEL IDs) where the check digit
// is embedded in optional1 instead of following the document number directly.
// According to ICAO Doc 9303 Part 11 (https://www.icao.int/sites/default/files/publications/DocSeries/9303_p11_cons_en.pdf)
// page 88, the check digit should be calculated on the document number including
// any additional characters from optional1 up to (but not including) the embedded check digit.
const embeddedDigit = optional.charAt(firstFiller - 1);
const embeddedValid =
computeCheckDigit(`${source}${tail}`) === Number(embeddedDigit);
if (embeddedValid) {
return {
value: embeddedDigit,
start: firstFiller,
end: firstFiller + 1,
};
}
source = `${source}<${tail}`;
checkDigit = embeddedDigit;
check(source, checkDigit);
return {
value: checkDigit,
Expand Down