aamvajs
Version:
Parse Pdf417 barcode data from US and Canada driver licenses. AAMVA Parser
139 lines (120 loc) • 5.14 kB
text/typescript
import { dataMatchHeaders } from './data-match';
import {
IOptions,
IDocument,
IDocumentSubfile,
IDocumentData
} from './interfaces';
export class AAMVA {
static raw(rawDocument: string, options: IOptions = {}): IDocument {
let headerLenght = 21;
const separator = rawDocument.charAt(1);
const terminator = rawDocument.charAt(3);
const fileType = rawDocument.substring(4, 9).replace(' ', '');
const iin = rawDocument.substring(9, 15);
const version = parseInt(rawDocument.substring(15, 17), 10);
const jurisdictionVersion = parseInt(rawDocument.substring(17, 19), 10);
let numberOfEntries = parseInt(rawDocument.substring(19, 21), 10);
if (!numberOfEntries) {
numberOfEntries = parseInt(rawDocument.substring(17, 19), 10);
headerLenght -= 2;
}
let subfileOffset = 0;
let subfuleLength = 0;
let subfileData: string[];
let dataHeaderId = '';
const subfiles: IDocumentSubfile[] = [];
const data: IDocumentData = {
localFields: {}
};
for (let i = 0; i < numberOfEntries; i++) {
subfileOffset = parseInt(rawDocument.substring(headerLenght + i * 10 + 2, headerLenght + i * 10 + 6));
subfuleLength = parseInt(rawDocument.substring(headerLenght + i * 10 + 6, headerLenght + i * 10 + 10));
if (!subfileOffset) {
if (i === 0) {
subfileOffset = headerLenght + numberOfEntries * 10;
}
else {
subfileOffset = subfiles[subfiles.length - 1].offset + subfiles[subfiles.length - 1].length;
}
}
subfileData = rawDocument
.substring(subfileOffset + separator.length + terminator.length, subfileOffset + subfuleLength - terminator.length)
.replace(separator + terminator, '')
.split(separator)
.filter(field => field.length);
subfiles.push({
type: rawDocument.substring(headerLenght + i * 10, headerLenght + i * 10 + 2),
offset: subfileOffset,
length: subfuleLength,
data: subfileData
});
let fieldData: string = '';
subfileData.forEach(subfileDataField => {
dataHeaderId = subfileDataField.substring(0, 3);
const allHeaders = dataMatchHeaders.filter(header => header.id === dataHeaderId);
if (allHeaders.length) {
let fieldHeader = allHeaders.find(header => header.version === version);
if (!fieldHeader) fieldHeader = allHeaders.find(header => !header.version);
if (fieldHeader) {
fieldData = fieldHeader.converters.reduce((acc, converter) => converter(acc), subfileDataField.substring(3));
if (dataHeaderId.charAt(0) === 'Z') {
if (!(fieldHeader.state && data.state && fieldHeader.state !== data.state)) {
if (!data.localFields[fieldHeader.name]) {
data.localFields[fieldHeader.name] = fieldData;
}
}
}
else {
data[fieldHeader.name] = fieldData;
}
}
}
});
}
if (options.clearNoneValue) {
for (let field in data) {
if (field !== 'localFields') {
if (data[field].toUpperCase() === 'NONE') {
data[field] = '';
}
}
}
for (let field in data.localFields) {
if (data.localFields[field].toUpperCase() === 'NONE') {
data.localFields[field] = '';
}
}
}
if (options.removeEmptyFields) {
for (let field in data) {
if (data[field] === '') {
delete data[field];
}
}
for (let field in data.localFields) {
if (data.localFields[field] === '') {
delete data.localFields[field];
}
}
}
const document: IDocument = {
type: subfiles[0]?.type ?? '',
header: {
separator,
terminator,
fileType,
iin,
version,
jurisdictionVersion,
numberOfEntries
},
subfiles,
data
};
return document;
}
static base64(base64Document: string, options: IOptions = {}): IDocument {
return AAMVA.raw(Buffer.from(base64Document, 'base64').toString(), options);
}
}