@nodable/flexible-xml-parser 1.0.0 → 1.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/README.md +129 -210
- package/lib/fxp.d.cts +4 -4
- package/package.json +5 -4
- package/src/ParseError.js +24 -24
- package/src/StopNodeProcessor.js +1 -1
- package/src/fxp.d.ts +4 -4
package/README.md
CHANGED
|
@@ -1,284 +1,203 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nodable/flexible-xml-parser
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A high-performance, flexible XML parser in pure javascript for Node.js and browsers with pluggable output builders, composable value parsers, and multiple input modes.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> From the creater of fast-xml-parser
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
## Benefits over fast-xml-parser?
|
|
8
|
+
|
|
9
|
+
| Feature | fast-xml-parser | flexible-xml-parser |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Output format | Fixed JS object | Pluggable (compact, sequential, node-tree, custom) |
|
|
12
|
+
| Value parsing | Inline options | Separate, composable pipeline per output builder |
|
|
13
|
+
| Value parsers for tags vs attrs | Single config | Independent chains |
|
|
14
|
+
| Input modes | String / Buffer | String, Buffer, Uint8Array, Stream, Feed/End |
|
|
15
|
+
| Stop node enclosures | Limited | Per-node `skipEnclosures` control |
|
|
16
|
+
| Exit | After complete processing | Allow partial parsing |
|
|
17
|
+
| Lenient HTML mode | No | `autoClose` with error collection |
|
|
18
|
+
| Custom output | No | Extend `BaseOutputBuilder` |
|
|
19
|
+
|
|
20
|
+
The core parser is intentionally minimal. Options like `transformTagName`, `alwaysArray`, `forceTextNode`, and value parser configuration live in the **output builder**, not in `XMLParser`. This keeps the parser lean and lets you mix builders without changing your parsing code.
|
|
21
|
+
|
|
22
|
+
### Performance
|
|
23
|
+
|
|
24
|
+
fast-xml-parser doesn't support streams, while flexible-xml-parser does. This makes flexible-xml-parser more memory efficient for large XML files.
|
|
25
|
+
|
|
26
|
+
Additionally, flexible-xml-parser is considerably faster than fast-xml-parser. Checkout [benchmarks](https://github.com/nodable/flexible-xml-parser) for more details.
|
|
27
|
+
|
|
28
|
+
## Package Ecosystem
|
|
29
|
+
|
|
30
|
+
`@nodable/flexible-xml-parser` is the core parser. Output builders are published separately so you only install what you need:
|
|
31
|
+
|
|
32
|
+
| Package | Description |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `@nodable/flexible-xml-parser` | Core parser (this package) |
|
|
35
|
+
| `@nodable/base-output-builder` | Base class + value parsers (`ElementType`, entity parsers) |
|
|
36
|
+
| `@nodable/compact-builder` | Default JS-object output (like fast-xml-parser) |
|
|
37
|
+
| `@nodable/sequential-builder` | Ordered key-value array output |
|
|
38
|
+
| `@nodable/sequential-stream-builder` | Sequential builder with streaming output |
|
|
39
|
+
| `@nodable/node-tree-builder` | Uniform AST-style node tree |
|
|
17
40
|
|
|
18
41
|
## Installation
|
|
19
42
|
|
|
20
43
|
```bash
|
|
21
|
-
npm install flexible-xml-parser
|
|
44
|
+
npm install @nodable/flexible-xml-parser @nodable/compact-builder
|
|
22
45
|
```
|
|
23
46
|
|
|
47
|
+
Install additional builders only as needed.
|
|
48
|
+
|
|
24
49
|
## Quick Start
|
|
25
50
|
|
|
26
51
|
```javascript
|
|
27
|
-
import XMLParser from 'flexible-xml-parser';
|
|
52
|
+
import XMLParser from '@nodable/flexible-xml-parser';
|
|
53
|
+
import { CompactBuilderFactory } from '@nodable/compact-builder';
|
|
28
54
|
|
|
55
|
+
// Default output (uses CompactBuilder internally)
|
|
29
56
|
const parser = new XMLParser();
|
|
30
57
|
const result = parser.parse('<root><count>3</count><active>true</active></root>');
|
|
31
58
|
// { root: { count: 3, active: true } }
|
|
32
59
|
|
|
33
|
-
//
|
|
60
|
+
// With attributes
|
|
34
61
|
const parser2 = new XMLParser({ skip: { attributes: false } });
|
|
35
62
|
parser2.parse('<item id="1">hello</item>');
|
|
36
63
|
// { item: { '@_id': 1, '#text': 'hello' } }
|
|
37
64
|
```
|
|
38
65
|
|
|
39
|
-
## Input modes
|
|
40
|
-
|
|
41
|
-
```javascript
|
|
42
|
-
// String or Buffer
|
|
43
|
-
parser.parse('<root/>');
|
|
44
|
-
parser.parse(Buffer.from('<root/>'));
|
|
45
|
-
|
|
46
|
-
// Typed array
|
|
47
|
-
parser.parseBytesArr(new Uint8Array([...]));
|
|
48
|
-
|
|
49
|
-
// Node.js Readable stream — memory stays proportional to the largest token,
|
|
50
|
-
// not the total document size
|
|
51
|
-
const result = await parser.parseStream(fs.createReadStream('large.xml'));
|
|
52
|
-
|
|
53
|
-
// Incremental feed — useful for WebSocket / chunked HTTP
|
|
54
|
-
parser.feed('<root>');
|
|
55
|
-
parser.feed('<item>1</item>');
|
|
56
|
-
const result = parser.end();
|
|
57
|
-
```
|
|
58
|
-
|
|
59
66
|
## Options
|
|
60
67
|
|
|
68
|
+
All options are optional. Pass only what you need.
|
|
69
|
+
|
|
61
70
|
```javascript
|
|
62
71
|
new XMLParser({
|
|
63
|
-
// What to
|
|
72
|
+
// What to skip
|
|
64
73
|
skip: {
|
|
65
|
-
attributes: true, //
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
attributes: true, // Skip all attributes
|
|
75
|
+
declaration: false, // Skip <?xml ...?> declaration
|
|
76
|
+
pi: false, // Skip <?...?> processing instructions
|
|
77
|
+
cdata: false, // Exclude CDATA from output entirely
|
|
78
|
+
comment: false, // Exclude comments from output entirely
|
|
79
|
+
nsPrefix: false, // Strip namespace prefixes (ns:tag → tag)
|
|
80
|
+
tags: [], // Tag paths to drop silently from output
|
|
71
81
|
},
|
|
72
82
|
|
|
73
83
|
// Property names for special nodes
|
|
74
84
|
nameFor: {
|
|
75
85
|
text: '#text', // mixed-content text property
|
|
76
|
-
cdata: '', // '' = merge
|
|
86
|
+
cdata: '', // '' = merge into text; '#cdata' = separate key
|
|
77
87
|
comment: '', // '' = omit; '#comment' = capture
|
|
78
88
|
},
|
|
79
89
|
|
|
80
90
|
// Attribute representation
|
|
81
91
|
attributes: {
|
|
82
|
-
prefix:
|
|
83
|
-
suffix:
|
|
84
|
-
groupBy:
|
|
85
|
-
booleanType:
|
|
86
|
-
valueParsers: ['entity', 'number', 'boolean'],
|
|
92
|
+
prefix: '@_',
|
|
93
|
+
suffix: '',
|
|
94
|
+
groupBy: '', // group all attributes under this key; '' = inline
|
|
95
|
+
booleanType: false, // allow valueless attributes (treated as true)
|
|
87
96
|
},
|
|
88
97
|
|
|
89
|
-
// Tag
|
|
98
|
+
// Tag options
|
|
90
99
|
tags: {
|
|
91
|
-
unpaired:
|
|
92
|
-
stopNodes:
|
|
93
|
-
valueParsers: ['entity', 'number', 'boolean'],
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
numberParseOptions: { hex: true, leadingZeros: true, eNotation: true },
|
|
97
|
-
|
|
98
|
-
// Entity sources and security limits
|
|
99
|
-
entityParseOptions: {
|
|
100
|
-
default: true, // built-in XML entities (lt, gt, amp, …)
|
|
101
|
-
html: false, // HTML named entities ( , ©, …)
|
|
102
|
-
external: true, // entities added via parser.addEntity()
|
|
103
|
-
docType: false, // entities declared in DOCTYPE internal subset
|
|
104
|
-
maxEntityCount: 100,
|
|
105
|
-
maxEntitySize: 10000,
|
|
106
|
-
maxTotalExpansions: 1000,
|
|
107
|
-
maxExpandedLength: 100000,
|
|
100
|
+
unpaired: [], // self-closing tags without / (e.g. ['br', 'img'])
|
|
101
|
+
stopNodes: [], // paths whose content is captured raw (see docs/04-stop-nodes.md)
|
|
108
102
|
},
|
|
109
103
|
|
|
110
104
|
// DoS prevention
|
|
111
105
|
limits: {
|
|
112
|
-
maxNestedTags: null,
|
|
113
|
-
maxAttributesPerTag: null,
|
|
106
|
+
maxNestedTags: null,
|
|
107
|
+
maxAttributesPerTag: null,
|
|
114
108
|
},
|
|
115
109
|
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
## Value parsers
|
|
125
|
-
|
|
126
|
-
Built-in chain names: `'entity'`, `'number'`, `'boolean'`, `'trim'`, `'currency'`.
|
|
110
|
+
// DOCTYPE entity expansion
|
|
111
|
+
doctypeOptions: {
|
|
112
|
+
enabled: false,
|
|
113
|
+
maxEntityCount: 100,
|
|
114
|
+
maxEntitySize: 10000,
|
|
115
|
+
},
|
|
127
116
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
117
|
+
// Security
|
|
118
|
+
strictReservedNames: false,
|
|
119
|
+
onDangerousProperty: defaultOnDangerousProperty,
|
|
131
120
|
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
tags: { valueParsers: ['entity', 'trim', 'number', 'boolean'] },
|
|
135
|
-
entityParseOptions: { html: true },
|
|
136
|
-
});
|
|
121
|
+
// Stop parsing early based on a condition
|
|
122
|
+
exitIf: null,
|
|
137
123
|
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
124
|
+
// Buffer settings for feed/stream modes
|
|
125
|
+
feedable: {
|
|
126
|
+
maxBufferSize: 10 * 1024 * 1024,
|
|
127
|
+
autoFlush: true,
|
|
128
|
+
flushThreshold: 1024,
|
|
129
|
+
},
|
|
143
130
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
parse(val, context) {
|
|
147
|
-
return context.elementName === 'price' ? parseFloat(val) : val;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
131
|
+
// Lenient HTML-mode recovery
|
|
132
|
+
autoClose: null, // null = strict; 'html' = recover from unclosed/mismatched tags
|
|
150
133
|
|
|
151
|
-
|
|
152
|
-
|
|
134
|
+
// Pluggable output builder
|
|
135
|
+
OutputBuilder: null, // default: CompactBuilder
|
|
153
136
|
});
|
|
154
137
|
```
|
|
155
138
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
```javascript
|
|
159
|
-
import { CompactObjBuilder } from 'flexible-xml-parser';
|
|
160
|
-
|
|
161
|
-
const builder = new CompactObjBuilder();
|
|
162
|
-
builder.registerValueParser('price', new PriceParser());
|
|
163
|
-
|
|
164
|
-
new XMLParser({
|
|
165
|
-
tags: { valueParsers: ['entity', 'price', 'boolean'] },
|
|
166
|
-
OutputBuilder: builder,
|
|
167
|
-
});
|
|
168
|
-
```
|
|
139
|
+
## Value Parsers
|
|
169
140
|
|
|
170
|
-
|
|
141
|
+
Value parsers are configured on the **output builder**, not on `XMLParser`. This lets you set independent pipelines for tag text and attribute values.
|
|
171
142
|
|
|
172
|
-
|
|
143
|
+
Built-in parsers: `'entity'`, `'number'`, `'boolean'`, `'trim'`, `'currency'`.
|
|
173
144
|
|
|
174
145
|
```javascript
|
|
175
|
-
import {
|
|
146
|
+
import { CompactBuilderFactory } from '@nodable/compact-builder';
|
|
176
147
|
|
|
177
|
-
new
|
|
178
|
-
tags:
|
|
179
|
-
|
|
180
|
-
'..script', // plain — first </script> ends collection
|
|
181
|
-
{ expression: 'body..pre', skipEnclosures: [...xmlEnclosures] },
|
|
182
|
-
{ expression: 'head..style', skipEnclosures: [...xmlEnclosures, ...quoteEnclosures] },
|
|
183
|
-
],
|
|
184
|
-
},
|
|
185
|
-
onStopNode(tagDetail, rawContent, matcher) {
|
|
186
|
-
console.log(tagDetail.name, rawContent);
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
`xmlEnclosures` covers XML comments and CDATA; `quoteEnclosures` covers single-quote, double-quote, and template literals.
|
|
192
|
-
|
|
193
|
-
## Pluggable output builders
|
|
194
|
-
|
|
195
|
-
```javascript
|
|
196
|
-
import XMLParser, { CompactObjBuilder, BaseOutputBuilder, ElementType } from 'flexible-xml-parser';
|
|
197
|
-
|
|
198
|
-
// CompactObjBuilder — default JS object output with extra options
|
|
199
|
-
const builder = new CompactObjBuilder({
|
|
200
|
-
alwaysArray: ['item'], // tag names or path expressions always wrapped in []
|
|
201
|
-
forceArray: (matcher) => ..., // function-based array forcing
|
|
202
|
-
forceTextNode: false, // always emit nameFor.text even for text-only tags
|
|
203
|
-
textJoint: '', // join string when text spans multiple text nodes
|
|
148
|
+
const builder = new CompactBuilderFactory({
|
|
149
|
+
tags: { valueParsers: ['entity', 'boolean', 'number'] },
|
|
150
|
+
attributes: { valueParsers: ['entity', 'number', 'boolean'] },
|
|
204
151
|
});
|
|
205
152
|
|
|
206
|
-
new XMLParser({ OutputBuilder: builder });
|
|
207
|
-
|
|
208
|
-
// Custom builder by extending BaseOutputBuilder
|
|
209
|
-
class MyBuilder extends BaseOutputBuilder {
|
|
210
|
-
addElement(tag, matcher) { /* … */ }
|
|
211
|
-
closeElement(matcher) { /* … */ }
|
|
212
|
-
addValue(text, matcher) { /* … */ }
|
|
213
|
-
getOutput() { return this.result; }
|
|
214
|
-
}
|
|
153
|
+
const parser = new XMLParser({ OutputBuilder: builder });
|
|
215
154
|
```
|
|
216
155
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
```javascript
|
|
220
|
-
// 'html' preset: recover from unclosed tags and mismatched close tags
|
|
221
|
-
const parser = new XMLParser({ autoClose: 'html' });
|
|
222
|
-
const result = parser.parse('<div><p>text<br></div>');
|
|
223
|
-
|
|
224
|
-
const errors = parser.getParseErrors();
|
|
225
|
-
// [{ type: 'unclosed-eof', tag: 'p', line: 1, col: … }, …]
|
|
226
|
-
```
|
|
156
|
+
See [`docs/03-value-parsers.md`](./docs/03-value-parsers.md) for the full pipeline reference and custom parser guide.
|
|
227
157
|
|
|
228
|
-
|
|
158
|
+
## Input Modes
|
|
229
159
|
|
|
230
160
|
```javascript
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
onMismatch: 'recover', // 'throw' | 'recover' | 'discard'
|
|
235
|
-
collectErrors: true,
|
|
236
|
-
},
|
|
237
|
-
});
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
## Error handling
|
|
241
|
-
|
|
242
|
-
```javascript
|
|
243
|
-
import XMLParser, { ParseError, ErrorCode } from 'flexible-xml-parser';
|
|
244
|
-
|
|
245
|
-
try {
|
|
246
|
-
parser.parse(xml);
|
|
247
|
-
} catch (e) {
|
|
248
|
-
if (e instanceof ParseError) {
|
|
249
|
-
console.error(e.code, e.line, e.col, e.message);
|
|
250
|
-
// e.g. 'MISMATCHED_CLOSE_TAG' 4 12 'Expected </div>, got </span>'
|
|
251
|
-
} else {
|
|
252
|
-
throw e;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
```
|
|
161
|
+
// String or Buffer
|
|
162
|
+
parser.parse('<root/>');
|
|
163
|
+
parser.parse(Buffer.from('<root/>'));
|
|
256
164
|
|
|
257
|
-
|
|
165
|
+
// Typed array
|
|
166
|
+
parser.parseBytesArr(new Uint8Array([...]));
|
|
258
167
|
|
|
259
|
-
|
|
168
|
+
// Node.js Readable stream
|
|
169
|
+
const result = await parser.parseStream(fs.createReadStream('large.xml'));
|
|
260
170
|
|
|
261
|
-
|
|
262
|
-
parser.
|
|
263
|
-
parser.
|
|
264
|
-
|
|
171
|
+
// Incremental feed (WebSocket, chunked HTTP, etc.)
|
|
172
|
+
parser.feed('<root>');
|
|
173
|
+
parser.feed('<item>1</item>');
|
|
174
|
+
const result = parser.end();
|
|
265
175
|
```
|
|
266
176
|
|
|
267
|
-
##
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
177
|
+
## Possible Usage
|
|
178
|
+
|
|
179
|
+
- Parse XML config files, SOAP responses, RSS/Atom feeds
|
|
180
|
+
- Stream-parse large XML files with bounded memory
|
|
181
|
+
- Build custom AST-style output with `NodeTreeBuilder`
|
|
182
|
+
- Lenient HTML-fragment parsing with `autoClose`
|
|
183
|
+
- Stop-node capture for `<script>`, `<style>`, embedded HTML
|
|
184
|
+
- Extend `BaseOutputBuilder` to write parsed data directly to a database
|
|
185
|
+
|
|
186
|
+
## Documentation
|
|
187
|
+
|
|
188
|
+
| File | Topic |
|
|
189
|
+
|---|---|
|
|
190
|
+
| [`docs/01-getting-started.md`](./docs/01-getting-started.md) | Installation, quick start, common patterns |
|
|
191
|
+
| [`docs/02-options.md`](./docs/02-options.md) | Full options reference |
|
|
192
|
+
| [`docs/03-value-parsers.md`](./docs/03-value-parsers.md) | Value parser pipeline, built-ins, custom parsers |
|
|
193
|
+
| [`docs/04-stop-nodes.md`](./docs/04-stop-nodes.md) | Stop nodes and skip tags |
|
|
194
|
+
| [`docs/05-output-builders.md`](./docs/05-output-builders.md) | Built-in and custom output builders |
|
|
195
|
+
| [`docs/06-streaming.md`](./docs/06-streaming.md) | Stream, feed/end, and memory characteristics |
|
|
196
|
+
| [`docs/07-auto-close.md`](./docs/07-auto-close.md) | Lenient HTML parsing and error collection |
|
|
197
|
+
| [`docs/08-security.md`](./docs/08-security.md) | Security, DoS limits, prototype pollution |
|
|
198
|
+
| [`docs/09-path-expressions.md`](./docs/09-path-expressions.md) | Path expression syntax for stop nodes, skip, exitIf |
|
|
199
|
+
| [`docs/10-typescript.md`](./docs/10-typescript.md) | TypeScript usage and type definitions |
|
|
281
200
|
|
|
282
201
|
## License
|
|
283
202
|
|
|
284
|
-
MIT — [Amit Gupta](https://
|
|
203
|
+
MIT — [Amit Gupta](https://solothought.com)
|
package/lib/fxp.d.cts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* tracking and enclosure skipping when scanning for the closing tag.
|
|
11
11
|
*
|
|
12
12
|
* ```ts
|
|
13
|
-
* import { xmlEnclosures } from '
|
|
13
|
+
* import { xmlEnclosures } from '@nodable/flexible-xml-parser';
|
|
14
14
|
*
|
|
15
15
|
* const parser = new XMLParser({
|
|
16
16
|
* skip: {
|
|
@@ -69,7 +69,7 @@ interface SkipOptions {
|
|
|
69
69
|
* Supports path-expression-matcher syntax. Default: []
|
|
70
70
|
*
|
|
71
71
|
* @example
|
|
72
|
-
* import { xmlEnclosures } from '
|
|
72
|
+
* import { xmlEnclosures } from '@nodable/flexible-xml-parser';
|
|
73
73
|
*
|
|
74
74
|
* skip: {
|
|
75
75
|
* tags: [
|
|
@@ -131,7 +131,7 @@ interface Enclosure {
|
|
|
131
131
|
* enclosures the processor should skip when scanning for the closing tag.
|
|
132
132
|
*
|
|
133
133
|
* ```ts
|
|
134
|
-
* import { xmlEnclosures, quoteEnclosures } from '
|
|
134
|
+
* import { xmlEnclosures, quoteEnclosures } from '@nodable/flexible-xml-parser';
|
|
135
135
|
*
|
|
136
136
|
* const parser = new XMLParser({
|
|
137
137
|
* tags: {
|
|
@@ -177,7 +177,7 @@ interface TagOptions {
|
|
|
177
177
|
* Supports path-expression-matcher syntax. Default: []
|
|
178
178
|
*
|
|
179
179
|
* @example
|
|
180
|
-
* import { xmlEnclosures, quoteEnclosures } from '
|
|
180
|
+
* import { xmlEnclosures, quoteEnclosures } from '@nodable/flexible-xml-parser';
|
|
181
181
|
*
|
|
182
182
|
* stopNodes: [
|
|
183
183
|
* "..script", // plain
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodable/flexible-xml-parser",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Fastest XML parser in pure JS with fully customizable ouput",
|
|
5
5
|
"main": "./lib/fxp.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@nodable/base-output-builder": "^1.0.
|
|
48
|
-
"@nodable/compact-builder": "^1.0.
|
|
49
|
-
"path-expression-matcher": "^1.
|
|
47
|
+
"@nodable/base-output-builder": "^1.0.3",
|
|
48
|
+
"@nodable/compact-builder": "^1.0.4",
|
|
49
|
+
"path-expression-matcher": "^1.5.0",
|
|
50
50
|
"strnum": "^2.2.2"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"@babel/plugin-transform-runtime": "^7.29.0",
|
|
55
55
|
"@babel/preset-env": "^7.29.2",
|
|
56
56
|
"@babel/register": "^7.28.6",
|
|
57
|
+
"@nodable/entities": "^1.1.0",
|
|
57
58
|
"@types/node": "^20.19.37",
|
|
58
59
|
"babel-loader": "^10.1.1",
|
|
59
60
|
"c8": "^11.0.0",
|
package/src/ParseError.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ParseError — structured error class for
|
|
2
|
+
* ParseError — structured error class for flexible-xml-parser.
|
|
3
3
|
*
|
|
4
4
|
* All errors thrown by the parser are instances of ParseError so callers can
|
|
5
5
|
* distinguish library errors from generic runtime errors and reliably inspect
|
|
@@ -24,8 +24,8 @@ export class ParseError extends Error {
|
|
|
24
24
|
this.name = 'ParseError';
|
|
25
25
|
this.code = code;
|
|
26
26
|
|
|
27
|
-
this.line
|
|
28
|
-
this.col
|
|
27
|
+
this.line = position.line ?? undefined;
|
|
28
|
+
this.col = position.col ?? undefined;
|
|
29
29
|
this.index = position.index ?? undefined;
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -49,43 +49,43 @@ export class ParseError extends Error {
|
|
|
49
49
|
|
|
50
50
|
export const ErrorCode = Object.freeze({
|
|
51
51
|
// Input type errors
|
|
52
|
-
INVALID_INPUT:
|
|
53
|
-
INVALID_STREAM:
|
|
52
|
+
INVALID_INPUT: 'INVALID_INPUT',
|
|
53
|
+
INVALID_STREAM: 'INVALID_STREAM',
|
|
54
54
|
|
|
55
55
|
// Streaming / feed API
|
|
56
|
-
ALREADY_STREAMING:
|
|
57
|
-
NOT_STREAMING:
|
|
58
|
-
DATA_MUST_BE_STRING:
|
|
56
|
+
ALREADY_STREAMING: 'ALREADY_STREAMING',
|
|
57
|
+
NOT_STREAMING: 'NOT_STREAMING',
|
|
58
|
+
DATA_MUST_BE_STRING: 'DATA_MUST_BE_STRING',
|
|
59
59
|
|
|
60
60
|
// Tag structure
|
|
61
|
-
UNEXPECTED_END:
|
|
62
|
-
UNEXPECTED_CLOSE_TAG:
|
|
63
|
-
MISMATCHED_CLOSE_TAG:
|
|
64
|
-
UNEXPECTED_TRAILING_DATA:
|
|
65
|
-
INVALID_TAG:
|
|
66
|
-
UNCLOSED_QUOTE:
|
|
61
|
+
UNEXPECTED_END: 'UNEXPECTED_END',
|
|
62
|
+
UNEXPECTED_CLOSE_TAG: 'UNEXPECTED_CLOSE_TAG',
|
|
63
|
+
MISMATCHED_CLOSE_TAG: 'MISMATCHED_CLOSE_TAG',
|
|
64
|
+
UNEXPECTED_TRAILING_DATA: 'UNEXPECTED_TRAILING_DATA',
|
|
65
|
+
INVALID_TAG: 'INVALID_TAG',
|
|
66
|
+
UNCLOSED_QUOTE: 'UNCLOSED_QUOTE',
|
|
67
67
|
|
|
68
68
|
// Namespace
|
|
69
|
-
MULTIPLE_NAMESPACES:
|
|
69
|
+
MULTIPLE_NAMESPACES: 'MULTIPLE_NAMESPACES',
|
|
70
70
|
|
|
71
71
|
// Security
|
|
72
72
|
SECURITY_PROTOTYPE_POLLUTION: 'SECURITY_PROTOTYPE_POLLUTION',
|
|
73
|
-
SECURITY_RESERVED_OPTION:
|
|
74
|
-
SECURITY_RESTRICTED_NAME:
|
|
73
|
+
SECURITY_RESERVED_OPTION: 'SECURITY_RESERVED_OPTION',
|
|
74
|
+
SECURITY_RESTRICTED_NAME: 'SECURITY_RESTRICTED_NAME',
|
|
75
75
|
|
|
76
76
|
// Limits (DoS prevention)
|
|
77
|
-
LIMIT_MAX_NESTED_TAGS:
|
|
78
|
-
LIMIT_MAX_ATTRIBUTES:
|
|
77
|
+
LIMIT_MAX_NESTED_TAGS: 'LIMIT_MAX_NESTED_TAGS',
|
|
78
|
+
LIMIT_MAX_ATTRIBUTES: 'LIMIT_MAX_ATTRIBUTES',
|
|
79
79
|
|
|
80
80
|
// Entity limits
|
|
81
|
-
ENTITY_MAX_COUNT:
|
|
82
|
-
ENTITY_MAX_SIZE:
|
|
83
|
-
ENTITY_MAX_EXPANSIONS:
|
|
81
|
+
ENTITY_MAX_COUNT: 'ENTITY_MAX_COUNT',
|
|
82
|
+
ENTITY_MAX_SIZE: 'ENTITY_MAX_SIZE',
|
|
83
|
+
ENTITY_MAX_EXPANSIONS: 'ENTITY_MAX_EXPANSIONS',
|
|
84
84
|
ENTITY_MAX_EXPANDED_LENGTH: 'ENTITY_MAX_EXPANDED_LENGTH',
|
|
85
85
|
|
|
86
86
|
// Entity registration
|
|
87
|
-
ENTITY_INVALID_KEY:
|
|
88
|
-
ENTITY_INVALID_VALUE:
|
|
87
|
+
ENTITY_INVALID_KEY: 'ENTITY_INVALID_KEY',
|
|
88
|
+
ENTITY_INVALID_VALUE: 'ENTITY_INVALID_VALUE',
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
export default ParseError;
|
package/src/StopNodeProcessor.js
CHANGED
|
@@ -5,7 +5,7 @@ import { ParseError, ErrorCode } from './ParseError.js';
|
|
|
5
5
|
*
|
|
6
6
|
* Import these in your parser config to compose skipEnclosures arrays:
|
|
7
7
|
*
|
|
8
|
-
* import { xmlEnclosures, quoteEnclosures } from '
|
|
8
|
+
* import { xmlEnclosures, quoteEnclosures } from '@nodable/flexible-xml-parser';
|
|
9
9
|
*
|
|
10
10
|
* stopNodes: [
|
|
11
11
|
* "..script", // plain — no enclosures (default)
|
package/src/fxp.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { BaseOutputBuilderFactory } from "@nodable/base-output-builder"
|
|
|
9
9
|
* tracking and enclosure skipping when scanning for the closing tag.
|
|
10
10
|
*
|
|
11
11
|
* ```ts
|
|
12
|
-
* import { xmlEnclosures } from '
|
|
12
|
+
* import { xmlEnclosures } from '@nodable/flexible-xml-parser';
|
|
13
13
|
*
|
|
14
14
|
* const parser = new XMLParser({
|
|
15
15
|
* skip: {
|
|
@@ -68,7 +68,7 @@ export interface SkipOptions {
|
|
|
68
68
|
* Supports path-expression-matcher syntax. Default: []
|
|
69
69
|
*
|
|
70
70
|
* @example
|
|
71
|
-
* import { xmlEnclosures } from '
|
|
71
|
+
* import { xmlEnclosures } from '@nodable/flexible-xml-parser';
|
|
72
72
|
*
|
|
73
73
|
* skip: {
|
|
74
74
|
* tags: [
|
|
@@ -130,7 +130,7 @@ export interface Enclosure {
|
|
|
130
130
|
* enclosures the processor should skip when scanning for the closing tag.
|
|
131
131
|
*
|
|
132
132
|
* ```ts
|
|
133
|
-
* import { xmlEnclosures, quoteEnclosures } from '
|
|
133
|
+
* import { xmlEnclosures, quoteEnclosures } from '@nodable/flexible-xml-parser';
|
|
134
134
|
*
|
|
135
135
|
* const parser = new XMLParser({
|
|
136
136
|
* tags: {
|
|
@@ -176,7 +176,7 @@ export interface TagOptions {
|
|
|
176
176
|
* Supports path-expression-matcher syntax. Default: []
|
|
177
177
|
*
|
|
178
178
|
* @example
|
|
179
|
-
* import { xmlEnclosures, quoteEnclosures } from '
|
|
179
|
+
* import { xmlEnclosures, quoteEnclosures } from '@nodable/flexible-xml-parser';
|
|
180
180
|
*
|
|
181
181
|
* stopNodes: [
|
|
182
182
|
* "..script", // plain
|