import moment from 'moment'; type MRZ = { type: 'TD1' | 'TD2' | 'TD3'; zone: string[]; }; type Field = { field: string; format?: (value: string, obj?: Record) => string; }; type FieldList = Record; const DEFAULT_FIELD = { documentType: { field: 'doc_type' }, documentNo: { field: 'doc_number' }, documentNoCheck: { field: 'doc_number_check' }, name: { field: 'full_name', format: (value, _) => value.replace(/0/, 'O'), }, country: { field: 'country', format: (value, _) => value.replace(/0/, 'O'), }, nationality: { field: 'nationality', format: (value, _) => value.replace(/0/, 'O'), }, gender: { field: 'sex' }, birthDate: { field: 'birth_date', format: (value, _) => moment(value, 'YYMMDD').format('YYYY-MM-DD'), }, birthDateCheck: { field: 'birth_date_check' }, expireDate: { field: 'expire_date', format: (value, _) => moment(value, 'YYMMDD').format('YYYY-MM-DD'), }, expireDateCheck: { field: 'expire_date_check' }, optionalData: { field: 'optional_data' }, optionalDataCheck: { field: 'optional_data_check' }, lineCheck: { field: 'line_check' }, } satisfies FieldList; const MRZ_TD_1 = [ new RegExp( [ `(?<${DEFAULT_FIELD.documentType.field}>[0-9A-Z<]{2})`, `(?<${DEFAULT_FIELD.country.field}>[0-9A-Z<]{3})`, `(?<${DEFAULT_FIELD.documentNo.field}>[0-9A-Z<]{9})`, `(?<${DEFAULT_FIELD.documentNoCheck.field}>[0-9A-Z<]{1})`, `(?<${DEFAULT_FIELD.optionalData.field}>[0-9A-Z<]{15})`, ].join(''), ), new RegExp( [ `(?<${DEFAULT_FIELD.birthDate.field}>[0-9A-Z<]{6})`, `(?<${DEFAULT_FIELD.birthDateCheck.field}>[0-9A-Z<]{1})`, `(?<${DEFAULT_FIELD.gender.field}>[mfMF<]{1})`, `(?<${DEFAULT_FIELD.expireDate.field}>[0-9A-Z<]{6})`, `(?<${DEFAULT_FIELD.expireDateCheck.field}>[0-9A-Z<]{1})`, `(?<${DEFAULT_FIELD.nationality.field}>[0-9A-Z<]{3})`, `(?<${DEFAULT_FIELD.optionalData.field}>[A-Z0-9<]{11})`, `(?<${DEFAULT_FIELD.lineCheck.field}>[0-9A-Z<]{1})`, ].join(''), ), new RegExp([`(?<${DEFAULT_FIELD.name.field}>[A-Z<]{30})`].join('')), ]; const MRZ_TD_2 = [ new RegExp( [ `(?<${DEFAULT_FIELD.documentType.field}>[0-9A-Z<]{2})`, `(?<${DEFAULT_FIELD.country.field}>[0-9A-Z<]{3})`, `(?<${DEFAULT_FIELD.name.field}>[A-Z<]{31})`, ].join(''), ), new RegExp( [ `(?<${DEFAULT_FIELD.documentNo.field}>[0-9A-Z<]{9})`, `(?<${DEFAULT_FIELD.documentNoCheck.field}>[0-9A-Z<]{1})`, `(?<${DEFAULT_FIELD.nationality.field}>[0-9A-Z<]{3})`, `(?<${DEFAULT_FIELD.birthDate.field}>[0-9A-Z<]{6})`, `(?<${DEFAULT_FIELD.birthDateCheck.field}>[0-9A-Z<]{1})`, `(?<${DEFAULT_FIELD.gender.field}>[mfMF]{1})`, `(?<${DEFAULT_FIELD.expireDate.field}>[0-9A-Z<]{6})`, `(?<${DEFAULT_FIELD.expireDateCheck.field}>[0-9A-Z<]{1})`, `(?<${DEFAULT_FIELD.optionalData.field}>[A-Z0-9<]{7})`, `(?<${DEFAULT_FIELD.lineCheck.field}>[0-9A-Z<]{1})`, ].join(''), ), ]; const MRZ_TD_3 = [ new RegExp( [ `(?<${DEFAULT_FIELD.documentType.field}>[A-Z0-9<]{2})`, `(?<${DEFAULT_FIELD.country.field}>[0-9A-Z<]{3})`, `(?<${DEFAULT_FIELD.name.field}>[A-Z0-9<]{39})`, ].join(''), ), new RegExp( [ `(?<${DEFAULT_FIELD.documentNo.field}>[0-9A-Z<]{9})`, `(?<${DEFAULT_FIELD.documentNoCheck.field}>[0-9A-Z<]{1})`, `(?<${DEFAULT_FIELD.nationality.field}>[0-9A-Z<]{3})`, `(?<${DEFAULT_FIELD.birthDate.field}>[0-9A-Z<]{6})`, `(?<${DEFAULT_FIELD.birthDateCheck.field}>[0-9A-Z<]{1})`, `(?<${DEFAULT_FIELD.gender.field}>[mfMF<]{1})`, `(?<${DEFAULT_FIELD.expireDate.field}>[0-9A-Z<]{6})`, `(?<${DEFAULT_FIELD.expireDateCheck.field}>[0-9A-Z<]{1})`, `(?<${DEFAULT_FIELD.optionalData.field}>[A-Z0-9<]{14})`, `(?<${DEFAULT_FIELD.optionalDataCheck.field}>[0-9A-Z<]{1})`, `(?<${DEFAULT_FIELD.lineCheck.field}>[0-9A-Z<]{1})`, ].join(''), ), ]; function mrzCleanResult(obj: Record) { Object.entries(obj).forEach(([k, v]) => { obj[k] = v .replace(/ { if (v === '<') return a; const num = Number(v); const weight = [7, 3, 1][i % 3]; if (Number.isNaN(num)) { return a + (v.charCodeAt(0) - 55) * weight; } return a + num * weight; }, 0); return sum % 10; } export function parseType1(mrz: MRZ) { const result: Record = {}; mrz.zone.forEach((line, i) => Object.assign(result, mrzFieldExtract(MRZ_TD_1[i], line)), ); return { mrz, result: mrzCleanResult(result) }; } export function parseType2(mrz: MRZ) { const result: Record = {}; mrz.zone.forEach((line, i) => Object.assign(result, mrzFieldExtract(MRZ_TD_2[i], line)), ); return { mrz, result: mrzCleanResult(result) }; } export function parseType3(mrz: MRZ) { const result: Record = {}; mrz.zone.forEach((line, i) => Object.assign(result, mrzFieldExtract(MRZ_TD_3[i], line)), ); return { mrz, result: mrzCleanResult(result) }; } export function parseMRZ(mrz: MRZ) { if (mrz.type === 'TD1') return parseType1(mrz); if (mrz.type === 'TD2') return parseType2(mrz); if (mrz.type === 'TD3') return parseType3(mrz); return null; }