xml-event-parser 0.1.0 → 0.2.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 CHANGED
@@ -1,36 +1,97 @@
1
1
  xml-event-parser
2
2
  ================
3
3
 
4
- A tiny XML parser with a document tree and path-based events, produced in a
5
- single pass.
6
-
7
- - **Zero dependencies**, no DOM, no `node:*` imports, no `eval`.
8
- - **One pass, two views**: every element is available as a tree node and as an
9
- `open`/`close` event keyed by its dotted path, so a consumer can convert while
10
- parsing without a second traversal.
11
- - **Streaming**: feed `write(chunk)` from a file or socket and call `end()`.
12
- Chunks may split anywhere - mid-tag, mid-attribute, inside CDATA or a comment -
13
- and `retain` decides whether the parser keeps the tree, one root, or nothing,
14
- so a 60 MiB document can be parsed with ~0 MiB of retained heap.
15
- - **Small**: 6.1 KiB minified, 2.1 KiB gzipped (ESM), streaming included.
16
- - **Fastest on every shape we benchmarked**: ~1.9x `txml`'s throughput on
17
- OSM-style documents at ~30% less retained heap, and ahead of `txml` on
18
- text/entity-heavy input too; ~1.6x `htmlparser2`'s on element callbacks. See
19
- [Benchmarks](#benchmarks).
4
+ **Stream XML without loading it.** A tiny parser that gives you a document tree
5
+ and path-based events in one pass: constant heap on a 240 MB CI report, 2.2 KiB
6
+ gzipped, zero dependencies, and no `node:*` imports in the core entry.
7
+
8
+ - **Bounded memory**: `retain: 'none'` plus a path subscription keeps only the
9
+ element being read. 240 MB in, 81 MiB of RSS.
10
+ - **Fast**: ~3.7x `sax` and ~4x `fast-xml-parser` on the same report, and faster
11
+ than `txml` on every shape we benchmark ([numbers](bench/RESULTS.md)).
12
+ - **Two views, one pass**: a tree node and an `open`/`close` event per element -
13
+ no second traversal, no DOM.
14
+ - **Small and portable**: 6.4 KiB minified / 2.2 KiB gzipped, zero dependencies,
15
+ no `eval`, works in Node, browsers and edge runtimes (the Node adapter is a
16
+ separate `xml-event-parser/stream` entry).
17
+
18
+ ## When to use this
19
+
20
+ | your job | use |
21
+ | --- | --- |
22
+ | Read a file bigger than memory: CI/test reports (JUnit, SARIF, coverage), sitemaps, GPX/KML tracks, OSM extracts | **this**, `xml-event-parser/stream` |
23
+ | Pick elements while streaming without writing a SAX state machine | **this**, path events |
24
+ | Parse occasionally in a browser or Worker where a 20 KiB parser is too much | **this**, core entry (2.2 KiB) |
25
+ | Turn a small config XML into a plain object | `fast-xml-parser` or `xml2js` (richer object mapping) |
26
+ | DOM semantics: namespaces, XPath, mutation | `@xmldom/xmldom` |
27
+ | Zero allocation, hand-tuned callbacks, no tree at all | `sax` (slightly less memory than ours, ~3.7x slower here) |
28
+
29
+ ## 240 MB in one second
30
+
31
+ A generated JUnit report, each parser in its own process capped at 512 MB of
32
+ heap - a typical CI container ([reproduce it](#development)):
33
+
34
+ | parser | 60 MB report | 240 MB report |
35
+ | --- | --- | --- |
36
+ | **xml-event-parser** | **0.27 s, 65 MiB** | **1.03 s, 81 MiB** |
37
+ | sax | 0.96 s, 67 MiB | 3.80 s, 79 MiB |
38
+ | fast-xml-parser | 1.52 s, 637 MiB | **out of memory** |
39
+
40
+ The file grows 4x; the heap does not. `fast-xml-parser` builds a tree, so it
41
+ needs roughly 7-11x the file size and dies on the larger report.
42
+
43
+ ## Quick start
44
+
45
+ ```sh
46
+ npm install xml-event-parser
47
+ ```
48
+
49
+ Tree, when the document fits:
20
50
 
21
51
  ```js
22
52
  import { XmlParser } from 'xml-event-parser';
23
53
 
24
- const parser = new XmlParser({ progressive: true });
54
+ const roots = new XmlParser().parse(xml);
55
+ // roots[0].tagName, roots[0].children, roots[0].text, attributes are flat
56
+ ```
25
57
 
26
- parser.on('</osm.way>', (way) => {
27
- // way.tagName === 'way', way.id, way.version, way.children ...
28
- console.log(way.id, way.children?.length ?? 0);
29
- });
58
+ Stream, when it does not:
30
59
 
31
- const roots = parser.parse(xml);
60
+ ```js
61
+ import { createReadStream } from 'node:fs';
62
+ import { Writable } from 'node:stream';
63
+ import { pipeline } from 'node:stream/promises';
64
+ import { createStream } from 'xml-event-parser/stream';
65
+
66
+ await pipeline(
67
+ createReadStream('junit.xml'),
68
+ createStream({ event: '</testsuites.testsuite.testcase>' }),
69
+ new Writable({
70
+ objectMode: true,
71
+ write(testcase, _encoding, done) {
72
+ // complete element: attributes + <failure>/<system-out> children
73
+ if (testcase.children?.some((child) => child.tagName === 'failure')) {
74
+ console.log(testcase.name);
75
+ }
76
+ done();
77
+ },
78
+ }),
79
+ );
80
+ ```
81
+
82
+ Without Node streams, `write()`/`end()` work anywhere:
83
+
84
+ ```js
85
+ const parser = new XmlParser({ progressive: true, retain: 'none', keep: ['testsuites.testsuite.testcase'] });
86
+ parser.on('</testsuites.testsuite.testcase>', (testcase) => { /* ... */ });
87
+ for await (const chunk of response.body) parser.write(chunk); // no node: imports
88
+ parser.end();
32
89
  ```
33
90
 
91
+ Runnable examples: [`examples/junit.mjs`](examples/junit.mjs),
92
+ [`examples/sitemap.mjs`](examples/sitemap.mjs),
93
+ [`examples/gpx.mjs`](examples/gpx.mjs) - `node examples/junit.mjs`.
94
+
34
95
  ## Node shape
35
96
 
36
97
  ```ts
@@ -87,6 +148,20 @@ Measured on a 60 MiB document (ten concatenated extracts, 327k elements):
87
148
  327k elements. Without listeners, `retain: 'none'` still parses - it just throws
88
149
  the result away.
89
150
 
151
+ Under `retain: 'none'`, `keep` restores the subtree of the elements you actually
152
+ want, so a streamed element is complete rather than a bare tag:
153
+
154
+ ```js
155
+ new XmlParser({
156
+ progressive: true,
157
+ retain: 'none',
158
+ keep: ['testsuites.testsuite.testcase'], // 'testsuites.testsuite.testcase' or '</testsuites.testsuite.testcase>'
159
+ });
160
+ ```
161
+
162
+ Memory is then the largest matched element, not the document. `createStream`
163
+ sets this up for you from its `event` path.
164
+
90
165
  ## Events
91
166
 
92
167
  | form | fires |
@@ -111,6 +186,7 @@ parser.on('</osm.way>', (way) => {
111
186
  | `progressive` | `false` | emit events. Without it, `parse()` only builds the tree |
112
187
  | `queryParent` | `false` | track parents so `parser.getParent(node)` works (costs a `WeakMap`) |
113
188
  | `retain` | `'all'` | what survives an element: `'all'`, `'root'` or `'none'` (see [Streaming](#streaming)) |
189
+ | `keep` | `[]` | under `retain: 'none'`, dotted paths whose elements keep their subtree |
114
190
 
115
191
  `parse(xml, parent?, dir?)` also accepts an existing node to attach the parsed
116
192
  roots to, and a dotted path prefix used when building event names.
@@ -159,10 +235,11 @@ public naming (`children`/`tagName`/`text` instead of `$innerNodes`/`$tag`/
159
235
  `$innerText`) and the removal of the `new Function`-based conditioned-path
160
236
  events.
161
237
 
162
- Not published yet. The release workflow publishes only when the repository
163
- variable `NPM_TRUSTED_PUBLISHER` is `true`; before that, a release is published
164
- locally because npm cannot configure a trusted publisher for a package that does
165
- not exist yet.
238
+ Published as `xml-event-parser` (current version on npm). Releases are cut by
239
+ changing `version` on `main`: `release.yml` publishes through npm trusted
240
+ publishing (OIDC, with provenance), tags `v<version>` and creates the GitHub
241
+ release. The job runs only when the repository variable
242
+ `NPM_TRUSTED_PUBLISHER` is `true`.
166
243
 
167
244
  ## Development
168
245
 
@@ -172,6 +249,7 @@ npm test # unit tests
172
249
  npm run typecheck
173
250
  npm run build # dist/index.mjs, dist/index.cjs, dist/index.d.ts
174
251
  npm run bench:data # generate ~27 MiB benchmark corpus (gitignored)
252
+ npm run bench:memory # JUnit memory/time table under a 512 MB heap cap
175
253
  npm run bench # compare against txml / sax / htmlparser2 / fast-xml-parser
176
254
  ```
177
255
 
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){switch(e){case`amp`:return`&`;case`lt`:return`<`;case`gt`:return`>`;case`quot`:return`"`;case`apos`:return`'`}if(e.charCodeAt(0)===35){let t=e.charCodeAt(1)===120||e.charCodeAt(1)===88?parseInt(e.slice(2),16):parseInt(e.slice(1),10);if(Number.isFinite(t)&&t>=0&&t<=1114111)return String.fromCodePoint(t)}return null}function t(t){let n=t.indexOf(`&`);if(n<0)return t;let r=t.slice(0,n);for(;n<t.length;){let i=t.indexOf(`;`,n+1);if(i<0){r+=t.slice(n);break}let a=e(t.slice(n+1,i));if(r+=a===null?t.slice(n,i+1):a,n=t.indexOf(`&`,i+1),n<0){r+=t.slice(i+1);break}r+=t.slice(i+1,n)}return r}function n(e){if(e.indexOf(`<![CDATA[`)<0)return e.indexOf(`&`)<0?e:t(e);let n=``,r=0;for(;r<e.length;){let i=e.indexOf(`<![CDATA[`,r);if(i<0){n+=t(e.slice(r));break}i>r&&(n+=t(e.slice(r,i)));let a=e.indexOf(`]]>`,i+9);if(a<0){n+=e.slice(i+9);break}n+=e.slice(i+9,a),r=a+3}return n}function r(e){return e===32||e===9||e===10||e===13}var i=class{constructor(e){this.queryParent=!1,this.progressive=!1,this.retain=`all`,this.parentMap=/* @__PURE__ */ new WeakMap,this.evtListeners={},this.openListenerCount=0,this.watchedPaths=/* @__PURE__ */ new Set,this.watchedChildren=/* @__PURE__ */ new Map,this.watchedTopLevel=/* @__PURE__ */ new Set,this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.basePath=``,this.active=!1,e&&(this.queryParent=e.queryParent===!0,this.progressive=e.progressive===!0,e.retain&&(this.retain=e.retain))}write(e){return this.active||this.begin(void 0,void 0),e.length===0||(this.buffer.length===0?this.buffer=e:this.buffer+=e,this.consume(!1)),this}end(){if(!this.active)return[];this.consume(!0),this.finish();let e=this.roots;return this.reset(),e}parse(e,t,n){return this.begin(t,n),this.write(e),this.end()}begin(e,t){this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.basePath=t||``,this.top=e?{node:e,fullTag:this.basePath,textStart:0}:void 0,this.rootOwner=this.top,this.parentMap=/* @__PURE__ */ new WeakMap,this.active=!0}reset(){this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.top=void 0,this.rootOwner=void 0,this.basePath=``,this.active=!1}attach(e,t){if(this.retain===`none`)return;if(t===void 0){this.retain===`all`&&this.roots.push(e);return}let n=t.children;n?n.push(e):t.children=[e]}consume(e){let t=this.buffer,r=t.length,i=this.progressive,a=i&&this.openListenerCount>0,o=this.cursor,s=-1;for(;o<r;){let c=t.indexOf(`<`,o);if(c<0||(s=c,o=c+1,o>=r))break;let l=t.charCodeAt(o);if(l===47){let a=t.indexOf(`>`,o);if(a<0){if(!e)break;o=r;break}let l=this.stack.pop();if(o=a+1,s=-1,!l)continue;let u=l.node,d=this.stack.length?this.stack[this.stack.length-1]:this.rootOwner,f=d?d.node:void 0;!l.hasChild&&c>l.textStart&&(u.text=n(t.slice(l.textStart,c))),i&&this.watchedPaths.has(l.fullTag)&&this.emit(`</`+l.fullTag+`>`,u,f),this.attach(u,f),d&&(d.hasChild=!0),this.top=d;continue}if(l===63){let n=t.indexOf(`>`,o);if(n<0){if(!e)break;o=r;break}o=n+1,s=-1;continue}if(l===33){let n;if(t.charCodeAt(o+1)===45&&t.charCodeAt(o+2)===45?(n=t.indexOf(`-->`,o+3),n=n<0?-1:n+3):t.startsWith(`![CDATA[`,o)?(n=t.indexOf(`]]>`,o+9),n=n<0?-1:n+3):(n=t.indexOf(`>`,o),n=n<0?-1:n+1),n<0){if(!e)break;o=r;break}o=n,s=-1;continue}let u=this.parseOpenTag(t,o,r,e);if(u===null)break;o=u.end,s=-1;let d=u.node,f=u.tag,p=this.top?this.top.node:void 0;if(this.queryParent&&p&&this.parentMap.set(d,p),u.selfClose){if(i){let e=this.top?this.top.fullTag:this.basePath,t=e?this.watchedChildren.get(e):this.watchedTopLevel;if(t&&t.has(f)){let t=e?e+`.`+f:f;a&&this.emit(`<`+t+`>`,d,p),this.emit(`</`+t+`>`,d,p)}}this.attach(d,p),this.top&&(this.top.hasChild=!0);continue}let m=this.top?this.top.fullTag?this.top.fullTag+`.`+f:f:this.basePath?this.basePath+`.`+f:f;a&&this.watchedPaths.has(m)&&this.emit(`<`+m+`>`,d,p);let h={node:d,fullTag:m,textStart:o};this.stack.push(h),this.top=h}this.cursor=e?r:s>=0?s:o,e||this.compact()}parseOpenTag(e,n,i,a){let o=n;for(;o<i;){let t=e.charCodeAt(o);if(r(t)||t===47||t===62)break;o++}if(o>=i&&!a)return null;let s=e.slice(n,o),c={tagName:s},l=!1;for(;o<i;){let n=e.charCodeAt(o);for(;o<i&&r(n);)n=e.charCodeAt(++o);if(o>=i)break;if(n===62)return o++,{node:c,tag:s,selfClose:l,end:o};if(n===47){l=!0,o++;continue}let u=o;for(;o<i&&(n=e.charCodeAt(o),!(n===61||r(n)||n===62||n===47));)o++;if(o>=i&&!a)return null;let d=e.slice(u,o);for(;o<i&&r(e.charCodeAt(o));)o++;if(o>=i&&!a)return null;if(e.charCodeAt(o)===61){for(o++;o<i&&r(e.charCodeAt(o));)o++;if(o>=i&&!a)return null;let n=e.charCodeAt(o);if(n===34||n===39){o++;let r=e.indexOf(n===34?`"`:`'`,o);if(r<0){if(!a)return null;r=i}let s=e.slice(o,r);c[d]=s.indexOf(`&`)<0?s:t(s),o=r+1}}else d&&(c[d]=``)}return a?{node:c,tag:s,selfClose:l,end:o}:null}finish(){let e=this.buffer,t=e.length;for(;this.stack.length>0;){let r=this.stack.pop(),i=r.node;!r.hasChild&&t>r.textStart&&(i.text=n(e.slice(r.textStart,t)),t=r.textStart);let a=this.stack.length?this.stack[this.stack.length-1]:this.rootOwner,o=a?a.node:void 0;this.attach(i,o)}}compact(){let e=this.cursor;for(let t=this.stack.length-1;t>=0;t--){let n=this.stack[t];if(!n.hasChild){e=n.textStart;break}}if(!(e<=0)){this.buffer=this.buffer.slice(e),this.cursor-=e;for(let t=0;t<this.stack.length;t++){let n=this.stack[t];n.textStart=n.textStart>e?n.textStart-e:0}this.rootOwner&&(this.rootOwner.textStart=0)}}getParent(e){return this.queryParent?this.parentMap.get(e):null}addListener(e,t){this.$addListener(e,t)}removeListener(e,t){this.$removeListener(e,t)}on(e,t){this.addListener(e,t)}off(e,t){this.removeListener(e,t)}$addListener(e,t){let n=this.evtListeners[e];if(n?n.push(t):this.evtListeners[e]=[t],e.charCodeAt(1)!==47&&this.openListenerCount++,e.charCodeAt(0)===60){let t=e.slice(e.charCodeAt(1)===47?2:1,e.length-1);this.watchedPaths.add(t);let n=t.lastIndexOf(`.`);if(n<0)this.watchedTopLevel.add(t);else{let e=t.slice(0,n),r=this.watchedChildren.get(e);r||(r=/* @__PURE__ */ new Set,this.watchedChildren.set(e,r)),r.add(t.slice(n+1))}}}$removeListener(e,t){let n=this.evtListeners[e],r=-1;n&&(r=n.indexOf(t)),r>=0&&(n.splice(r,1),e.charCodeAt(1)!==47&&this.openListenerCount--)}emit(e,t,n){let r=this.evtListeners[e];if(r)for(let e=0;e<r.length;e++)r[e](t,n)}};exports.XmlParser=i,exports.decodeEntities=t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){switch(e){case`amp`:return`&`;case`lt`:return`<`;case`gt`:return`>`;case`quot`:return`"`;case`apos`:return`'`}if(e.charCodeAt(0)===35){let t=e.charCodeAt(1)===120||e.charCodeAt(1)===88?parseInt(e.slice(2),16):parseInt(e.slice(1),10);if(Number.isFinite(t)&&t>=0&&t<=1114111)return String.fromCodePoint(t)}return null}function t(t){let n=t.indexOf(`&`);if(n<0)return t;let r=t.slice(0,n);for(;n<t.length;){let i=t.indexOf(`;`,n+1);if(i<0){r+=t.slice(n);break}let a=e(t.slice(n+1,i));if(r+=a===null?t.slice(n,i+1):a,n=t.indexOf(`&`,i+1),n<0){r+=t.slice(i+1);break}r+=t.slice(i+1,n)}return r}function n(e){if(e.indexOf(`<![CDATA[`)<0)return e.indexOf(`&`)<0?e:t(e);let n=``,r=0;for(;r<e.length;){let i=e.indexOf(`<![CDATA[`,r);if(i<0){n+=t(e.slice(r));break}i>r&&(n+=t(e.slice(r,i)));let a=e.indexOf(`]]>`,i+9);if(a<0){n+=e.slice(i+9);break}n+=e.slice(i+9,a),r=a+3}return n}function r(e){let t=0,n=e.length;return e.charCodeAt(0)===60&&(t=e.charCodeAt(1)===47?2:1),e.charCodeAt(n-1)===62&&n--,e.slice(t,n)}function i(e){return e===32||e===9||e===10||e===13}var a=class{constructor(e){if(this.queryParent=!1,this.progressive=!1,this.retain=`all`,this.keepPaths=/* @__PURE__ */ new Set,this.parentMap=/* @__PURE__ */ new WeakMap,this.evtListeners={},this.openListenerCount=0,this.watchedPaths=/* @__PURE__ */ new Set,this.watchedChildren=/* @__PURE__ */ new Map,this.watchedTopLevel=/* @__PURE__ */ new Set,this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.basePath=``,this.active=!1,e&&(this.queryParent=e.queryParent===!0,this.progressive=e.progressive===!0,e.retain&&(this.retain=e.retain),e.keep))for(let t of e.keep)this.keepPaths.add(r(t))}write(e){return this.active||this.begin(void 0,void 0),e.length===0||(this.buffer.length===0?this.buffer=e:this.buffer+=e,this.consume(!1)),this}end(){if(!this.active)return[];this.consume(!0),this.finish();let e=this.roots;return this.reset(),e}parse(e,t,n){return this.begin(t,n),this.write(e),this.end()}begin(e,t){this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.basePath=t||``,this.top=e?{node:e,fullTag:this.basePath,textStart:0}:void 0,this.rootOwner=this.top,this.parentMap=/* @__PURE__ */ new WeakMap,this.active=!0}reset(){this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.top=void 0,this.rootOwner=void 0,this.basePath=``,this.active=!1}attach(e,t,n){if(this.retain===`none`&&!n)return;if(t===void 0){this.retain===`all`&&this.roots.push(e);return}let r=t.children;r?r.push(e):t.children=[e]}consume(e){let t=this.buffer,r=t.length,i=this.progressive,a=i&&this.openListenerCount>0,o=this.cursor,s=-1;for(;o<r;){let c=t.indexOf(`<`,o);if(c<0||(s=c,o=c+1,o>=r))break;let l=t.charCodeAt(o);if(l===47){let a=t.indexOf(`>`,o);if(a<0){if(!e)break;o=r;break}let l=this.stack.pop();if(o=a+1,s=-1,!l)continue;let u=l.node,d=this.stack.length?this.stack[this.stack.length-1]:this.rootOwner,f=d?d.node:void 0;!l.hasChild&&c>l.textStart&&(u.text=n(t.slice(l.textStart,c))),i&&this.watchedPaths.has(l.fullTag)&&this.emit(`</`+l.fullTag+`>`,u,f),this.attach(u,f,d?d.keep===!0:!1),d&&(d.hasChild=!0),this.top=d;continue}if(l===63){let n=t.indexOf(`>`,o);if(n<0){if(!e)break;o=r;break}o=n+1,s=-1;continue}if(l===33){let n;if(t.charCodeAt(o+1)===45&&t.charCodeAt(o+2)===45?(n=t.indexOf(`-->`,o+3),n=n<0?-1:n+3):t.startsWith(`![CDATA[`,o)?(n=t.indexOf(`]]>`,o+9),n=n<0?-1:n+3):(n=t.indexOf(`>`,o),n=n<0?-1:n+1),n<0){if(!e)break;o=r;break}o=n,s=-1;continue}let u=this.parseOpenTag(t,o,r,e);if(u===null)break;o=u.end,s=-1;let d=u.node,f=u.tag,p=this.top?this.top.node:void 0;if(this.queryParent&&p&&this.parentMap.set(d,p),u.selfClose){if(i){let e=this.top?this.top.fullTag:this.basePath,t=e?this.watchedChildren.get(e):this.watchedTopLevel;if(t&&t.has(f)){let t=e?e+`.`+f:f;a&&this.emit(`<`+t+`>`,d,p),this.emit(`</`+t+`>`,d,p)}}this.attach(d,p,this.top?this.top.keep===!0:!1),this.top&&(this.top.hasChild=!0);continue}let m=this.top?this.top.fullTag?this.top.fullTag+`.`+f:f:this.basePath?this.basePath+`.`+f:f;a&&this.watchedPaths.has(m)&&this.emit(`<`+m+`>`,d,p);let h=this.top!==void 0&&this.top.keep===!0||this.keepPaths.has(m),g={node:d,fullTag:m,textStart:o,keep:h};this.stack.push(g),this.top=g}this.cursor=e?r:s>=0?s:o,e||this.compact()}parseOpenTag(e,n,r,a){let o=n;for(;o<r;){let t=e.charCodeAt(o);if(i(t)||t===47||t===62)break;o++}if(o>=r&&!a)return null;let s=e.slice(n,o),c={tagName:s},l=!1;for(;o<r;){let n=e.charCodeAt(o);for(;o<r&&i(n);)n=e.charCodeAt(++o);if(o>=r)break;if(n===62)return o++,{node:c,tag:s,selfClose:l,end:o};if(n===47){l=!0,o++;continue}let u=o;for(;o<r&&(n=e.charCodeAt(o),!(n===61||i(n)||n===62||n===47));)o++;if(o>=r&&!a)return null;let d=e.slice(u,o);for(;o<r&&i(e.charCodeAt(o));)o++;if(o>=r&&!a)return null;if(e.charCodeAt(o)===61){for(o++;o<r&&i(e.charCodeAt(o));)o++;if(o>=r&&!a)return null;let n=e.charCodeAt(o);if(n===34||n===39){o++;let i=e.indexOf(n===34?`"`:`'`,o);if(i<0){if(!a)return null;i=r}let s=e.slice(o,i);c[d]=s.indexOf(`&`)<0?s:t(s),o=i+1}}else d&&(c[d]=``)}return a?{node:c,tag:s,selfClose:l,end:o}:null}finish(){let e=this.buffer,t=e.length;for(;this.stack.length>0;){let r=this.stack.pop(),i=r.node;!r.hasChild&&t>r.textStart&&(i.text=n(e.slice(r.textStart,t)),t=r.textStart);let a=this.stack.length?this.stack[this.stack.length-1]:this.rootOwner,o=a?a.node:void 0;this.attach(i,o,a?a.keep===!0:!1)}}compact(){let e=this.cursor;for(let t=this.stack.length-1;t>=0;t--){let n=this.stack[t];if(!n.hasChild){e=n.textStart;break}}if(!(e<=0)){this.buffer=this.buffer.slice(e),this.cursor-=e;for(let t=0;t<this.stack.length;t++){let n=this.stack[t];n.textStart=n.textStart>e?n.textStart-e:0}this.rootOwner&&(this.rootOwner.textStart=0)}}getParent(e){return this.queryParent?this.parentMap.get(e):null}addListener(e,t){this.$addListener(e,t)}removeListener(e,t){this.$removeListener(e,t)}on(e,t){this.addListener(e,t)}off(e,t){this.removeListener(e,t)}$addListener(e,t){let n=this.evtListeners[e];if(n?n.push(t):this.evtListeners[e]=[t],e.charCodeAt(1)!==47&&this.openListenerCount++,e.charCodeAt(0)===60){let t=e.slice(e.charCodeAt(1)===47?2:1,e.length-1);this.watchedPaths.add(t);let n=t.lastIndexOf(`.`);if(n<0)this.watchedTopLevel.add(t);else{let e=t.slice(0,n),r=this.watchedChildren.get(e);r||(r=/* @__PURE__ */ new Set,this.watchedChildren.set(e,r)),r.add(t.slice(n+1))}}}$removeListener(e,t){let n=this.evtListeners[e],r=-1;n&&(r=n.indexOf(t)),r>=0&&(n.splice(r,1),e.charCodeAt(1)!==47&&this.openListenerCount--)}emit(e,t,n){let r=this.evtListeners[e];if(r)for(let e=0;e<r.length;e++)r[e](t,n)}};exports.XmlParser=a,exports.decodeEntities=t;
package/dist/index.d.ts CHANGED
@@ -38,11 +38,21 @@ interface XmlParserOptions {
38
38
  * nobody listens for is parsed and discarded.
39
39
  */
40
40
  retain?: Retain;
41
+ /**
42
+ * Under `retain: 'none'`, elements whose dotted path is listed here keep
43
+ * their subtree (children and text), so a streamed element arrives
44
+ * complete instead of as a bare tag. Paths use the event form without
45
+ * brackets: `'testsuites.testsuite.testcase'`.
46
+ *
47
+ * Memory stays bounded by the largest matched subtree, not the document.
48
+ */
49
+ keep?: string[];
41
50
  }
42
51
  export declare class XmlParser {
43
52
  private queryParent;
44
53
  private progressive;
45
54
  private retain;
55
+ private keepPaths;
46
56
  private parentMap;
47
57
  private evtListeners;
48
58
  /**
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- function e(e){switch(e){case`amp`:return`&`;case`lt`:return`<`;case`gt`:return`>`;case`quot`:return`"`;case`apos`:return`'`}if(e.charCodeAt(0)===35){let t=e.charCodeAt(1)===120||e.charCodeAt(1)===88?parseInt(e.slice(2),16):parseInt(e.slice(1),10);if(Number.isFinite(t)&&t>=0&&t<=1114111)return String.fromCodePoint(t)}return null}function t(t){let n=t.indexOf(`&`);if(n<0)return t;let r=t.slice(0,n);for(;n<t.length;){let i=t.indexOf(`;`,n+1);if(i<0){r+=t.slice(n);break}let a=e(t.slice(n+1,i));if(r+=a===null?t.slice(n,i+1):a,n=t.indexOf(`&`,i+1),n<0){r+=t.slice(i+1);break}r+=t.slice(i+1,n)}return r}function n(e){if(e.indexOf(`<![CDATA[`)<0)return e.indexOf(`&`)<0?e:t(e);let n=``,r=0;for(;r<e.length;){let i=e.indexOf(`<![CDATA[`,r);if(i<0){n+=t(e.slice(r));break}i>r&&(n+=t(e.slice(r,i)));let a=e.indexOf(`]]>`,i+9);if(a<0){n+=e.slice(i+9);break}n+=e.slice(i+9,a),r=a+3}return n}function r(e){return e===32||e===9||e===10||e===13}var i=class{constructor(e){this.queryParent=!1,this.progressive=!1,this.retain=`all`,this.parentMap=/* @__PURE__ */ new WeakMap,this.evtListeners={},this.openListenerCount=0,this.watchedPaths=/* @__PURE__ */ new Set,this.watchedChildren=/* @__PURE__ */ new Map,this.watchedTopLevel=/* @__PURE__ */ new Set,this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.basePath=``,this.active=!1,e&&(this.queryParent=e.queryParent===!0,this.progressive=e.progressive===!0,e.retain&&(this.retain=e.retain))}write(e){return this.active||this.begin(void 0,void 0),e.length===0||(this.buffer.length===0?this.buffer=e:this.buffer+=e,this.consume(!1)),this}end(){if(!this.active)return[];this.consume(!0),this.finish();let e=this.roots;return this.reset(),e}parse(e,t,n){return this.begin(t,n),this.write(e),this.end()}begin(e,t){this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.basePath=t||``,this.top=e?{node:e,fullTag:this.basePath,textStart:0}:void 0,this.rootOwner=this.top,this.parentMap=/* @__PURE__ */ new WeakMap,this.active=!0}reset(){this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.top=void 0,this.rootOwner=void 0,this.basePath=``,this.active=!1}attach(e,t){if(this.retain===`none`)return;if(t===void 0){this.retain===`all`&&this.roots.push(e);return}let n=t.children;n?n.push(e):t.children=[e]}consume(e){let t=this.buffer,r=t.length,i=this.progressive,a=i&&this.openListenerCount>0,o=this.cursor,s=-1;for(;o<r;){let c=t.indexOf(`<`,o);if(c<0||(s=c,o=c+1,o>=r))break;let l=t.charCodeAt(o);if(l===47){let a=t.indexOf(`>`,o);if(a<0){if(!e)break;o=r;break}let l=this.stack.pop();if(o=a+1,s=-1,!l)continue;let u=l.node,d=this.stack.length?this.stack[this.stack.length-1]:this.rootOwner,f=d?d.node:void 0;!l.hasChild&&c>l.textStart&&(u.text=n(t.slice(l.textStart,c))),i&&this.watchedPaths.has(l.fullTag)&&this.emit(`</`+l.fullTag+`>`,u,f),this.attach(u,f),d&&(d.hasChild=!0),this.top=d;continue}if(l===63){let n=t.indexOf(`>`,o);if(n<0){if(!e)break;o=r;break}o=n+1,s=-1;continue}if(l===33){let n;if(t.charCodeAt(o+1)===45&&t.charCodeAt(o+2)===45?(n=t.indexOf(`-->`,o+3),n=n<0?-1:n+3):t.startsWith(`![CDATA[`,o)?(n=t.indexOf(`]]>`,o+9),n=n<0?-1:n+3):(n=t.indexOf(`>`,o),n=n<0?-1:n+1),n<0){if(!e)break;o=r;break}o=n,s=-1;continue}let u=this.parseOpenTag(t,o,r,e);if(u===null)break;o=u.end,s=-1;let d=u.node,f=u.tag,p=this.top?this.top.node:void 0;if(this.queryParent&&p&&this.parentMap.set(d,p),u.selfClose){if(i){let e=this.top?this.top.fullTag:this.basePath,t=e?this.watchedChildren.get(e):this.watchedTopLevel;if(t&&t.has(f)){let t=e?e+`.`+f:f;a&&this.emit(`<`+t+`>`,d,p),this.emit(`</`+t+`>`,d,p)}}this.attach(d,p),this.top&&(this.top.hasChild=!0);continue}let m=this.top?this.top.fullTag?this.top.fullTag+`.`+f:f:this.basePath?this.basePath+`.`+f:f;a&&this.watchedPaths.has(m)&&this.emit(`<`+m+`>`,d,p);let h={node:d,fullTag:m,textStart:o};this.stack.push(h),this.top=h}this.cursor=e?r:s>=0?s:o,e||this.compact()}parseOpenTag(e,n,i,a){let o=n;for(;o<i;){let t=e.charCodeAt(o);if(r(t)||t===47||t===62)break;o++}if(o>=i&&!a)return null;let s=e.slice(n,o),c={tagName:s},l=!1;for(;o<i;){let n=e.charCodeAt(o);for(;o<i&&r(n);)n=e.charCodeAt(++o);if(o>=i)break;if(n===62)return o++,{node:c,tag:s,selfClose:l,end:o};if(n===47){l=!0,o++;continue}let u=o;for(;o<i&&(n=e.charCodeAt(o),!(n===61||r(n)||n===62||n===47));)o++;if(o>=i&&!a)return null;let d=e.slice(u,o);for(;o<i&&r(e.charCodeAt(o));)o++;if(o>=i&&!a)return null;if(e.charCodeAt(o)===61){for(o++;o<i&&r(e.charCodeAt(o));)o++;if(o>=i&&!a)return null;let n=e.charCodeAt(o);if(n===34||n===39){o++;let r=e.indexOf(n===34?`"`:`'`,o);if(r<0){if(!a)return null;r=i}let s=e.slice(o,r);c[d]=s.indexOf(`&`)<0?s:t(s),o=r+1}}else d&&(c[d]=``)}return a?{node:c,tag:s,selfClose:l,end:o}:null}finish(){let e=this.buffer,t=e.length;for(;this.stack.length>0;){let r=this.stack.pop(),i=r.node;!r.hasChild&&t>r.textStart&&(i.text=n(e.slice(r.textStart,t)),t=r.textStart);let a=this.stack.length?this.stack[this.stack.length-1]:this.rootOwner,o=a?a.node:void 0;this.attach(i,o)}}compact(){let e=this.cursor;for(let t=this.stack.length-1;t>=0;t--){let n=this.stack[t];if(!n.hasChild){e=n.textStart;break}}if(!(e<=0)){this.buffer=this.buffer.slice(e),this.cursor-=e;for(let t=0;t<this.stack.length;t++){let n=this.stack[t];n.textStart=n.textStart>e?n.textStart-e:0}this.rootOwner&&(this.rootOwner.textStart=0)}}getParent(e){return this.queryParent?this.parentMap.get(e):null}addListener(e,t){this.$addListener(e,t)}removeListener(e,t){this.$removeListener(e,t)}on(e,t){this.addListener(e,t)}off(e,t){this.removeListener(e,t)}$addListener(e,t){let n=this.evtListeners[e];if(n?n.push(t):this.evtListeners[e]=[t],e.charCodeAt(1)!==47&&this.openListenerCount++,e.charCodeAt(0)===60){let t=e.slice(e.charCodeAt(1)===47?2:1,e.length-1);this.watchedPaths.add(t);let n=t.lastIndexOf(`.`);if(n<0)this.watchedTopLevel.add(t);else{let e=t.slice(0,n),r=this.watchedChildren.get(e);r||(r=/* @__PURE__ */ new Set,this.watchedChildren.set(e,r)),r.add(t.slice(n+1))}}}$removeListener(e,t){let n=this.evtListeners[e],r=-1;n&&(r=n.indexOf(t)),r>=0&&(n.splice(r,1),e.charCodeAt(1)!==47&&this.openListenerCount--)}emit(e,t,n){let r=this.evtListeners[e];if(r)for(let e=0;e<r.length;e++)r[e](t,n)}};export{i as XmlParser,t as decodeEntities};
1
+ function e(e){switch(e){case`amp`:return`&`;case`lt`:return`<`;case`gt`:return`>`;case`quot`:return`"`;case`apos`:return`'`}if(e.charCodeAt(0)===35){let t=e.charCodeAt(1)===120||e.charCodeAt(1)===88?parseInt(e.slice(2),16):parseInt(e.slice(1),10);if(Number.isFinite(t)&&t>=0&&t<=1114111)return String.fromCodePoint(t)}return null}function t(t){let n=t.indexOf(`&`);if(n<0)return t;let r=t.slice(0,n);for(;n<t.length;){let i=t.indexOf(`;`,n+1);if(i<0){r+=t.slice(n);break}let a=e(t.slice(n+1,i));if(r+=a===null?t.slice(n,i+1):a,n=t.indexOf(`&`,i+1),n<0){r+=t.slice(i+1);break}r+=t.slice(i+1,n)}return r}function n(e){if(e.indexOf(`<![CDATA[`)<0)return e.indexOf(`&`)<0?e:t(e);let n=``,r=0;for(;r<e.length;){let i=e.indexOf(`<![CDATA[`,r);if(i<0){n+=t(e.slice(r));break}i>r&&(n+=t(e.slice(r,i)));let a=e.indexOf(`]]>`,i+9);if(a<0){n+=e.slice(i+9);break}n+=e.slice(i+9,a),r=a+3}return n}function r(e){let t=0,n=e.length;return e.charCodeAt(0)===60&&(t=e.charCodeAt(1)===47?2:1),e.charCodeAt(n-1)===62&&n--,e.slice(t,n)}function i(e){return e===32||e===9||e===10||e===13}var a=class{constructor(e){if(this.queryParent=!1,this.progressive=!1,this.retain=`all`,this.keepPaths=/* @__PURE__ */ new Set,this.parentMap=/* @__PURE__ */ new WeakMap,this.evtListeners={},this.openListenerCount=0,this.watchedPaths=/* @__PURE__ */ new Set,this.watchedChildren=/* @__PURE__ */ new Map,this.watchedTopLevel=/* @__PURE__ */ new Set,this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.basePath=``,this.active=!1,e&&(this.queryParent=e.queryParent===!0,this.progressive=e.progressive===!0,e.retain&&(this.retain=e.retain),e.keep))for(let t of e.keep)this.keepPaths.add(r(t))}write(e){return this.active||this.begin(void 0,void 0),e.length===0||(this.buffer.length===0?this.buffer=e:this.buffer+=e,this.consume(!1)),this}end(){if(!this.active)return[];this.consume(!0),this.finish();let e=this.roots;return this.reset(),e}parse(e,t,n){return this.begin(t,n),this.write(e),this.end()}begin(e,t){this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.basePath=t||``,this.top=e?{node:e,fullTag:this.basePath,textStart:0}:void 0,this.rootOwner=this.top,this.parentMap=/* @__PURE__ */ new WeakMap,this.active=!0}reset(){this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.top=void 0,this.rootOwner=void 0,this.basePath=``,this.active=!1}attach(e,t,n){if(this.retain===`none`&&!n)return;if(t===void 0){this.retain===`all`&&this.roots.push(e);return}let r=t.children;r?r.push(e):t.children=[e]}consume(e){let t=this.buffer,r=t.length,i=this.progressive,a=i&&this.openListenerCount>0,o=this.cursor,s=-1;for(;o<r;){let c=t.indexOf(`<`,o);if(c<0||(s=c,o=c+1,o>=r))break;let l=t.charCodeAt(o);if(l===47){let a=t.indexOf(`>`,o);if(a<0){if(!e)break;o=r;break}let l=this.stack.pop();if(o=a+1,s=-1,!l)continue;let u=l.node,d=this.stack.length?this.stack[this.stack.length-1]:this.rootOwner,f=d?d.node:void 0;!l.hasChild&&c>l.textStart&&(u.text=n(t.slice(l.textStart,c))),i&&this.watchedPaths.has(l.fullTag)&&this.emit(`</`+l.fullTag+`>`,u,f),this.attach(u,f,d?d.keep===!0:!1),d&&(d.hasChild=!0),this.top=d;continue}if(l===63){let n=t.indexOf(`>`,o);if(n<0){if(!e)break;o=r;break}o=n+1,s=-1;continue}if(l===33){let n;if(t.charCodeAt(o+1)===45&&t.charCodeAt(o+2)===45?(n=t.indexOf(`-->`,o+3),n=n<0?-1:n+3):t.startsWith(`![CDATA[`,o)?(n=t.indexOf(`]]>`,o+9),n=n<0?-1:n+3):(n=t.indexOf(`>`,o),n=n<0?-1:n+1),n<0){if(!e)break;o=r;break}o=n,s=-1;continue}let u=this.parseOpenTag(t,o,r,e);if(u===null)break;o=u.end,s=-1;let d=u.node,f=u.tag,p=this.top?this.top.node:void 0;if(this.queryParent&&p&&this.parentMap.set(d,p),u.selfClose){if(i){let e=this.top?this.top.fullTag:this.basePath,t=e?this.watchedChildren.get(e):this.watchedTopLevel;if(t&&t.has(f)){let t=e?e+`.`+f:f;a&&this.emit(`<`+t+`>`,d,p),this.emit(`</`+t+`>`,d,p)}}this.attach(d,p,this.top?this.top.keep===!0:!1),this.top&&(this.top.hasChild=!0);continue}let m=this.top?this.top.fullTag?this.top.fullTag+`.`+f:f:this.basePath?this.basePath+`.`+f:f;a&&this.watchedPaths.has(m)&&this.emit(`<`+m+`>`,d,p);let h=this.top!==void 0&&this.top.keep===!0||this.keepPaths.has(m),g={node:d,fullTag:m,textStart:o,keep:h};this.stack.push(g),this.top=g}this.cursor=e?r:s>=0?s:o,e||this.compact()}parseOpenTag(e,n,r,a){let o=n;for(;o<r;){let t=e.charCodeAt(o);if(i(t)||t===47||t===62)break;o++}if(o>=r&&!a)return null;let s=e.slice(n,o),c={tagName:s},l=!1;for(;o<r;){let n=e.charCodeAt(o);for(;o<r&&i(n);)n=e.charCodeAt(++o);if(o>=r)break;if(n===62)return o++,{node:c,tag:s,selfClose:l,end:o};if(n===47){l=!0,o++;continue}let u=o;for(;o<r&&(n=e.charCodeAt(o),!(n===61||i(n)||n===62||n===47));)o++;if(o>=r&&!a)return null;let d=e.slice(u,o);for(;o<r&&i(e.charCodeAt(o));)o++;if(o>=r&&!a)return null;if(e.charCodeAt(o)===61){for(o++;o<r&&i(e.charCodeAt(o));)o++;if(o>=r&&!a)return null;let n=e.charCodeAt(o);if(n===34||n===39){o++;let i=e.indexOf(n===34?`"`:`'`,o);if(i<0){if(!a)return null;i=r}let s=e.slice(o,i);c[d]=s.indexOf(`&`)<0?s:t(s),o=i+1}}else d&&(c[d]=``)}return a?{node:c,tag:s,selfClose:l,end:o}:null}finish(){let e=this.buffer,t=e.length;for(;this.stack.length>0;){let r=this.stack.pop(),i=r.node;!r.hasChild&&t>r.textStart&&(i.text=n(e.slice(r.textStart,t)),t=r.textStart);let a=this.stack.length?this.stack[this.stack.length-1]:this.rootOwner,o=a?a.node:void 0;this.attach(i,o,a?a.keep===!0:!1)}}compact(){let e=this.cursor;for(let t=this.stack.length-1;t>=0;t--){let n=this.stack[t];if(!n.hasChild){e=n.textStart;break}}if(!(e<=0)){this.buffer=this.buffer.slice(e),this.cursor-=e;for(let t=0;t<this.stack.length;t++){let n=this.stack[t];n.textStart=n.textStart>e?n.textStart-e:0}this.rootOwner&&(this.rootOwner.textStart=0)}}getParent(e){return this.queryParent?this.parentMap.get(e):null}addListener(e,t){this.$addListener(e,t)}removeListener(e,t){this.$removeListener(e,t)}on(e,t){this.addListener(e,t)}off(e,t){this.removeListener(e,t)}$addListener(e,t){let n=this.evtListeners[e];if(n?n.push(t):this.evtListeners[e]=[t],e.charCodeAt(1)!==47&&this.openListenerCount++,e.charCodeAt(0)===60){let t=e.slice(e.charCodeAt(1)===47?2:1,e.length-1);this.watchedPaths.add(t);let n=t.lastIndexOf(`.`);if(n<0)this.watchedTopLevel.add(t);else{let e=t.slice(0,n),r=this.watchedChildren.get(e);r||(r=/* @__PURE__ */ new Set,this.watchedChildren.set(e,r)),r.add(t.slice(n+1))}}}$removeListener(e,t){let n=this.evtListeners[e],r=-1;n&&(r=n.indexOf(t)),r>=0&&(n.splice(r,1),e.charCodeAt(1)!==47&&this.openListenerCount--)}emit(e,t,n){let r=this.evtListeners[e];if(r)for(let e=0;e<r.length;e++)r[e](t,n)}};export{a as XmlParser,t as decodeEntities};
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("node:stream"),t=require("node:string_decoder");function n(e){switch(e){case`amp`:return`&`;case`lt`:return`<`;case`gt`:return`>`;case`quot`:return`"`;case`apos`:return`'`}if(e.charCodeAt(0)===35){let t=e.charCodeAt(1)===120||e.charCodeAt(1)===88?parseInt(e.slice(2),16):parseInt(e.slice(1),10);if(Number.isFinite(t)&&t>=0&&t<=1114111)return String.fromCodePoint(t)}return null}function r(e){let t=e.indexOf(`&`);if(t<0)return e;let r=e.slice(0,t);for(;t<e.length;){let i=e.indexOf(`;`,t+1);if(i<0){r+=e.slice(t);break}let a=n(e.slice(t+1,i));if(r+=a===null?e.slice(t,i+1):a,t=e.indexOf(`&`,i+1),t<0){r+=e.slice(i+1);break}r+=e.slice(i+1,t)}return r}function i(e){if(e.indexOf(`<![CDATA[`)<0)return e.indexOf(`&`)<0?e:r(e);let t=``,n=0;for(;n<e.length;){let i=e.indexOf(`<![CDATA[`,n);if(i<0){t+=r(e.slice(n));break}i>n&&(t+=r(e.slice(n,i)));let a=e.indexOf(`]]>`,i+9);if(a<0){t+=e.slice(i+9);break}t+=e.slice(i+9,a),n=a+3}return t}function a(e){let t=0,n=e.length;return e.charCodeAt(0)===60&&(t=e.charCodeAt(1)===47?2:1),e.charCodeAt(n-1)===62&&n--,e.slice(t,n)}function o(e){return e===32||e===9||e===10||e===13}var s=class{constructor(e){if(this.queryParent=!1,this.progressive=!1,this.retain=`all`,this.keepPaths=/* @__PURE__ */ new Set,this.parentMap=/* @__PURE__ */ new WeakMap,this.evtListeners={},this.openListenerCount=0,this.watchedPaths=/* @__PURE__ */ new Set,this.watchedChildren=/* @__PURE__ */ new Map,this.watchedTopLevel=/* @__PURE__ */ new Set,this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.basePath=``,this.active=!1,e&&(this.queryParent=e.queryParent===!0,this.progressive=e.progressive===!0,e.retain&&(this.retain=e.retain),e.keep))for(let t of e.keep)this.keepPaths.add(a(t))}write(e){return this.active||this.begin(void 0,void 0),e.length===0||(this.buffer.length===0?this.buffer=e:this.buffer+=e,this.consume(!1)),this}end(){if(!this.active)return[];this.consume(!0),this.finish();let e=this.roots;return this.reset(),e}parse(e,t,n){return this.begin(t,n),this.write(e),this.end()}begin(e,t){this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.basePath=t||``,this.top=e?{node:e,fullTag:this.basePath,textStart:0}:void 0,this.rootOwner=this.top,this.parentMap=/* @__PURE__ */ new WeakMap,this.active=!0}reset(){this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.top=void 0,this.rootOwner=void 0,this.basePath=``,this.active=!1}attach(e,t,n){if(this.retain===`none`&&!n)return;if(t===void 0){this.retain===`all`&&this.roots.push(e);return}let r=t.children;r?r.push(e):t.children=[e]}consume(e){let t=this.buffer,n=t.length,r=this.progressive,a=r&&this.openListenerCount>0,o=this.cursor,s=-1;for(;o<n;){let c=t.indexOf(`<`,o);if(c<0||(s=c,o=c+1,o>=n))break;let l=t.charCodeAt(o);if(l===47){let a=t.indexOf(`>`,o);if(a<0){if(!e)break;o=n;break}let l=this.stack.pop();if(o=a+1,s=-1,!l)continue;let u=l.node,d=this.stack.length?this.stack[this.stack.length-1]:this.rootOwner,f=d?d.node:void 0;!l.hasChild&&c>l.textStart&&(u.text=i(t.slice(l.textStart,c))),r&&this.watchedPaths.has(l.fullTag)&&this.emit(`</`+l.fullTag+`>`,u,f),this.attach(u,f,d?d.keep===!0:!1),d&&(d.hasChild=!0),this.top=d;continue}if(l===63){let r=t.indexOf(`>`,o);if(r<0){if(!e)break;o=n;break}o=r+1,s=-1;continue}if(l===33){let r;if(t.charCodeAt(o+1)===45&&t.charCodeAt(o+2)===45?(r=t.indexOf(`-->`,o+3),r=r<0?-1:r+3):t.startsWith(`![CDATA[`,o)?(r=t.indexOf(`]]>`,o+9),r=r<0?-1:r+3):(r=t.indexOf(`>`,o),r=r<0?-1:r+1),r<0){if(!e)break;o=n;break}o=r,s=-1;continue}let u=this.parseOpenTag(t,o,n,e);if(u===null)break;o=u.end,s=-1;let d=u.node,f=u.tag,p=this.top?this.top.node:void 0;if(this.queryParent&&p&&this.parentMap.set(d,p),u.selfClose){if(r){let e=this.top?this.top.fullTag:this.basePath,t=e?this.watchedChildren.get(e):this.watchedTopLevel;if(t&&t.has(f)){let t=e?e+`.`+f:f;a&&this.emit(`<`+t+`>`,d,p),this.emit(`</`+t+`>`,d,p)}}this.attach(d,p,this.top?this.top.keep===!0:!1),this.top&&(this.top.hasChild=!0);continue}let m=this.top?this.top.fullTag?this.top.fullTag+`.`+f:f:this.basePath?this.basePath+`.`+f:f;a&&this.watchedPaths.has(m)&&this.emit(`<`+m+`>`,d,p);let h=this.top!==void 0&&this.top.keep===!0||this.keepPaths.has(m),g={node:d,fullTag:m,textStart:o,keep:h};this.stack.push(g),this.top=g}this.cursor=e?n:s>=0?s:o,e||this.compact()}parseOpenTag(e,t,n,i){let a=t;for(;a<n;){let t=e.charCodeAt(a);if(o(t)||t===47||t===62)break;a++}if(a>=n&&!i)return null;let s=e.slice(t,a),c={tagName:s},l=!1;for(;a<n;){let t=e.charCodeAt(a);for(;a<n&&o(t);)t=e.charCodeAt(++a);if(a>=n)break;if(t===62)return a++,{node:c,tag:s,selfClose:l,end:a};if(t===47){l=!0,a++;continue}let u=a;for(;a<n&&(t=e.charCodeAt(a),!(t===61||o(t)||t===62||t===47));)a++;if(a>=n&&!i)return null;let d=e.slice(u,a);for(;a<n&&o(e.charCodeAt(a));)a++;if(a>=n&&!i)return null;if(e.charCodeAt(a)===61){for(a++;a<n&&o(e.charCodeAt(a));)a++;if(a>=n&&!i)return null;let t=e.charCodeAt(a);if(t===34||t===39){a++;let o=e.indexOf(t===34?`"`:`'`,a);if(o<0){if(!i)return null;o=n}let s=e.slice(a,o);c[d]=s.indexOf(`&`)<0?s:r(s),a=o+1}}else d&&(c[d]=``)}return i?{node:c,tag:s,selfClose:l,end:a}:null}finish(){let e=this.buffer,t=e.length;for(;this.stack.length>0;){let n=this.stack.pop(),r=n.node;!n.hasChild&&t>n.textStart&&(r.text=i(e.slice(n.textStart,t)),t=n.textStart);let a=this.stack.length?this.stack[this.stack.length-1]:this.rootOwner,o=a?a.node:void 0;this.attach(r,o,a?a.keep===!0:!1)}}compact(){let e=this.cursor;for(let t=this.stack.length-1;t>=0;t--){let n=this.stack[t];if(!n.hasChild){e=n.textStart;break}}if(!(e<=0)){this.buffer=this.buffer.slice(e),this.cursor-=e;for(let t=0;t<this.stack.length;t++){let n=this.stack[t];n.textStart=n.textStart>e?n.textStart-e:0}this.rootOwner&&(this.rootOwner.textStart=0)}}getParent(e){return this.queryParent?this.parentMap.get(e):null}addListener(e,t){this.$addListener(e,t)}removeListener(e,t){this.$removeListener(e,t)}on(e,t){this.addListener(e,t)}off(e,t){this.removeListener(e,t)}$addListener(e,t){let n=this.evtListeners[e];if(n?n.push(t):this.evtListeners[e]=[t],e.charCodeAt(1)!==47&&this.openListenerCount++,e.charCodeAt(0)===60){let t=e.slice(e.charCodeAt(1)===47?2:1,e.length-1);this.watchedPaths.add(t);let n=t.lastIndexOf(`.`);if(n<0)this.watchedTopLevel.add(t);else{let e=t.slice(0,n),r=this.watchedChildren.get(e);r||(r=/* @__PURE__ */ new Set,this.watchedChildren.set(e,r)),r.add(t.slice(n+1))}}}$removeListener(e,t){let n=this.evtListeners[e],r=-1;n&&(r=n.indexOf(t)),r>=0&&(n.splice(r,1),e.charCodeAt(1)!==47&&this.openListenerCount--)}emit(e,t,n){let r=this.evtListeners[e];if(r)for(let e=0;e<r.length;e++)r[e](t,n)}};function c(e){if(typeof e!=`string`||e.length===0)throw TypeError(`createStream() requires { event }, e.g. { event: "</root.item>" }`);return e}function l(n){let r=c(n&&n.event),i=new s({...n,progressive:!0,retain:`none`,keep:[r]}),a=new t.StringDecoder(`utf8`),o=new e.Transform({readableObjectMode:!0,transform(e,t,n){try{i.write(typeof e==`string`?e:a.write(e)),n()}catch(e){n(e)}},flush(e){try{let t=a.end();t.length>0&&i.write(t),i.end(),e()}catch(t){e(t)}}});return i.on(r,e=>{o.push(e)}),o}exports.createStream=l;
@@ -0,0 +1,61 @@
1
+ import { Transform } from "node:stream";
2
+ type Retain = 'all' | 'root' | 'none';
3
+ interface XmlParserOptions {
4
+ /** Emit `<path>` / `</path>` events as elements open and close. Default `false`. */
5
+ progressive?: boolean;
6
+ /** Track parents so `getParent(node)` works. Default `false`. */
7
+ queryParent?: boolean;
8
+ /**
9
+ * What the parser keeps after an element closes:
10
+ * - `'all'` (default) the whole document tree, returned by `end()`
11
+ * - `'root'` the root being built; a finished top-level element is emitted
12
+ * and dropped, so memory is bounded by the largest root
13
+ * - `'none'` nothing; every element is emitted and dropped, so memory is
14
+ * bounded by the nesting depth. Combine with listeners: an element
15
+ * nobody listens for is parsed and discarded.
16
+ */
17
+ retain?: Retain;
18
+ /**
19
+ * Under `retain: 'none'`, elements whose dotted path is listed here keep
20
+ * their subtree (children and text), so a streamed element arrives
21
+ * complete instead of as a bare tag. Paths use the event form without
22
+ * brackets: `'testsuites.testsuite.testcase'`.
23
+ *
24
+ * Memory stays bounded by the largest matched subtree, not the document.
25
+ */
26
+ keep?: string[];
27
+ }
28
+ export interface XmlStreamOptions extends XmlParserOptions {
29
+ /**
30
+ * Close-event path to emit, in event form: `'</testsuites.testsuite.testcase>'`.
31
+ *
32
+ * Required, and it is what keeps memory bounded: elements matching the path
33
+ * are handed out whole (with their children) and everything else is
34
+ * dropped as it closes. Memory is the largest matched element, not the file.
35
+ */
36
+ event: string;
37
+ }
38
+ /**
39
+ * Parse XML from a readable stream, emitting one object per matching element.
40
+ *
41
+ * ```js
42
+ * import { createStream } from 'xml-event-parser/stream';
43
+ *
44
+ * pipeline(
45
+ * createReadStream('junit.xml'),
46
+ * createStream({ event: '</testsuites.testsuite.testcase>' }),
47
+ * new Writable({ objectMode: true, write(testcase, _enc, done) { ...; done(); } }),
48
+ * );
49
+ * ```
50
+ *
51
+ * Chunks may split anywhere; multibyte characters split across chunk boundaries
52
+ * are reassembled with `StringDecoder`. Backpressure is applied per chunk: the
53
+ * stream stops pulling input while the readable side is full, so a slow consumer
54
+ * does not accumulate the document.
55
+ *
56
+ * This module imports `node:stream`, which is why it is a separate entry point
57
+ * (`xml-event-parser/stream`): the core parser stays usable in browsers and edge
58
+ * runtimes that expose no Node built-ins.
59
+ */
60
+ export declare function createStream(options: XmlStreamOptions): Transform;
61
+ export {};
@@ -0,0 +1 @@
1
+ import{Transform as e}from"node:stream";import{StringDecoder as t}from"node:string_decoder";function n(e){switch(e){case`amp`:return`&`;case`lt`:return`<`;case`gt`:return`>`;case`quot`:return`"`;case`apos`:return`'`}if(e.charCodeAt(0)===35){let t=e.charCodeAt(1)===120||e.charCodeAt(1)===88?parseInt(e.slice(2),16):parseInt(e.slice(1),10);if(Number.isFinite(t)&&t>=0&&t<=1114111)return String.fromCodePoint(t)}return null}function r(e){let t=e.indexOf(`&`);if(t<0)return e;let r=e.slice(0,t);for(;t<e.length;){let i=e.indexOf(`;`,t+1);if(i<0){r+=e.slice(t);break}let a=n(e.slice(t+1,i));if(r+=a===null?e.slice(t,i+1):a,t=e.indexOf(`&`,i+1),t<0){r+=e.slice(i+1);break}r+=e.slice(i+1,t)}return r}function i(e){if(e.indexOf(`<![CDATA[`)<0)return e.indexOf(`&`)<0?e:r(e);let t=``,n=0;for(;n<e.length;){let i=e.indexOf(`<![CDATA[`,n);if(i<0){t+=r(e.slice(n));break}i>n&&(t+=r(e.slice(n,i)));let a=e.indexOf(`]]>`,i+9);if(a<0){t+=e.slice(i+9);break}t+=e.slice(i+9,a),n=a+3}return t}function a(e){let t=0,n=e.length;return e.charCodeAt(0)===60&&(t=e.charCodeAt(1)===47?2:1),e.charCodeAt(n-1)===62&&n--,e.slice(t,n)}function o(e){return e===32||e===9||e===10||e===13}var s=class{constructor(e){if(this.queryParent=!1,this.progressive=!1,this.retain=`all`,this.keepPaths=/* @__PURE__ */ new Set,this.parentMap=/* @__PURE__ */ new WeakMap,this.evtListeners={},this.openListenerCount=0,this.watchedPaths=/* @__PURE__ */ new Set,this.watchedChildren=/* @__PURE__ */ new Map,this.watchedTopLevel=/* @__PURE__ */ new Set,this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.basePath=``,this.active=!1,e&&(this.queryParent=e.queryParent===!0,this.progressive=e.progressive===!0,e.retain&&(this.retain=e.retain),e.keep))for(let t of e.keep)this.keepPaths.add(a(t))}write(e){return this.active||this.begin(void 0,void 0),e.length===0||(this.buffer.length===0?this.buffer=e:this.buffer+=e,this.consume(!1)),this}end(){if(!this.active)return[];this.consume(!0),this.finish();let e=this.roots;return this.reset(),e}parse(e,t,n){return this.begin(t,n),this.write(e),this.end()}begin(e,t){this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.basePath=t||``,this.top=e?{node:e,fullTag:this.basePath,textStart:0}:void 0,this.rootOwner=this.top,this.parentMap=/* @__PURE__ */ new WeakMap,this.active=!0}reset(){this.buffer=``,this.cursor=0,this.stack=[],this.roots=[],this.top=void 0,this.rootOwner=void 0,this.basePath=``,this.active=!1}attach(e,t,n){if(this.retain===`none`&&!n)return;if(t===void 0){this.retain===`all`&&this.roots.push(e);return}let r=t.children;r?r.push(e):t.children=[e]}consume(e){let t=this.buffer,n=t.length,r=this.progressive,a=r&&this.openListenerCount>0,o=this.cursor,s=-1;for(;o<n;){let c=t.indexOf(`<`,o);if(c<0||(s=c,o=c+1,o>=n))break;let l=t.charCodeAt(o);if(l===47){let a=t.indexOf(`>`,o);if(a<0){if(!e)break;o=n;break}let l=this.stack.pop();if(o=a+1,s=-1,!l)continue;let u=l.node,d=this.stack.length?this.stack[this.stack.length-1]:this.rootOwner,f=d?d.node:void 0;!l.hasChild&&c>l.textStart&&(u.text=i(t.slice(l.textStart,c))),r&&this.watchedPaths.has(l.fullTag)&&this.emit(`</`+l.fullTag+`>`,u,f),this.attach(u,f,d?d.keep===!0:!1),d&&(d.hasChild=!0),this.top=d;continue}if(l===63){let r=t.indexOf(`>`,o);if(r<0){if(!e)break;o=n;break}o=r+1,s=-1;continue}if(l===33){let r;if(t.charCodeAt(o+1)===45&&t.charCodeAt(o+2)===45?(r=t.indexOf(`-->`,o+3),r=r<0?-1:r+3):t.startsWith(`![CDATA[`,o)?(r=t.indexOf(`]]>`,o+9),r=r<0?-1:r+3):(r=t.indexOf(`>`,o),r=r<0?-1:r+1),r<0){if(!e)break;o=n;break}o=r,s=-1;continue}let u=this.parseOpenTag(t,o,n,e);if(u===null)break;o=u.end,s=-1;let d=u.node,f=u.tag,p=this.top?this.top.node:void 0;if(this.queryParent&&p&&this.parentMap.set(d,p),u.selfClose){if(r){let e=this.top?this.top.fullTag:this.basePath,t=e?this.watchedChildren.get(e):this.watchedTopLevel;if(t&&t.has(f)){let t=e?e+`.`+f:f;a&&this.emit(`<`+t+`>`,d,p),this.emit(`</`+t+`>`,d,p)}}this.attach(d,p,this.top?this.top.keep===!0:!1),this.top&&(this.top.hasChild=!0);continue}let m=this.top?this.top.fullTag?this.top.fullTag+`.`+f:f:this.basePath?this.basePath+`.`+f:f;a&&this.watchedPaths.has(m)&&this.emit(`<`+m+`>`,d,p);let h=this.top!==void 0&&this.top.keep===!0||this.keepPaths.has(m),g={node:d,fullTag:m,textStart:o,keep:h};this.stack.push(g),this.top=g}this.cursor=e?n:s>=0?s:o,e||this.compact()}parseOpenTag(e,t,n,i){let a=t;for(;a<n;){let t=e.charCodeAt(a);if(o(t)||t===47||t===62)break;a++}if(a>=n&&!i)return null;let s=e.slice(t,a),c={tagName:s},l=!1;for(;a<n;){let t=e.charCodeAt(a);for(;a<n&&o(t);)t=e.charCodeAt(++a);if(a>=n)break;if(t===62)return a++,{node:c,tag:s,selfClose:l,end:a};if(t===47){l=!0,a++;continue}let u=a;for(;a<n&&(t=e.charCodeAt(a),!(t===61||o(t)||t===62||t===47));)a++;if(a>=n&&!i)return null;let d=e.slice(u,a);for(;a<n&&o(e.charCodeAt(a));)a++;if(a>=n&&!i)return null;if(e.charCodeAt(a)===61){for(a++;a<n&&o(e.charCodeAt(a));)a++;if(a>=n&&!i)return null;let t=e.charCodeAt(a);if(t===34||t===39){a++;let o=e.indexOf(t===34?`"`:`'`,a);if(o<0){if(!i)return null;o=n}let s=e.slice(a,o);c[d]=s.indexOf(`&`)<0?s:r(s),a=o+1}}else d&&(c[d]=``)}return i?{node:c,tag:s,selfClose:l,end:a}:null}finish(){let e=this.buffer,t=e.length;for(;this.stack.length>0;){let n=this.stack.pop(),r=n.node;!n.hasChild&&t>n.textStart&&(r.text=i(e.slice(n.textStart,t)),t=n.textStart);let a=this.stack.length?this.stack[this.stack.length-1]:this.rootOwner,o=a?a.node:void 0;this.attach(r,o,a?a.keep===!0:!1)}}compact(){let e=this.cursor;for(let t=this.stack.length-1;t>=0;t--){let n=this.stack[t];if(!n.hasChild){e=n.textStart;break}}if(!(e<=0)){this.buffer=this.buffer.slice(e),this.cursor-=e;for(let t=0;t<this.stack.length;t++){let n=this.stack[t];n.textStart=n.textStart>e?n.textStart-e:0}this.rootOwner&&(this.rootOwner.textStart=0)}}getParent(e){return this.queryParent?this.parentMap.get(e):null}addListener(e,t){this.$addListener(e,t)}removeListener(e,t){this.$removeListener(e,t)}on(e,t){this.addListener(e,t)}off(e,t){this.removeListener(e,t)}$addListener(e,t){let n=this.evtListeners[e];if(n?n.push(t):this.evtListeners[e]=[t],e.charCodeAt(1)!==47&&this.openListenerCount++,e.charCodeAt(0)===60){let t=e.slice(e.charCodeAt(1)===47?2:1,e.length-1);this.watchedPaths.add(t);let n=t.lastIndexOf(`.`);if(n<0)this.watchedTopLevel.add(t);else{let e=t.slice(0,n),r=this.watchedChildren.get(e);r||(r=/* @__PURE__ */ new Set,this.watchedChildren.set(e,r)),r.add(t.slice(n+1))}}}$removeListener(e,t){let n=this.evtListeners[e],r=-1;n&&(r=n.indexOf(t)),r>=0&&(n.splice(r,1),e.charCodeAt(1)!==47&&this.openListenerCount--)}emit(e,t,n){let r=this.evtListeners[e];if(r)for(let e=0;e<r.length;e++)r[e](t,n)}};function c(e){if(typeof e!=`string`||e.length===0)throw TypeError(`createStream() requires { event }, e.g. { event: "</root.item>" }`);return e}function l(n){let r=c(n&&n.event),i=new s({...n,progressive:!0,retain:`none`,keep:[r]}),a=new t(`utf8`),o=new e({readableObjectMode:!0,transform(e,t,n){try{i.write(typeof e==`string`?e:a.write(e)),n()}catch(e){n(e)}},flush(e){try{let t=a.end();t.length>0&&i.write(t),i.end(),e()}catch(t){e(t)}}});return i.on(r,e=>{o.push(e)}),o}export{l as createStream};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xml-event-parser",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Tiny dependency-free XML parser with a tree and path-based events in one pass",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,9 +13,15 @@
13
13
  "keywords": [
14
14
  "xml",
15
15
  "parser",
16
- "events",
17
- "tree",
18
16
  "streaming",
17
+ "sax",
18
+ "event",
19
+ "junit",
20
+ "test-report",
21
+ "sitemap",
22
+ "gpx",
23
+ "edge",
24
+ "cloudflare-workers",
19
25
  "zero-dependency"
20
26
  ],
21
27
  "main": "dist/index.cjs",
@@ -25,6 +31,7 @@
25
31
  "typecheck": "tsc --noEmit",
26
32
  "bench:data": "node bench/generate.mjs",
27
33
  "bench": "node --expose-gc bench/run.mjs",
34
+ "bench:memory": "npm run build && node bench/memory.mjs",
28
35
  "prepublishOnly": "npm run build && npm test"
29
36
  },
30
37
  "license": "MIT",
@@ -38,6 +45,7 @@
38
45
  "access": "public"
39
46
  },
40
47
  "devDependencies": {
48
+ "@types/node": "^26.6.6",
41
49
  "fast-xml-parser": "^5.12.1",
42
50
  "htmlparser2": "^12.0.0",
43
51
  "rolldown": "^1.3.2",
@@ -54,6 +62,11 @@
54
62
  "types": "./dist/index.d.ts",
55
63
  "import": "./dist/index.mjs",
56
64
  "require": "./dist/index.cjs"
65
+ },
66
+ "./stream": {
67
+ "types": "./dist/stream.d.ts",
68
+ "import": "./dist/stream.mjs",
69
+ "require": "./dist/stream.cjs"
57
70
  }
58
71
  },
59
72
  "sideEffects": false,