Sorry, the diff of this file is not supported yet
| class Field86StructureParser { | ||
| static buildTagRe(details) { | ||
| const prefix = details.charAt(); | ||
| // check first symbol is known separator | ||
| let tagRe; | ||
| if (!prefix) { | ||
| return; | ||
| } else if (prefix === '/') { // assume /XXX/ fields | ||
| tagRe = '\\/[0-9A-Z]{2,4}\\/'; | ||
| } else if ('>?'.includes(prefix)) { // assume >DD fields | ||
| tagRe = `\\${prefix}\\d{2}`; | ||
| } else return; // known separator not found | ||
| return { | ||
| detect: new RegExp(`^${tagRe}`), // line must begin with a tag | ||
| match: new RegExp(`(${tagRe})(.*?)(?=(${tagRe})|$)`, 'g'), // matcher | ||
| // item: new RegExp(`^(${tagRe})(.*)`), | ||
| strip: new RegExp(`\\${prefix}`, 'g'), | ||
| }; | ||
| } | ||
| /** | ||
| * Detects if field 86 is structured and attempts to parse it | ||
| */ | ||
| static parse(details) { | ||
| details = details.replace(/\n/g, '').trim(); | ||
| const rule = Field86StructureParser.buildTagRe(details); | ||
| if (!rule) return; | ||
| if (!rule.detect.test(details)) return; // string must start with tag | ||
| const parsedStruc = {}; | ||
| let match = rule.match.exec(details); | ||
| while (match) { | ||
| const subTag = match[1].replace(rule.strip, ''); | ||
| parsedStruc[subTag] = match[2]; | ||
| match = rule.match.exec(details); | ||
| } | ||
| return parsedStruc; | ||
| } | ||
| } | ||
| module.exports = Field86StructureParser; |
Sorry, the diff of this file is not supported yet
+2
-1
@@ -19,4 +19,5 @@ { | ||
| "no-console": "off", | ||
| "no-trailing-spaces": ["error"] | ||
| "no-trailing-spaces": ["error"], | ||
| "indent": ["error", 2] | ||
| } | ||
| } |
+6
-1
@@ -11,4 +11,9 @@ mt940-js changelog | ||
| 2019-01-29 v1.2.2 | ||
| 2019-01-31 v1.3.1 | ||
| ------------------ | ||
| + no86structure param to skip 86 tag structure detection | ||
| * minor fixes and refactorings | ||
| 2019-01-29 v1.3.0 | ||
| ------------------ | ||
| + experimental messageBlocks detection and partial parsing '{1:...}' | ||
@@ -15,0 +20,0 @@ |
+12
-12
@@ -20,15 +20,15 @@ const fs = require('fs'); | ||
| switch (arg) { | ||
| case '--help': | ||
| case '-h': | ||
| banner(); | ||
| case '--help': | ||
| case '-h': | ||
| banner(); | ||
| process.exit(1); | ||
| break; | ||
| default: | ||
| try { | ||
| fs.accessSync(arg); | ||
| files.push(arg); | ||
| } catch (e) { | ||
| console.error(`Cannot access file: ${arg}`); | ||
| process.exit(1); | ||
| break; | ||
| default: | ||
| try { | ||
| fs.accessSync(arg); | ||
| files.push(arg); | ||
| } catch (e) { | ||
| console.error(`Cannot access file: ${arg}`); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| } | ||
@@ -35,0 +35,0 @@ }); |
+17
-44
@@ -8,2 +8,3 @@ /** | ||
| const helperModels = require('./helperModels'); | ||
| const Field86Structure = require('./field86structure'); | ||
@@ -60,3 +61,11 @@ /** | ||
| class Parser { | ||
| constructor () { | ||
| /** | ||
| * Constructor, params are given as object fields { no86Structure: true } | ||
| * @constructor | ||
| * @param {boolean} no86Structure - don't parse 86 field structure | ||
| */ | ||
| constructor ({ no86Structure } = {}) { | ||
| this.params = { | ||
| no86Structure, | ||
| }; | ||
| this.postParseMiddlewareStack = []; | ||
@@ -171,3 +180,3 @@ } | ||
| !hasMessageBlocks && i instanceof Tags.TagTransactionReferenceNumber) { | ||
| groups.push(curGroup = []); // Statement starting tag -> start new group | ||
| groups.push(curGroup = []); // Statement starting tag -> start new group | ||
| } | ||
@@ -317,5 +326,7 @@ curGroup.push(i); | ||
| } | ||
| for (let t of statement.transactions) { | ||
| let structuredDetails = this._detectDetailStructure(t); | ||
| if (structuredDetails) t.structuredDetails = structuredDetails; | ||
| if (!this.params.no86Structure) { | ||
| for (let t of statement.transactions) { | ||
| let structuredDetails = Field86Structure.parse(t.details); | ||
| if (structuredDetails) t.structuredDetails = structuredDetails; | ||
| } | ||
| } | ||
@@ -336,44 +347,6 @@ if (withTags) { statement.tags = group } // preserve tags | ||
| /** | ||
| * Detects if field 86 is structured and attempts to parse it | ||
| * @private | ||
| */ | ||
| _detectDetailStructure(transaction) { | ||
| const details = transaction.details.replace(/\n/g, '').trim(); | ||
| const prefix = details.charAt(); | ||
| // check first symbol is known separator | ||
| let tagRe; | ||
| if (!prefix) return; | ||
| else if (prefix === '/') { // assume /XXX/ fields | ||
| tagRe = '\\/[0-9A-Z]{2,4}\\/'; | ||
| } else if ('>?'.includes(prefix)) { // assume >DD fields | ||
| tagRe = `\\${prefix}\\d{2}`; | ||
| } else return; // known separator not found | ||
| const rule = { | ||
| detect: new RegExp(`^${tagRe}`), | ||
| split: new RegExp(`(?=${tagRe})`), | ||
| item: new RegExp(`^(${tagRe})(.*)`), | ||
| }; | ||
| const stripPrefixRe = new RegExp(`\\${prefix}`, 'g'); | ||
| if (!rule.detect.test(details)) return; // string must start with tag | ||
| const matches = details.split(rule.split); | ||
| if (matches.length > 0 && matches[0].length === 0) matches.shift(); // remove empty match at start | ||
| if (matches.length === 0) return; // no matches found | ||
| const parsedStruc = matches | ||
| .map(m => m.match(rule.item)) // supposed to match groups 1 and 2 | ||
| .map(m => [ m[1].replace(stripPrefixRe, ''), m[2] ]) // remove prefix symbols from tag | ||
| .reduce((struc, m) => Object.assign(struc, { [m[0]]: m[1] }), {}); | ||
| return parsedStruc; | ||
| } | ||
| /** | ||
| * parses message blocks | ||
| * @private | ||
| */ | ||
| _parseMessageBlockFields(messageId, value) { | ||
| _parseMessageBlockFields(messageId, value) { //eslint-disable-line no-unused-vars | ||
| // TODO somethings like: | ||
@@ -380,0 +353,0 @@ // messageBlockFactory.createTag(messageId, value) |
+1
-1
| { | ||
| "name": "mt940js", | ||
| "version": "1.3.0", | ||
| "version": "1.3.1", | ||
| "description": "javascript mt940 bank statement parser", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
+2
-1
@@ -100,3 +100,3 @@ # SWIFT MT940 bank statement format JS parser | ||
| The parser attempts to detect if field 86 contains tags like these and, if yes, adds `structuredDetails` attribute to a statement line. Tag digits are not interpreted as they are not standardized among different banks. | ||
| The parser attempts to detect if field 86 contains tags like these and, if yes, adds `structuredDetails` attribute to a statement line. Tag digits are not interpreted as they are not standardized among different banks. Parsing 86 structure can be force disabled by passing `{ no86Structure: true }` to the constructor. | ||
@@ -137,2 +137,3 @@ ```javascript | ||
| - pre parsing middlewares | ||
| - parsing structure of block messages | ||
@@ -139,0 +140,0 @@ ## Author |
+30
-0
@@ -18,2 +18,13 @@ const assert = require('chai').assert; | ||
| const DUMMY_STATEMENT_LINES_WITH_STRUCTURE = [ | ||
| ':20:B4E08MS9D00A0009', | ||
| ':21:X', | ||
| ':25:123456789', | ||
| ':28C:123/1', | ||
| ':60F:C140507EUR0,00', | ||
| ':61:1405070507C500,00NTRFNONREF//AUXREF', | ||
| ':86:?20some?21data', | ||
| ':62F:C140508EUR500,00', | ||
| ]; | ||
| const DUMMY_STATEMENT_LINES_61_64_65 = [ | ||
@@ -264,2 +275,21 @@ ':20:B4E08MS9D00A0009', | ||
| it('statement with structured 86', () => { | ||
| let parser = new Parser(); | ||
| let result = parser.parse(DUMMY_STATEMENT_LINES_WITH_STRUCTURE.join('\n')); | ||
| assert.equal(result.length, 1); | ||
| const exp = expectedStatement(); | ||
| exp.transactions[0].details = '?20some?21data'; | ||
| exp.transactions[0].structuredDetails = { | ||
| '20': 'some', | ||
| '21': 'data', | ||
| }; | ||
| assert.deepEqual(result[0], exp); | ||
| parser = new Parser({ no86Structure: true }); | ||
| result = parser.parse(DUMMY_STATEMENT_LINES_WITH_STRUCTURE.join('\n')); | ||
| delete exp.transactions[0].structuredDetails; | ||
| assert.deepEqual(result[0], exp); | ||
| }); | ||
| it('statement with fields 64, 65, long 61 and statement comment', () => { | ||
@@ -266,0 +296,0 @@ const parser = new Parser(); |
+24
-10
@@ -1,15 +0,9 @@ | ||
| const assert = require('chai').assert; | ||
| const Parser = require('../lib/parser'); | ||
| const parser = new Parser(); | ||
| const assert = require('chai').assert; | ||
| const Field86Parser = require('../lib/field86structure'); | ||
| function run(details) { | ||
| const transaction = { | ||
| details: details | ||
| }; | ||
| const structure = parser._detectDetailStructure(transaction); | ||
| // console.log(structure); | ||
| return structure; | ||
| return Field86Parser.parse(details); | ||
| } | ||
| describe('Parser::_detectDetailStructure', () => { | ||
| describe('Field86Structure', () => { | ||
| it('Detects no structure', () => { | ||
@@ -68,2 +62,22 @@ assert.isUndefined(run('some arbitrary text')); | ||
| it('complex detect', () => { | ||
| const tag = [ | ||
| ' /XXXX//100924006010 XXXXXXXXXXXXX XXXXXXXX XXXXXX AB (PUBL)', | ||
| ' /ORDP/XX XXXXXX XXXXX XXXX N.A.25 XXXX XXXXX, CANARY WHARF', | ||
| ' /REMI/UBERWEISUNG OUR REF: 03MT181024144353', | ||
| 'YOUR REF: P6363103 240 1 M CA O/XXXXGB2L', | ||
| '/ACC/INST/XXXXGB2L /6231400604', | ||
| 'BIC:XXXXGB2L', | ||
| ].join(''); | ||
| const parsed = run(tag); | ||
| console.log(parsed); | ||
| assert.deepEqual(parsed, { | ||
| 'XXXX': '/100924006010 XXXXXXXXXXXXX XXXXXXXX XXXXXX AB (PUBL) ', | ||
| 'ORDP': 'XX XXXXXX XXXXX XXXX N.A.25 XXXX XXXXX, CANARY WHARF ', | ||
| 'REMI': 'UBERWEISUNG OUR REF: 03MT181024144353YOUR REF: P6363103 240 1 M CA O/XXXXGB2L', | ||
| 'ACC': 'INST/XXXXGB2L /6231400604BIC:XXXXGB2L' | ||
| }); | ||
| }); | ||
| }); |
71822
5.01%22
15.79%1339
4.45%151
0.67%