From a85f867a2a19594cd06173f795597960244a542d Mon Sep 17 00:00:00 2001 From: lukesolo Date: Mon, 1 Jun 2020 14:12:23 +0300 Subject: [PATCH 1/7] Fix french ID parser --- src/parse/__tests__/frenchNationalId.js | 28 ++++++++++++++++++++++++- src/parse/frenchNationalIdFields.js | 2 +- src/parse/parse.js | 3 +-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/parse/__tests__/frenchNationalId.js b/src/parse/__tests__/frenchNationalId.js index a017f02..207e844 100644 --- a/src/parse/__tests__/frenchNationalId.js +++ b/src/parse/__tests__/frenchNationalId.js @@ -19,7 +19,7 @@ describe('parse French National Id', () => { administrativeCode: '0CHE02', issueDate: '1710', administrativeCode2: 'GVA', - documentNumber: '12345', + documentNumber: '1710GVA12345', documentNumberCheckDigit: '1', firstName: 'ROBERTA', birthDate: '911231', @@ -28,4 +28,30 @@ describe('parse French National Id', () => { compositeCheckDigit: '2' }); }); + + it('valid MRZ of version without administrativeCode', function () { + const MRZ = [ + 'IDFRABERTHIER<<<<<<<<<<<<<<<<<<<<<<<', + '9409923102854CORINNE<<<<<<<6512068F4' + ]; + var result = parse(MRZ); + expect(result.format).toBe('FRENCH_NATIONAL_ID'); + // expect(result.valid).toEqual(true); + expect(result.details.filter((a) => !a.valid)).toHaveLength(0); + expect(result.fields).toEqual({ + documentCode: 'ID', + issuingState: 'FRA', + lastName: 'BERTHIER', + administrativeCode: '', + issueDate: '9409', + administrativeCode2: '923', + documentNumber: '940992310285', + documentNumberCheckDigit: '4', + firstName: 'CORINNE', + birthDate: '651206', + birthDateCheckDigit: '8', + sex: 'female', + compositeCheckDigit: '4' + }); + }); }); diff --git a/src/parse/frenchNationalIdFields.js b/src/parse/frenchNationalIdFields.js index 8916e65..82f2ea3 100644 --- a/src/parse/frenchNationalIdFields.js +++ b/src/parse/frenchNationalIdFields.js @@ -59,7 +59,7 @@ module.exports = [ }, Object.assign({}, documentNumberTemplate, { line: 1, - start: 7, + start: 0, end: 12 }), Object.assign({}, documentNumberCheckDigitTemplate, { diff --git a/src/parse/parse.js b/src/parse/parse.js index 14fb022..d10254b 100644 --- a/src/parse/parse.js +++ b/src/parse/parse.js @@ -13,8 +13,7 @@ function parseMRZ(lines) { case 30: return parsers.TD1(lines); case 36: { - const endLine1 = lines[0].substr(30, 36); - if (endLine1.match(/[0-9]/)) { + if (lines[0].match(/^I.FRA/)) { return parsers.FRENCH_NATIONAL_ID(lines); } else { return parsers.TD2(lines); From e3ebb0ab19450512591dbc453848b02b2bb7501c Mon Sep 17 00:00:00 2001 From: lukesolo Date: Mon, 1 Jun 2020 14:18:15 +0300 Subject: [PATCH 2/7] Add jest.config.js to fix tests --- jest.config.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 jest.config.js diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..3c5a4f7 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + testURL: 'http://localhost', +}; From 747aded93c1af94662a6716be2dfe5436814cb4e Mon Sep 17 00:00:00 2001 From: Artem Gerus Date: Tue, 9 Jun 2020 10:56:25 +0300 Subject: [PATCH 3/7] add RP --- src/parsers/parseDocumentCodeId.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parsers/parseDocumentCodeId.js b/src/parsers/parseDocumentCodeId.js index 41d6202..b63075f 100644 --- a/src/parsers/parseDocumentCodeId.js +++ b/src/parsers/parseDocumentCodeId.js @@ -2,9 +2,9 @@ module.exports = function parseDocumentCodeId(source) { const first = source.charAt(0); - if (first !== 'A' && first !== 'C' && first !== 'I') { + if (first !== 'A' && first !== 'C' && first !== 'I' && source !== 'R') { throw new Error( - `invalid document code: ${source}. First character must be A, C or I` + `invalid document code: ${source}. First character must be A, C, R or I` ); } From e7802e5e0f5c2127e90c29285218b50e2587902b Mon Sep 17 00:00:00 2001 From: Artem Gerus Date: Tue, 9 Jun 2020 11:19:31 +0300 Subject: [PATCH 4/7] Update parseDocumentCodeId.js --- src/parsers/parseDocumentCodeId.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parsers/parseDocumentCodeId.js b/src/parsers/parseDocumentCodeId.js index b63075f..c02897f 100644 --- a/src/parsers/parseDocumentCodeId.js +++ b/src/parsers/parseDocumentCodeId.js @@ -2,7 +2,7 @@ module.exports = function parseDocumentCodeId(source) { const first = source.charAt(0); - if (first !== 'A' && first !== 'C' && first !== 'I' && source !== 'R') { + if (first !== 'A' && first !== 'C' && first !== 'I' && first !== 'R') { throw new Error( `invalid document code: ${source}. First character must be A, C, R or I` ); From 916e5cb4589046f19fc6abae45cb2e6ab801d8e2 Mon Sep 17 00:00:00 2001 From: lukesolo Date: Tue, 9 Jun 2020 11:41:23 +0300 Subject: [PATCH 5/7] v3.1.3 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7a8582c..e33163a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "mrz", - "version": "3.1.1", + "name": "@lukesolo/mrz", + "version": "3.1.3", "description": "Parse MRZ (Machine Readable Zone) from identity documents", "main": "./src/index.js", "files": [ From 94f88e1153effde2adaff27660b2f22c6b0c1685 Mon Sep 17 00:00:00 2001 From: st__dev Date: Wed, 10 Jun 2020 20:20:13 +0300 Subject: [PATCH 6/7] parse mrz from French driving licence --- src/formats.js | 3 +- src/parse/__tests__/frenchDrivingLicense.js | 22 +++++ src/parse/fieldTemplates.js | 8 +- src/parse/frenchDrivingLicence.js | 25 ++++++ src/parse/frenchDrivingLicenceFields.js | 81 +++++++++++++++++++ src/parse/parse.js | 8 ++ src/parse/parsers.js | 4 +- .../france/parseDocumentNumber.js | 11 +++ .../euDrivingLicense/france/parseLastName.js | 12 +++ .../euDrivingLicense/parseBapConfiguration.js | 15 ++++ .../euDrivingLicense/parseDocumentCode.js | 8 ++ 11 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 src/parse/__tests__/frenchDrivingLicense.js create mode 100644 src/parse/frenchDrivingLicence.js create mode 100644 src/parse/frenchDrivingLicenceFields.js create mode 100644 src/parsers/euDrivingLicense/france/parseDocumentNumber.js create mode 100644 src/parsers/euDrivingLicense/france/parseLastName.js create mode 100644 src/parsers/euDrivingLicense/parseBapConfiguration.js create mode 100644 src/parsers/euDrivingLicense/parseDocumentCode.js diff --git a/src/formats.js b/src/formats.js index 60405c4..0ac3fa8 100644 --- a/src/formats.js +++ b/src/formats.js @@ -5,7 +5,8 @@ const formats = { TD2: 'TD2', TD3: 'TD3', SWISS_DRIVING_LICENSE: 'SWISS_DRIVING_LICENSE', - FRENCH_NATIONAL_ID: 'FRENCH_NATIONAL_ID' + FRENCH_NATIONAL_ID: 'FRENCH_NATIONAL_ID', + FRENCH_DRIVING_LICENCE: 'FRENCH_DRIVING_LICENCE' }; Object.freeze(formats); diff --git a/src/parse/__tests__/frenchDrivingLicense.js b/src/parse/__tests__/frenchDrivingLicense.js new file mode 100644 index 0000000..b144d30 --- /dev/null +++ b/src/parse/__tests__/frenchDrivingLicense.js @@ -0,0 +1,22 @@ +'use strict'; + +const parse = require('../parse'); + +describe('parse French Driving License', () => { + it('valid MRZ', function () { + const MRZ = [ 'D1FRA13BB148959280920BETTOLO<7' ]; + var result = parse(MRZ); + expect(result.format).toBe('FRENCH_DRIVING_LICENCE'); + expect(result.details.filter((a) => !a.valid)).toHaveLength(0); + expect(result.fields).toEqual({ + documentCode: 'D', + bapConfiguration: '1', + issuingState: 'FRA', + documentNumber: '13BB14895', + documentNumberCheckDigit: '9', + expirationDate: '280920', + lastName: 'BETTOLO', + compositeCheckDigit: '7' + }); + }); +}); diff --git a/src/parse/fieldTemplates.js b/src/parse/fieldTemplates.js index 6d0605e..0189acb 100644 --- a/src/parse/fieldTemplates.js +++ b/src/parse/fieldTemplates.js @@ -83,6 +83,11 @@ const issuingStateTemplate = { parser: require('../parsers/parseState') }; +const bapConfigurationTemplate = { + label: 'BAP configuration', + field: 'bapConfiguration' +}; + module.exports = { documentNumberTemplate, documentNumberCheckDigitTemplate, @@ -97,5 +102,6 @@ module.exports = { compositeCheckDigitTemplate, firstNameTemplate, lastNameTemplate, - issuingStateTemplate + issuingStateTemplate, + bapConfigurationTemplate }; diff --git a/src/parse/frenchDrivingLicence.js b/src/parse/frenchDrivingLicence.js new file mode 100644 index 0000000..3ad13ba --- /dev/null +++ b/src/parse/frenchDrivingLicence.js @@ -0,0 +1,25 @@ +'use strict'; + +const checkLines = require('./checkLines'); +const getResult = require('./getResult'); +const { FRENCH_DRIVING_LICENCE } = require('../formats'); +const frenchDrivingLicenceFields = require('./frenchDrivingLicenceFields'); + +module.exports = function parseFrenchNationalId(lines) { + lines = checkLines(lines); + if (lines.length !== 1) { + throw new Error( + `invalid number of lines: ${ + lines.length + }: Must be 1 for ${FRENCH_DRIVING_LICENCE}` + ); + } + if (lines[0].length !== 30) { + throw new Error( + `invalid number of characters for line: ${ + lines[0].length + }. Must be 30 for ${FRENCH_DRIVING_LICENCE}` + ); + } + return getResult(FRENCH_DRIVING_LICENCE, lines, frenchDrivingLicenceFields); +}; diff --git a/src/parse/frenchDrivingLicenceFields.js b/src/parse/frenchDrivingLicenceFields.js new file mode 100644 index 0000000..03f7cb8 --- /dev/null +++ b/src/parse/frenchDrivingLicenceFields.js @@ -0,0 +1,81 @@ +'use strict'; + +const parseDocumentCode = require('../parsers/euDrivingLicense/parseDocumentCode'); +const parseBapConfiguration = require('../parsers/euDrivingLicense/parseBapConfiguration'); +const parseState = require('../parsers/parseState'); +const parseDocumentNumber = require('../parsers/euDrivingLicense/france/parseDocumentNumber'); +const parseLastName = require('../parsers/euDrivingLicense/france/parseLastName'); + +const { + documentCodeTemplate, + bapConfigurationTemplate, + issuingStateTemplate, + documentNumberTemplate, + documentNumberCheckDigitTemplate, + expirationDateTemplate, + lastNameTemplate, + compositeCheckDigitTemplate +} = require('./fieldTemplates'); +const createFieldParser = require('./createFieldParser'); + +module.exports = [ + Object.assign({}, documentCodeTemplate, { + line: 0, + start: 0, + end: 1, + parser: parseDocumentCode + }), + Object.assign({}, bapConfigurationTemplate, { + line: 0, + start: 1, + end: 2, + parser: parseBapConfiguration + }), + Object.assign({}, issuingStateTemplate, { + line: 0, + start: 2, + end: 5, + parser: parseState + }), + Object.assign({}, documentNumberTemplate, { + line: 0, + start: 5, + end: 14, + parser: parseDocumentNumber + }), + Object.assign({}, documentNumberCheckDigitTemplate, { + line: 0, + start: 14, + end: 15, + related: [ + { + line: 0, + start: 5, + end: 14 + } + ] + }), + Object.assign({}, expirationDateTemplate, { + line: 0, + start: 15, + end: 21 + }), + Object.assign({}, lastNameTemplate, { + line: 0, + start: 21, + end: 29, + parser: parseLastName + }), + Object.assign({}, compositeCheckDigitTemplate, { + line: 0, + start: 29, + end: 30, + related: [ + { + line: 0, + start: 0, + end: 29 + } + ] + }) +].map(createFieldParser); diff --git a/src/parse/parse.js b/src/parse/parse.js index d10254b..098fba1 100644 --- a/src/parse/parse.js +++ b/src/parse/parse.js @@ -7,6 +7,14 @@ const parsers = require('./parsers'); function parseMRZ(lines) { lines = checkLines(lines); switch (lines.length) { + case 1:{ + if (lines[0].match(/^D[1,P,N,<]FRA/)) { + return parsers.FRENCH_DRIVING_LICENCE(lines); + } + throw new Error( + 'unrecognized document format. Input must match pattern /^D[1,P,N,<]FRA/ (French Driving License)' + ); + } case 2: case 3: { switch (lines[0].length) { diff --git a/src/parse/parsers.js b/src/parse/parsers.js index 510e159..3572cf5 100644 --- a/src/parse/parsers.js +++ b/src/parse/parsers.js @@ -5,11 +5,13 @@ const parseTD2 = require('./td2'); const parseTD3 = require('./td3'); const parseSwissDrivingLicense = require('./swissDrivingLicense'); const parseFrenchNationalId = require('./frenchNationalId'); +const parseFrenchDrivingLicence = require('./frenchDrivingLicence'); module.exports = { TD1: parseTD1, TD2: parseTD2, TD3: parseTD3, SWISS_DRIVING_LICENSE: parseSwissDrivingLicense, - FRENCH_NATIONAL_ID: parseFrenchNationalId + FRENCH_NATIONAL_ID: parseFrenchNationalId, + FRENCH_DRIVING_LICENCE: parseFrenchDrivingLicence }; diff --git a/src/parsers/euDrivingLicense/france/parseDocumentNumber.js b/src/parsers/euDrivingLicense/france/parseDocumentNumber.js new file mode 100644 index 0000000..4a9d7ce --- /dev/null +++ b/src/parsers/euDrivingLicense/france/parseDocumentNumber.js @@ -0,0 +1,11 @@ +'use strict'; + +module.exports = function parseDocumentCode(source) { + // french driving license number + if (!source.match(/^[0-9]{2}[A-Z]{2}[0-9]{5}$/)) { + throw new Error( + `invalid document number: ${source}.` + ); + } + return source; +}; diff --git a/src/parsers/euDrivingLicense/france/parseLastName.js b/src/parsers/euDrivingLicense/france/parseLastName.js new file mode 100644 index 0000000..78cb756 --- /dev/null +++ b/src/parsers/euDrivingLicense/france/parseLastName.js @@ -0,0 +1,12 @@ +'use strict'; + +var parseText = require('../../parseText'); + +module.exports = function parseLastName(source) { + const parsed = parseText(source, /^[A-Z<]+<*$/); + return { + value: parsed, + start: 0, + end: parsed.length + }; +}; diff --git a/src/parsers/euDrivingLicense/parseBapConfiguration.js b/src/parsers/euDrivingLicense/parseBapConfiguration.js new file mode 100644 index 0000000..5982e68 --- /dev/null +++ b/src/parsers/euDrivingLicense/parseBapConfiguration.js @@ -0,0 +1,15 @@ +'use strict'; + +module.exports = function parseBapConfiguration(bapConfig) { + switch (bapConfig) { + case '1': + case 'P': + case 'N': + case '<': + return bapConfig; + default: + throw new Error( + `invalid BAP configuration code: ${bapConfig}. Must be 1, P, N or <` + ); + } +}; diff --git a/src/parsers/euDrivingLicense/parseDocumentCode.js b/src/parsers/euDrivingLicense/parseDocumentCode.js new file mode 100644 index 0000000..35f1b55 --- /dev/null +++ b/src/parsers/euDrivingLicense/parseDocumentCode.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function parseDocumentCode(source) { + if (source !== 'D') { + throw new Error(`invalid document code: ${source}. Must be D`); + } + return source; +}; From e3efd158a60db4a084db45962b546b37aa2475b0 Mon Sep 17 00:00:00 2001 From: st__dev Date: Thu, 11 Jun 2020 11:19:38 +0300 Subject: [PATCH 7/7] implemented code-review suggestions --- src/formats.js | 2 +- src/parse/__tests__/frenchDrivingLicense.js | 2 +- ...enchDrivingLicence.js => frenchDrivingLicense.js} | 12 ++++++------ ...icenceFields.js => frenchDrivingLicenseFields.js} | 0 src/parse/parse.js | 6 +++--- src/parse/parsers.js | 4 ++-- src/parsers/euDrivingLicense/france/parseLastName.js | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) rename src/parse/{frenchDrivingLicence.js => frenchDrivingLicense.js} (54%) rename src/parse/{frenchDrivingLicenceFields.js => frenchDrivingLicenseFields.js} (100%) diff --git a/src/formats.js b/src/formats.js index 0ac3fa8..dc1b6f2 100644 --- a/src/formats.js +++ b/src/formats.js @@ -6,7 +6,7 @@ const formats = { TD3: 'TD3', SWISS_DRIVING_LICENSE: 'SWISS_DRIVING_LICENSE', FRENCH_NATIONAL_ID: 'FRENCH_NATIONAL_ID', - FRENCH_DRIVING_LICENCE: 'FRENCH_DRIVING_LICENCE' + FRENCH_DRIVING_LICENSE: 'FRENCH_DRIVING_LICENSE' }; Object.freeze(formats); diff --git a/src/parse/__tests__/frenchDrivingLicense.js b/src/parse/__tests__/frenchDrivingLicense.js index b144d30..89ffce6 100644 --- a/src/parse/__tests__/frenchDrivingLicense.js +++ b/src/parse/__tests__/frenchDrivingLicense.js @@ -6,7 +6,7 @@ describe('parse French Driving License', () => { it('valid MRZ', function () { const MRZ = [ 'D1FRA13BB148959280920BETTOLO<7' ]; var result = parse(MRZ); - expect(result.format).toBe('FRENCH_DRIVING_LICENCE'); + expect(result.format).toBe('FRENCH_DRIVING_LICENSE'); expect(result.details.filter((a) => !a.valid)).toHaveLength(0); expect(result.fields).toEqual({ documentCode: 'D', diff --git a/src/parse/frenchDrivingLicence.js b/src/parse/frenchDrivingLicense.js similarity index 54% rename from src/parse/frenchDrivingLicence.js rename to src/parse/frenchDrivingLicense.js index 3ad13ba..7890745 100644 --- a/src/parse/frenchDrivingLicence.js +++ b/src/parse/frenchDrivingLicense.js @@ -2,24 +2,24 @@ const checkLines = require('./checkLines'); const getResult = require('./getResult'); -const { FRENCH_DRIVING_LICENCE } = require('../formats'); -const frenchDrivingLicenceFields = require('./frenchDrivingLicenceFields'); +const { FRENCH_DRIVING_LICENSE } = require('../formats'); +const frenchDrivingLicenseFields = require('./frenchDrivingLicenseFields'); -module.exports = function parseFrenchNationalId(lines) { +module.exports = function parseFrenchDrivingLicense(lines) { lines = checkLines(lines); if (lines.length !== 1) { throw new Error( `invalid number of lines: ${ lines.length - }: Must be 1 for ${FRENCH_DRIVING_LICENCE}` + }: Must be 1 for ${FRENCH_DRIVING_LICENSE}` ); } if (lines[0].length !== 30) { throw new Error( `invalid number of characters for line: ${ lines[0].length - }. Must be 30 for ${FRENCH_DRIVING_LICENCE}` + }. Must be 30 for ${FRENCH_DRIVING_LICENSE}` ); } - return getResult(FRENCH_DRIVING_LICENCE, lines, frenchDrivingLicenceFields); + return getResult(FRENCH_DRIVING_LICENSE, lines, frenchDrivingLicenseFields); }; diff --git a/src/parse/frenchDrivingLicenceFields.js b/src/parse/frenchDrivingLicenseFields.js similarity index 100% rename from src/parse/frenchDrivingLicenceFields.js rename to src/parse/frenchDrivingLicenseFields.js diff --git a/src/parse/parse.js b/src/parse/parse.js index 098fba1..60e842b 100644 --- a/src/parse/parse.js +++ b/src/parse/parse.js @@ -8,11 +8,11 @@ function parseMRZ(lines) { lines = checkLines(lines); switch (lines.length) { case 1:{ - if (lines[0].match(/^D[1,P,N,<]FRA/)) { - return parsers.FRENCH_DRIVING_LICENCE(lines); + if (lines[0].match(/^D[1PN<]FRA/)) { + return parsers.FRENCH_DRIVING_LICENSE(lines); } throw new Error( - 'unrecognized document format. Input must match pattern /^D[1,P,N,<]FRA/ (French Driving License)' + 'unrecognized document format. Input must match pattern /^D[1PN<]FRA/ (French Driving License)' ); } case 2: diff --git a/src/parse/parsers.js b/src/parse/parsers.js index 3572cf5..d54c155 100644 --- a/src/parse/parsers.js +++ b/src/parse/parsers.js @@ -5,7 +5,7 @@ const parseTD2 = require('./td2'); const parseTD3 = require('./td3'); const parseSwissDrivingLicense = require('./swissDrivingLicense'); const parseFrenchNationalId = require('./frenchNationalId'); -const parseFrenchDrivingLicence = require('./frenchDrivingLicence'); +const parseFrenchDrivingLicense = require('./frenchDrivingLicense'); module.exports = { TD1: parseTD1, @@ -13,5 +13,5 @@ module.exports = { TD3: parseTD3, SWISS_DRIVING_LICENSE: parseSwissDrivingLicense, FRENCH_NATIONAL_ID: parseFrenchNationalId, - FRENCH_DRIVING_LICENCE: parseFrenchDrivingLicence + FRENCH_DRIVING_LICENSE: parseFrenchDrivingLicense }; diff --git a/src/parsers/euDrivingLicense/france/parseLastName.js b/src/parsers/euDrivingLicense/france/parseLastName.js index 78cb756..e3fc357 100644 --- a/src/parsers/euDrivingLicense/france/parseLastName.js +++ b/src/parsers/euDrivingLicense/france/parseLastName.js @@ -3,7 +3,7 @@ var parseText = require('../../parseText'); module.exports = function parseLastName(source) { - const parsed = parseText(source, /^[A-Z<]+<*$/); + const parsed = parseText(source, /^[A-Z<]+$/); return { value: parsed, start: 0,