reverse-schema 0.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.
- package/LICENSE +21 -0
- package/README.md +85 -0
- package/package.json +33 -0
- package/src/index.d.ts +22 -0
- package/src/index.js +112 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Reverse Schema
|
|
2
|
+
|
|
3
|
+
Reverse Schema is a small Node.js library for parsing text lines with an ordered array of regular expression fragments. It is designed for cases where a line has a known shape, such as MT940 statement lines, fixed-width data, CSV-like rows, or other text formats.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
For local React app development, install this package from its folder:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install ../ReverseSchema
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { parseText } from 'reverse-schema';
|
|
17
|
+
|
|
18
|
+
const schema = [
|
|
19
|
+
{ pattern: ':61:', capture: false },
|
|
20
|
+
{ name: 'valueDate', pattern: '\\d{6}' },
|
|
21
|
+
{ name: 'entryDate', pattern: '\\d{4}' },
|
|
22
|
+
{ name: 'dcMark', pattern: '[DC]' },
|
|
23
|
+
{ name: 'amount', pattern: '\\d+,\\d{0,2}' },
|
|
24
|
+
{ name: 'transactionType', pattern: '[A-Z0-9]{4}' },
|
|
25
|
+
{ name: 'customerReference', pattern: '[^/]+' },
|
|
26
|
+
{ pattern: '//', capture: false },
|
|
27
|
+
{ name: 'bankReference', pattern: '.+' }
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const text = ':61:2502180218D12,01NTRFGSLNVSHSUTKWDR//GI2504900007841';
|
|
31
|
+
|
|
32
|
+
const rows = parseText(schema, text);
|
|
33
|
+
|
|
34
|
+
console.log(rows);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Output:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
[
|
|
41
|
+
{
|
|
42
|
+
valueDate: '250218',
|
|
43
|
+
entryDate: '0218',
|
|
44
|
+
dcMark: 'D',
|
|
45
|
+
amount: '12,01',
|
|
46
|
+
transactionType: 'NTRF',
|
|
47
|
+
customerReference: 'GSLNVSHSUTKWDR',
|
|
48
|
+
bankReference: 'GI2504900007841'
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## API
|
|
54
|
+
|
|
55
|
+
### `parseText(schema, text)`
|
|
56
|
+
|
|
57
|
+
Parses a string line by line. Each line is checked against the full generated regex. Nonmatching lines are ignored. Matching lines return one plain JavaScript object each.
|
|
58
|
+
|
|
59
|
+
### `parseLine(schemaOrCompiled, line)`
|
|
60
|
+
|
|
61
|
+
Parses one line and returns a plain JavaScript object, or `null` if the line does not match.
|
|
62
|
+
|
|
63
|
+
### `compileSchema(schema)`
|
|
64
|
+
|
|
65
|
+
Compiles a schema once and returns `{ regex, source, capturedNames }`. Use this when parsing many individual lines with the same schema.
|
|
66
|
+
|
|
67
|
+
## Schema Items
|
|
68
|
+
|
|
69
|
+
Each schema item supports:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
{
|
|
73
|
+
name?: string;
|
|
74
|
+
pattern: string;
|
|
75
|
+
capture?: boolean;
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`pattern` is a JavaScript regular expression fragment. Patterns are concatenated in order and anchored as `^{patterns}$`.
|
|
80
|
+
|
|
81
|
+
When `capture` is omitted or `true`, the item must have a `name`. Its pattern is wrapped in a named capture group and included in output.
|
|
82
|
+
|
|
83
|
+
When `capture` is `false`, the pattern still participates in matching but is omitted from output. This is useful for tags, delimiters, and other structural text.
|
|
84
|
+
|
|
85
|
+
All captured values are returned as strings. No date, number, or amount conversion is performed.
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "reverse-schema",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Parse text lines using ordered regex schema fragments.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"import": "./src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"parser",
|
|
24
|
+
"regex",
|
|
25
|
+
"schema",
|
|
26
|
+
"mt940"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface ReverseSchemaItem {
|
|
2
|
+
name?: string;
|
|
3
|
+
pattern: string;
|
|
4
|
+
capture?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CompiledReverseSchema {
|
|
8
|
+
regex: RegExp;
|
|
9
|
+
source: string;
|
|
10
|
+
capturedNames: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ReverseSchemaError extends Error {}
|
|
14
|
+
|
|
15
|
+
export function compileSchema(schema: ReverseSchemaItem[]): CompiledReverseSchema;
|
|
16
|
+
|
|
17
|
+
export function parseLine(
|
|
18
|
+
schemaOrCompiled: ReverseSchemaItem[] | CompiledReverseSchema,
|
|
19
|
+
line: string
|
|
20
|
+
): Record<string, string> | null;
|
|
21
|
+
|
|
22
|
+
export function parseText(schema: ReverseSchemaItem[], text: string): Array<Record<string, string>>;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const VALID_GROUP_NAME = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
2
|
+
|
|
3
|
+
export class ReverseSchemaError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'ReverseSchemaError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function compileSchema(schema) {
|
|
11
|
+
validateSchemaArray(schema);
|
|
12
|
+
|
|
13
|
+
const capturedNames = [];
|
|
14
|
+
const seenNames = new Set();
|
|
15
|
+
const fragments = schema.map((item, index) => {
|
|
16
|
+
validateSchemaItem(item, index);
|
|
17
|
+
|
|
18
|
+
if (item.capture === false) {
|
|
19
|
+
return `(?:${item.pattern})`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!VALID_GROUP_NAME.test(item.name)) {
|
|
23
|
+
throw new ReverseSchemaError(
|
|
24
|
+
`Schema item at index ${index} has invalid capture name "${item.name}". Names must be valid JavaScript RegExp group names.`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (seenNames.has(item.name)) {
|
|
29
|
+
throw new ReverseSchemaError(`Duplicate capture name "${item.name}" at schema index ${index}.`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
seenNames.add(item.name);
|
|
33
|
+
capturedNames.push(item.name);
|
|
34
|
+
return `(?<${item.name}>${item.pattern})`;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const source = `^${fragments.join('')}$`;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
return {
|
|
41
|
+
regex: new RegExp(source),
|
|
42
|
+
source,
|
|
43
|
+
capturedNames
|
|
44
|
+
};
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new ReverseSchemaError(`Generated regex is invalid: ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function parseLine(schemaOrCompiled, line) {
|
|
51
|
+
const compiled = isCompiledSchema(schemaOrCompiled) ? schemaOrCompiled : compileSchema(schemaOrCompiled);
|
|
52
|
+
const match = compiled.regex.exec(String(line));
|
|
53
|
+
|
|
54
|
+
if (!match) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const output = {};
|
|
59
|
+
for (const name of compiled.capturedNames) {
|
|
60
|
+
output[name] = match.groups?.[name] ?? '';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return output;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function parseText(schema, text) {
|
|
67
|
+
const compiled = compileSchema(schema);
|
|
68
|
+
const lines = String(text).split(/\r\n|\n|\r/);
|
|
69
|
+
const results = [];
|
|
70
|
+
|
|
71
|
+
for (const line of lines) {
|
|
72
|
+
const parsed = parseLine(compiled, line);
|
|
73
|
+
if (parsed !== null) {
|
|
74
|
+
results.push(parsed);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return results;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function validateSchemaArray(schema) {
|
|
82
|
+
if (!Array.isArray(schema)) {
|
|
83
|
+
throw new ReverseSchemaError('Schema must be an array.');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function validateSchemaItem(item, index) {
|
|
88
|
+
if (item === null || typeof item !== 'object' || Array.isArray(item)) {
|
|
89
|
+
throw new ReverseSchemaError(`Schema item at index ${index} must be an object.`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (typeof item.pattern !== 'string') {
|
|
93
|
+
throw new ReverseSchemaError(`Schema item at index ${index} must have a string pattern.`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if ('capture' in item && typeof item.capture !== 'boolean') {
|
|
97
|
+
throw new ReverseSchemaError(`Schema item at index ${index} must use a boolean capture value.`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (item.capture !== false && typeof item.name !== 'string') {
|
|
101
|
+
throw new ReverseSchemaError(`Schema item at index ${index} must have a string name unless capture is false.`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isCompiledSchema(value) {
|
|
106
|
+
return (
|
|
107
|
+
value !== null &&
|
|
108
|
+
typeof value === 'object' &&
|
|
109
|
+
value.regex instanceof RegExp &&
|
|
110
|
+
Array.isArray(value.capturedNames)
|
|
111
|
+
);
|
|
112
|
+
}
|