@fin.cx/mt940 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@fin.cx/mt940',
6
- version: '2.0.0',
6
+ version: '2.1.0',
7
7
  description: 'A parser to process and extract statements from MT940 format files, commonly used by banks for electronic account statements.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxlQUFlO0lBQ3JCLE9BQU8sRUFBRSxPQUFPO0lBQ2hCLFdBQVcsRUFBRSwrSEFBK0g7Q0FDN0ksQ0FBQSJ9
@@ -1,5 +1,21 @@
1
1
  import * as plugins from './mt940parser.plugins.js';
2
+ /** The encoding an MT940 file is read in: UTF-8 where its bytes are valid UTF-8, else Windows-1252. */
3
+ export type TMt940Encoding = 'utf-8' | 'windows-1252';
2
4
  export declare class Mt940Parser {
5
+ /**
6
+ * Parses MT940 file content given as text. Its characters are read as they
7
+ * are, whatever encoding the file was in before it became text.
8
+ */
3
9
  parseMt940FileString(fileStringArg: string): Promise<plugins.mt940Js.Statement[]>;
10
+ /**
11
+ * Parses the bytes of an MT940 file: UTF-8 where they are valid UTF-8 (a
12
+ * byte order mark is not part of the text), else Windows-1252, the
13
+ * encoding German banks write MT940 in. Every field is decoded from its own
14
+ * bytes, by the Encoding Standard's table for Windows-1252 on every
15
+ * runtime (`@push.rocks/smartstring`).
16
+ */
17
+ parseMt940FileBytes(fileBytesArg: Uint8Array): Promise<plugins.mt940Js.Statement[]>;
18
+ /** Parses the bytes of an MT940 file in an `ArrayBufferLike`, as `parseMt940FileBytes` does. */
4
19
  parseMt940Buffer(fileBufferArg: ArrayBufferLike): Promise<plugins.mt940Js.Statement[]>;
20
+ private parseBytesIn;
5
21
  }
package/dist_ts/index.js CHANGED
@@ -1,15 +1,132 @@
1
1
  import * as plugins from './mt940parser.plugins.js';
2
+ const UTF8_BYTE_ORDER_MARK = [0xef, 0xbb, 0xbf];
3
+ const startsWithUtf8ByteOrderMark = (bytesArg) => UTF8_BYTE_ORDER_MARK.every((byteArg, indexArg) => bytesArg[indexArg] === byteArg);
4
+ /** The byte of the no-break space in Windows-1252, and of a UTF-8 continuation byte a character may end in (à is C3 A0). */
5
+ const NO_BREAK_SPACE_BYTE = 0xa0;
6
+ /**
7
+ * The bytes a field of mt940-js holds. mt940-js reads its input one byte as
8
+ * one character (`String.fromCharCode` of each byte), so each code unit of a
9
+ * field it answers is one byte of the file.
10
+ */
11
+ const bytesOfField = (fieldArg) => {
12
+ const bytes = new Uint8Array(fieldArg.length);
13
+ for (let index = 0; index < fieldArg.length; index++) {
14
+ const codeUnit = fieldArg.charCodeAt(index);
15
+ if (codeUnit > 0xff) {
16
+ throw new Error(`mt940-js answered a character that is no byte (U+${codeUnit.toString(16)})`);
17
+ }
18
+ bytes[index] = codeUnit;
19
+ }
20
+ return bytes;
21
+ };
22
+ /** How many continuation bytes the UTF-8 sequence at the end of `bytesArg` still lacks (0 when it is complete). */
23
+ const missingContinuationBytes = (bytesArg) => {
24
+ for (let back = 1; back <= Math.min(3, bytesArg.length); back++) {
25
+ const byte = bytesArg[bytesArg.length - back];
26
+ if (byte < 0x80) {
27
+ return 0;
28
+ }
29
+ if (byte >= 0xc0) {
30
+ const length = byte >= 0xf0 ? 4 : byte >= 0xe0 ? 3 : 2;
31
+ return Math.max(0, length - back);
32
+ }
33
+ }
34
+ return 0;
35
+ };
36
+ const decodeField = (bytesArg, encodingArg) => encodingArg === 'utf-8' ? new TextDecoder('utf-8').decode(bytesArg) : plugins.smartstring.encoding.decodeWindows1252(bytesArg);
37
+ /**
38
+ * A text of an :86: field, which mt940-js trims as it reads it byte by byte:
39
+ * JavaScript's `trim` takes a no-break space (0xA0) off its end, and in a
40
+ * file of UTF-8 0xA0 is also the last byte of characters (à is C3 A0, Š C5
41
+ * A0, † E2 80 A0). No other byte from 0x80 up is whitespace to `trim`, so the
42
+ * bytes the sequence at the end lacks were each 0xA0 and are put back; what
43
+ * was trimmed after that character was whitespace.
44
+ */
45
+ const decodeTrimmedField = (fieldArg, encodingArg) => {
46
+ const bytes = bytesOfField(fieldArg);
47
+ if (encodingArg !== 'utf-8') {
48
+ return decodeField(bytes, encodingArg);
49
+ }
50
+ const missing = missingContinuationBytes(bytes);
51
+ if (missing === 0) {
52
+ return decodeField(bytes, encodingArg);
53
+ }
54
+ const completed = new Uint8Array(bytes.length + missing);
55
+ completed.set(bytes);
56
+ completed.fill(NO_BREAK_SPACE_BYTE, bytes.length);
57
+ return decodeField(completed, encodingArg);
58
+ };
59
+ /*
60
+ * mt940-js does not set every field its types name: a :61: line without a
61
+ * transaction code answers `code` undefined. A field that is no string is
62
+ * passed through as mt940-js answered it; a string is decoded from its bytes.
63
+ */
64
+ /** A field of mt940-js in the file's encoding. */
65
+ const decodeText = (valueArg, encodingArg) => typeof valueArg === 'string' ? decodeField(bytesOfField(valueArg), encodingArg) : valueArg;
66
+ /** An :86: text of mt940-js in the file's encoding (`decodeTrimmedField`). */
67
+ const decodeTrimmedText = (valueArg, encodingArg) => typeof valueArg === 'string' ? decodeTrimmedField(valueArg, encodingArg) : valueArg;
68
+ const decodeBalance = (balanceArg, encodingArg) => ({
69
+ ...balanceArg,
70
+ date: decodeText(balanceArg.date, encodingArg),
71
+ currency: decodeText(balanceArg.currency, encodingArg),
72
+ });
73
+ /** A statement mt940-js read, with every text decoded from its bytes in the file's encoding. */
74
+ const decodeStatement = (statementArg, encodingArg) => {
75
+ const text = (valueArg) => decodeText(valueArg, encodingArg);
76
+ const { referenceNumber, relatedReferenceNumber, accountId, number, additionalInformation, openingBalance, closingBalance, closingAvailableBalance, forwardAvailableBalance, } = statementArg;
77
+ return {
78
+ ...statementArg,
79
+ ...(referenceNumber !== undefined ? { referenceNumber: text(referenceNumber) } : {}),
80
+ ...(relatedReferenceNumber !== undefined ? { relatedReferenceNumber: text(relatedReferenceNumber) } : {}),
81
+ ...(accountId !== undefined ? { accountId: text(accountId) } : {}),
82
+ ...(number !== undefined ? { number: text(number) } : {}),
83
+ ...(additionalInformation !== undefined ? { additionalInformation: decodeTrimmedText(additionalInformation, encodingArg) } : {}),
84
+ ...(openingBalance ? { openingBalance: decodeBalance(openingBalance, encodingArg) } : {}),
85
+ ...(closingBalance ? { closingBalance: decodeBalance(closingBalance, encodingArg) } : {}),
86
+ ...(closingAvailableBalance ? { closingAvailableBalance: decodeBalance(closingAvailableBalance, encodingArg) } : {}),
87
+ ...(forwardAvailableBalance ? { forwardAvailableBalance: decodeBalance(forwardAvailableBalance, encodingArg) } : {}),
88
+ transactions: statementArg.transactions.map((transactionArg) => ({
89
+ ...transactionArg,
90
+ code: text(transactionArg.code),
91
+ fundsCode: text(transactionArg.fundsCode),
92
+ currency: text(transactionArg.currency),
93
+ description: decodeTrimmedText(transactionArg.description, encodingArg),
94
+ valueDate: text(transactionArg.valueDate),
95
+ entryDate: text(transactionArg.entryDate),
96
+ customerReference: text(transactionArg.customerReference),
97
+ bankReference: text(transactionArg.bankReference),
98
+ })),
99
+ };
100
+ };
2
101
  export class Mt940Parser {
102
+ /**
103
+ * Parses MT940 file content given as text. Its characters are read as they
104
+ * are, whatever encoding the file was in before it became text.
105
+ */
3
106
  async parseMt940FileString(fileStringArg) {
4
- const textEncode = new TextEncoder();
5
- const statements = await this.parseMt940Buffer(textEncode.encode(fileStringArg).buffer);
6
- return statements;
107
+ return this.parseBytesIn(new TextEncoder().encode(fileStringArg), 'utf-8');
7
108
  }
109
+ /**
110
+ * Parses the bytes of an MT940 file: UTF-8 where they are valid UTF-8 (a
111
+ * byte order mark is not part of the text), else Windows-1252, the
112
+ * encoding German banks write MT940 in. Every field is decoded from its own
113
+ * bytes, by the Encoding Standard's table for Windows-1252 on every
114
+ * runtime (`@push.rocks/smartstring`).
115
+ */
116
+ async parseMt940FileBytes(fileBytesArg) {
117
+ const encoding = plugins.smartstring.encoding.isValidUtf8(fileBytesArg) ? 'utf-8' : 'windows-1252';
118
+ return this.parseBytesIn(fileBytesArg, encoding);
119
+ }
120
+ /** Parses the bytes of an MT940 file in an `ArrayBufferLike`, as `parseMt940FileBytes` does. */
8
121
  async parseMt940Buffer(fileBufferArg) {
9
- const fileBuffer = fileBufferArg instanceof ArrayBuffer
10
- ? fileBufferArg
11
- : new Uint8Array(fileBufferArg).slice().buffer;
12
- const statements = await plugins.mt940Js.read(fileBuffer);
122
+ return this.parseMt940FileBytes(new Uint8Array(fileBufferArg));
123
+ }
124
+ async parseBytesIn(fileBytesArg, encodingArg) {
125
+ // a byte order mark is no text of the file, and mt940-js finds no statement behind one
126
+ const text = encodingArg === 'utf-8' && startsWithUtf8ByteOrderMark(fileBytesArg) ? fileBytesArg.subarray(UTF8_BYTE_ORDER_MARK.length) : fileBytesArg;
127
+ // mt940-js takes an ArrayBuffer of its own; it reads each byte as one character
128
+ const fileBuffer = text.slice().buffer;
129
+ const statements = (await plugins.mt940Js.read(fileBuffer)).map((statementArg) => decodeStatement(statementArg, encodingArg));
13
130
  // bank afterburner
14
131
  for (const statement of statements) {
15
132
  if (statement.referenceNumber === 'BUNQ BV' && statement.accountId) {
@@ -19,4 +136,4 @@ export class Mt940Parser {
19
136
  return statements;
20
137
  }
21
138
  }
22
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLDBCQUEwQixDQUFDO0FBRXBELE1BQU0sT0FBTyxXQUFXO0lBQ2YsS0FBSyxDQUFDLG9CQUFvQixDQUFDLGFBQXFCO1FBQ3JELE1BQU0sVUFBVSxHQUFHLElBQUksV0FBVyxFQUFFLENBQUM7UUFDckMsTUFBTSxVQUFVLEdBQUcsTUFBTSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUV4RixPQUFPLFVBQVUsQ0FBQztJQUNwQixDQUFDO0lBRU0sS0FBSyxDQUFDLGdCQUFnQixDQUFDLGFBQThCO1FBQzFELE1BQU0sVUFBVSxHQUFHLGFBQWEsWUFBWSxXQUFXO1lBQ3JELENBQUMsQ0FBQyxhQUFhO1lBQ2YsQ0FBQyxDQUFDLElBQUksVUFBVSxDQUFDLGFBQWEsQ0FBQyxDQUFDLEtBQUssRUFBRSxDQUFDLE1BQXFCLENBQUM7UUFDaEUsTUFBTSxVQUFVLEdBQUcsTUFBTSxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUUxRCxtQkFBbUI7UUFDbkIsS0FBSyxNQUFNLFNBQVMsSUFBSSxVQUFVLEVBQUUsQ0FBQztZQUNuQyxJQUFJLFNBQVMsQ0FBQyxlQUFlLEtBQUssU0FBUyxJQUFJLFNBQVMsQ0FBQyxTQUFTLEVBQUUsQ0FBQztnQkFDbkUsU0FBUyxDQUFDLFNBQVMsR0FBRyxTQUFTLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUMxRCxDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sVUFBVSxDQUFDO0lBQ3BCLENBQUM7Q0FDRiJ9
139
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLDBCQUEwQixDQUFDO0FBS3BELE1BQU0sb0JBQW9CLEdBQXNCLENBQUMsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztBQUVuRSxNQUFNLDJCQUEyQixHQUFHLENBQUMsUUFBb0IsRUFBVyxFQUFFLENBQ3BFLG9CQUFvQixDQUFDLEtBQUssQ0FBQyxDQUFDLE9BQU8sRUFBRSxRQUFRLEVBQUUsRUFBRSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsS0FBSyxPQUFPLENBQUMsQ0FBQztBQUVwRiw0SEFBNEg7QUFDNUgsTUFBTSxtQkFBbUIsR0FBRyxJQUFJLENBQUM7QUFFakM7Ozs7R0FJRztBQUNILE1BQU0sWUFBWSxHQUFHLENBQUMsUUFBZ0IsRUFBYyxFQUFFO0lBQ3BELE1BQU0sS0FBSyxHQUFHLElBQUksVUFBVSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUM5QyxLQUFLLElBQUksS0FBSyxHQUFHLENBQUMsRUFBRSxLQUFLLEdBQUcsUUFBUSxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUFDO1FBQ3JELE1BQU0sUUFBUSxHQUFHLFFBQVEsQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDNUMsSUFBSSxRQUFRLEdBQUcsSUFBSSxFQUFFLENBQUM7WUFDcEIsTUFBTSxJQUFJLEtBQUssQ0FBQyxvREFBb0QsUUFBUSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDaEcsQ0FBQztRQUNELEtBQUssQ0FBQyxLQUFLLENBQUMsR0FBRyxRQUFRLENBQUM7SUFDMUIsQ0FBQztJQUNELE9BQU8sS0FBSyxDQUFDO0FBQ2YsQ0FBQyxDQUFDO0FBRUYsbUhBQW1IO0FBQ25ILE1BQU0sd0JBQXdCLEdBQUcsQ0FB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
@@ -1,2 +1,4 @@
1
+ import * as smartstring from '@push.rocks/smartstring';
2
+ export { smartstring };
1
3
  import * as mt940Js from 'mt940-js';
2
4
  export { mt940Js };
@@ -1,3 +1,7 @@
1
+ // @push.rocks scope
2
+ import * as smartstring from '@push.rocks/smartstring';
3
+ export { smartstring };
4
+ // third party scope
1
5
  import * as mt940Js from 'mt940-js';
2
6
  export { mt940Js };
3
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibXQ5NDBwYXJzZXIucGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL210OTQwcGFyc2VyLnBsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxVQUFVLENBQUM7QUFFcEMsT0FBTyxFQUNMLE9BQU8sRUFDUixDQUFDIn0=
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibXQ5NDBwYXJzZXIucGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL210OTQwcGFyc2VyLnBsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFFdkQsT0FBTyxFQUFFLFdBQVcsRUFBRSxDQUFDO0FBRXZCLG9CQUFvQjtBQUNwQixPQUFPLEtBQUssT0FBTyxNQUFNLFVBQVUsQ0FBQztBQUVwQyxPQUFPLEVBQ0wsT0FBTyxFQUNSLENBQUMifQ==
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fin.cx/mt940",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "private": false,
5
5
  "description": "A parser to process and extract statements from MT940 format files, commonly used by banks for electronic account statements.",
6
6
  "main": "dist_ts/index.js",
@@ -9,11 +9,11 @@
9
9
  "author": "Task Venture Capital GmbH",
10
10
  "license": "MIT",
11
11
  "devDependencies": {
12
- "@git.zone/tsbuild": "^4.4.2",
13
- "@git.zone/tsbundle": "^2.10.1",
14
- "@git.zone/tsrun": "^2.0.5",
15
- "@git.zone/tstest": "^3.6.6",
16
- "@types/node": "^26.1.0"
12
+ "@git.zone/tsbuild": "^5.0.0",
13
+ "@git.zone/tsbundle": "^3.1.0",
14
+ "@git.zone/tsrun": "^3.0.2",
15
+ "@git.zone/tstest": "^6.3.2",
16
+ "@types/node": "^26.6.2"
17
17
  },
18
18
  "browserslist": [
19
19
  "last 1 chrome versions"
@@ -32,6 +32,7 @@
32
32
  "readme.md"
33
33
  ],
34
34
  "dependencies": {
35
+ "@push.rocks/smartstring": "^4.2.0",
35
36
  "mt940-js": "^1.0.0"
36
37
  },
37
38
  "keywords": [
package/readme.md CHANGED
@@ -14,8 +14,9 @@ pnpm add @fin.cx/mt940
14
14
 
15
15
  ## What It Does
16
16
 
17
+ - Parses the bytes of an MT940 file with `parseMt940FileBytes()`: UTF-8 where they are valid UTF-8, else Windows-1252.
17
18
  - Parses complete MT940 file content from a string with `parseMt940FileString()`.
18
- - Parses MT940 data from an `ArrayBufferLike` with `parseMt940Buffer()`.
19
+ - Parses MT940 data from an `ArrayBufferLike` with `parseMt940Buffer()`, as `parseMt940FileBytes()` does.
19
20
  - Returns the statement, balance, and transaction objects produced by `mt940-js`.
20
21
  - Normalizes BUNQ statement account ids by stripping the trailing currency segment from `accountId`.
21
22
  - Works with modern ESM TypeScript projects.
@@ -43,23 +44,23 @@ console.log(statements[0].transactions[0].amount); // 5
43
44
 
44
45
  ## Parsing File Data In Node.js
45
46
 
46
- `parseMt940Buffer()` expects an `ArrayBufferLike`. When reading with Node.js, slice the underlying `ArrayBuffer` to the actual byte range of the `Buffer`.
47
+ `parseMt940FileBytes()` takes the file's bytes as they are; a Node.js `Buffer` is a `Uint8Array`.
47
48
 
48
49
  ```typescript
49
50
  import { readFile } from 'node:fs/promises';
50
51
  import { Mt940Parser } from '@fin.cx/mt940';
51
52
 
52
53
  const parser = new Mt940Parser();
53
- const fileBuffer = await readFile('./statement.mt940');
54
- const arrayBuffer = fileBuffer.buffer.slice(
55
- fileBuffer.byteOffset,
56
- fileBuffer.byteOffset + fileBuffer.byteLength
57
- );
58
-
59
- const statements = await parser.parseMt940Buffer(arrayBuffer);
54
+ const statements = await parser.parseMt940FileBytes(await readFile('./statement.mt940'));
60
55
  console.log(statements);
61
56
  ```
62
57
 
58
+ ## Encodings
59
+
60
+ MT940 files carry no declaration of their encoding. German banks write them in Windows-1252; newer exports use UTF-8. `parseMt940FileBytes()` reads a file as UTF-8 when its bytes are valid UTF-8 (a byte order mark is not part of the text) and as Windows-1252 otherwise, by the WHATWG Encoding Standard's table through `@push.rocks/smartstring`, so `€`, `„`, `“` and `–` read the same on every runtime. `parseMt940Buffer()` does the same for an `ArrayBufferLike`; `parseMt940FileString()` takes text that is already decoded.
61
+
62
+ `mt940-js` reads one byte as one character. This module hands it the file's bytes and decodes every text it answers from those bytes, so umlauts are neither lost nor garbled, and a transaction's `id` stays the one `mt940-js` computes from the file's bytes: the same file read again yields the same ids.
63
+
63
64
  ## Parsing Browser File Uploads
64
65
 
65
66
  Browser `File` objects already expose `arrayBuffer()`, so uploaded MT940 files can be parsed without Node.js APIs.
@@ -70,7 +71,7 @@ import { Mt940Parser } from '@fin.cx/mt940';
70
71
  const parser = new Mt940Parser();
71
72
 
72
73
  async function parseUploadedMt940(file: File) {
73
- const statements = await parser.parseMt940Buffer(await file.arrayBuffer());
74
+ const statements = await parser.parseMt940FileBytes(new Uint8Array(await file.arrayBuffer()));
74
75
  return statements;
75
76
  }
76
77
  ```
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@fin.cx/mt940',
6
- version: '2.0.0',
6
+ version: '2.1.0',
7
7
  description: 'A parser to process and extract statements from MT940 format files, commonly used by banks for electronic account statements.'
8
8
  }
package/ts/index.ts CHANGED
@@ -1,18 +1,165 @@
1
1
  import * as plugins from './mt940parser.plugins.js';
2
2
 
3
+ /** The encoding an MT940 file is read in: UTF-8 where its bytes are valid UTF-8, else Windows-1252. */
4
+ export type TMt940Encoding = 'utf-8' | 'windows-1252';
5
+
6
+ const UTF8_BYTE_ORDER_MARK: readonly number[] = [0xef, 0xbb, 0xbf];
7
+
8
+ const startsWithUtf8ByteOrderMark = (bytesArg: Uint8Array): boolean =>
9
+ UTF8_BYTE_ORDER_MARK.every((byteArg, indexArg) => bytesArg[indexArg] === byteArg);
10
+
11
+ /** The byte of the no-break space in Windows-1252, and of a UTF-8 continuation byte a character may end in (à is C3 A0). */
12
+ const NO_BREAK_SPACE_BYTE = 0xa0;
13
+
14
+ /**
15
+ * The bytes a field of mt940-js holds. mt940-js reads its input one byte as
16
+ * one character (`String.fromCharCode` of each byte), so each code unit of a
17
+ * field it answers is one byte of the file.
18
+ */
19
+ const bytesOfField = (fieldArg: string): Uint8Array => {
20
+ const bytes = new Uint8Array(fieldArg.length);
21
+ for (let index = 0; index < fieldArg.length; index++) {
22
+ const codeUnit = fieldArg.charCodeAt(index);
23
+ if (codeUnit > 0xff) {
24
+ throw new Error(`mt940-js answered a character that is no byte (U+${codeUnit.toString(16)})`);
25
+ }
26
+ bytes[index] = codeUnit;
27
+ }
28
+ return bytes;
29
+ };
30
+
31
+ /** How many continuation bytes the UTF-8 sequence at the end of `bytesArg` still lacks (0 when it is complete). */
32
+ const missingContinuationBytes = (bytesArg: Uint8Array): number => {
33
+ for (let back = 1; back <= Math.min(3, bytesArg.length); back++) {
34
+ const byte = bytesArg[bytesArg.length - back];
35
+ if (byte < 0x80) {
36
+ return 0;
37
+ }
38
+ if (byte >= 0xc0) {
39
+ const length = byte >= 0xf0 ? 4 : byte >= 0xe0 ? 3 : 2;
40
+ return Math.max(0, length - back);
41
+ }
42
+ }
43
+ return 0;
44
+ };
45
+
46
+ const decodeField = (bytesArg: Uint8Array, encodingArg: TMt940Encoding): string =>
47
+ encodingArg === 'utf-8' ? new TextDecoder('utf-8').decode(bytesArg) : plugins.smartstring.encoding.decodeWindows1252(bytesArg);
48
+
49
+ /**
50
+ * A text of an :86: field, which mt940-js trims as it reads it byte by byte:
51
+ * JavaScript's `trim` takes a no-break space (0xA0) off its end, and in a
52
+ * file of UTF-8 0xA0 is also the last byte of characters (à is C3 A0, Š C5
53
+ * A0, † E2 80 A0). No other byte from 0x80 up is whitespace to `trim`, so the
54
+ * bytes the sequence at the end lacks were each 0xA0 and are put back; what
55
+ * was trimmed after that character was whitespace.
56
+ */
57
+ const decodeTrimmedField = (fieldArg: string, encodingArg: TMt940Encoding): string => {
58
+ const bytes = bytesOfField(fieldArg);
59
+ if (encodingArg !== 'utf-8') {
60
+ return decodeField(bytes, encodingArg);
61
+ }
62
+ const missing = missingContinuationBytes(bytes);
63
+ if (missing === 0) {
64
+ return decodeField(bytes, encodingArg);
65
+ }
66
+ const completed = new Uint8Array(bytes.length + missing);
67
+ completed.set(bytes);
68
+ completed.fill(NO_BREAK_SPACE_BYTE, bytes.length);
69
+ return decodeField(completed, encodingArg);
70
+ };
71
+
72
+ /*
73
+ * mt940-js does not set every field its types name: a :61: line without a
74
+ * transaction code answers `code` undefined. A field that is no string is
75
+ * passed through as mt940-js answered it; a string is decoded from its bytes.
76
+ */
77
+
78
+ /** A field of mt940-js in the file's encoding. */
79
+ const decodeText = <T>(valueArg: T, encodingArg: TMt940Encoding): T =>
80
+ typeof valueArg === 'string' ? (decodeField(bytesOfField(valueArg), encodingArg) as T) : valueArg;
81
+
82
+ /** An :86: text of mt940-js in the file's encoding (`decodeTrimmedField`). */
83
+ const decodeTrimmedText = <T>(valueArg: T, encodingArg: TMt940Encoding): T =>
84
+ typeof valueArg === 'string' ? (decodeTrimmedField(valueArg, encodingArg) as T) : valueArg;
85
+
86
+ const decodeBalance = (balanceArg: plugins.mt940Js.BalanceInfo, encodingArg: TMt940Encoding): plugins.mt940Js.BalanceInfo => ({
87
+ ...balanceArg,
88
+ date: decodeText(balanceArg.date, encodingArg),
89
+ currency: decodeText(balanceArg.currency, encodingArg),
90
+ });
91
+
92
+ /** A statement mt940-js read, with every text decoded from its bytes in the file's encoding. */
93
+ const decodeStatement = (statementArg: plugins.mt940Js.Statement, encodingArg: TMt940Encoding): plugins.mt940Js.Statement => {
94
+ const text = <T>(valueArg: T): T => decodeText(valueArg, encodingArg);
95
+ const {
96
+ referenceNumber,
97
+ relatedReferenceNumber,
98
+ accountId,
99
+ number,
100
+ additionalInformation,
101
+ openingBalance,
102
+ closingBalance,
103
+ closingAvailableBalance,
104
+ forwardAvailableBalance,
105
+ } = statementArg;
106
+ return {
107
+ ...statementArg,
108
+ ...(referenceNumber !== undefined ? { referenceNumber: text(referenceNumber) } : {}),
109
+ ...(relatedReferenceNumber !== undefined ? { relatedReferenceNumber: text(relatedReferenceNumber) } : {}),
110
+ ...(accountId !== undefined ? { accountId: text(accountId) } : {}),
111
+ ...(number !== undefined ? { number: text(number) } : {}),
112
+ ...(additionalInformation !== undefined ? { additionalInformation: decodeTrimmedText(additionalInformation, encodingArg) } : {}),
113
+ ...(openingBalance ? { openingBalance: decodeBalance(openingBalance, encodingArg) } : {}),
114
+ ...(closingBalance ? { closingBalance: decodeBalance(closingBalance, encodingArg) } : {}),
115
+ ...(closingAvailableBalance ? { closingAvailableBalance: decodeBalance(closingAvailableBalance, encodingArg) } : {}),
116
+ ...(forwardAvailableBalance ? { forwardAvailableBalance: decodeBalance(forwardAvailableBalance, encodingArg) } : {}),
117
+ transactions: statementArg.transactions.map((transactionArg) => ({
118
+ ...transactionArg,
119
+ code: text(transactionArg.code),
120
+ fundsCode: text(transactionArg.fundsCode),
121
+ currency: text(transactionArg.currency),
122
+ description: decodeTrimmedText(transactionArg.description, encodingArg),
123
+ valueDate: text(transactionArg.valueDate),
124
+ entryDate: text(transactionArg.entryDate),
125
+ customerReference: text(transactionArg.customerReference),
126
+ bankReference: text(transactionArg.bankReference),
127
+ })),
128
+ };
129
+ };
130
+
3
131
  export class Mt940Parser {
132
+ /**
133
+ * Parses MT940 file content given as text. Its characters are read as they
134
+ * are, whatever encoding the file was in before it became text.
135
+ */
4
136
  public async parseMt940FileString(fileStringArg: string) {
5
- const textEncode = new TextEncoder();
6
- const statements = await this.parseMt940Buffer(textEncode.encode(fileStringArg).buffer);
137
+ return this.parseBytesIn(new TextEncoder().encode(fileStringArg), 'utf-8');
138
+ }
7
139
 
8
- return statements;
140
+ /**
141
+ * Parses the bytes of an MT940 file: UTF-8 where they are valid UTF-8 (a
142
+ * byte order mark is not part of the text), else Windows-1252, the
143
+ * encoding German banks write MT940 in. Every field is decoded from its own
144
+ * bytes, by the Encoding Standard's table for Windows-1252 on every
145
+ * runtime (`@push.rocks/smartstring`).
146
+ */
147
+ public async parseMt940FileBytes(fileBytesArg: Uint8Array) {
148
+ const encoding: TMt940Encoding = plugins.smartstring.encoding.isValidUtf8(fileBytesArg) ? 'utf-8' : 'windows-1252';
149
+ return this.parseBytesIn(fileBytesArg, encoding);
9
150
  }
10
151
 
152
+ /** Parses the bytes of an MT940 file in an `ArrayBufferLike`, as `parseMt940FileBytes` does. */
11
153
  public async parseMt940Buffer(fileBufferArg: ArrayBufferLike) {
12
- const fileBuffer = fileBufferArg instanceof ArrayBuffer
13
- ? fileBufferArg
14
- : new Uint8Array(fileBufferArg).slice().buffer as ArrayBuffer;
15
- const statements = await plugins.mt940Js.read(fileBuffer);
154
+ return this.parseMt940FileBytes(new Uint8Array(fileBufferArg));
155
+ }
156
+
157
+ private async parseBytesIn(fileBytesArg: Uint8Array, encodingArg: TMt940Encoding) {
158
+ // a byte order mark is no text of the file, and mt940-js finds no statement behind one
159
+ const text = encodingArg === 'utf-8' && startsWithUtf8ByteOrderMark(fileBytesArg) ? fileBytesArg.subarray(UTF8_BYTE_ORDER_MARK.length) : fileBytesArg;
160
+ // mt940-js takes an ArrayBuffer of its own; it reads each byte as one character
161
+ const fileBuffer = text.slice().buffer as ArrayBuffer;
162
+ const statements = (await plugins.mt940Js.read(fileBuffer)).map((statementArg) => decodeStatement(statementArg, encodingArg));
16
163
 
17
164
  // bank afterburner
18
165
  for (const statement of statements) {
@@ -1,3 +1,9 @@
1
+ // @push.rocks scope
2
+ import * as smartstring from '@push.rocks/smartstring';
3
+
4
+ export { smartstring };
5
+
6
+ // third party scope
1
7
  import * as mt940Js from 'mt940-js';
2
8
 
3
9
  export {
package/readme.hints.md DELETED
File without changes