xml-twig 1.2.94 → 1.3.1
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 +6 -6
- package/doc/twig.md +63 -6
- package/package.json +3 -2
- package/samples/bookstore.xml +1 -1
- package/samples/breakfast-menu.xml +3 -2
- package/samples/encoding-UTF-16BE.xml +0 -0
- package/samples/encoding-UTF-16LE.xml +0 -0
- package/samples/encoding-UTF-8.xml +2 -0
- package/samples/processingInstruction.xml +0 -1
- package/samples/speed-test.bat +17 -0
- package/samples/speed-test.js +22 -34
- package/samples/speed-test.txt +164 -0
- package/samples/xmlns.xml +2 -2
- package/test.js +43 -0
- package/twig.js +267 -143
package/README.md
CHANGED
|
@@ -18,22 +18,22 @@ When you need to read a XML file, then you have two principles:
|
|
|
18
18
|
This module tries to combine both principles. The XML document can be read in chunks and within a chunk you have all the nice features and functions you know from a DOM based parser.
|
|
19
19
|
|
|
20
20
|
## Dependencies
|
|
21
|
-
XML documents are read either with [sax](https://www.npmjs.com/package/sax)
|
|
21
|
+
XML documents are read either with [sax](https://www.npmjs.com/package/sax), [node-expat](https://www.npmjs.com/package/node-expat) or [saxophone](https://www.npmjs.com/package/saxophone) parser. More parser may be added in future releases. By default the `sax` parser is used.
|
|
22
22
|
|
|
23
|
-
**NOTE: The `node-expat`
|
|
23
|
+
**NOTE: The `node-expat` and `saxophone` modules are not automatically installed with this module. Install the parser by yourself, if you like to use it**
|
|
24
24
|
|
|
25
25
|
## Installation
|
|
26
26
|
|
|
27
|
-
Install module like any other node module and optionally `node-expat`:
|
|
27
|
+
Install module like any other node module and optionally `node-expat` and/or `saxophone`:
|
|
28
28
|
```bash
|
|
29
29
|
npm install xml-twig
|
|
30
30
|
|
|
31
31
|
# and optionally
|
|
32
32
|
npm install node-expat
|
|
33
|
+
npm install saxophone
|
|
33
34
|
|
|
34
35
|
```
|
|
35
|
-
In my tests I parsed a
|
|
36
|
-
|
|
36
|
+
In my tests I parsed a 900 MB big XML file, the `node-expat` is faster than `sax` (node-expat: around 2:50 Minutes, sax: around 4:10 Minutes). However, you may run into problems when you try to install the `node-expat` parser. That's the reason why `node-expat` parser is not installed automatically. `saxophone` is even a little faster (around 2:10 Minutes) than `node-expat`, however `saxophone` lacks some functions and is not fully compliant to XML standards.
|
|
37
37
|
|
|
38
38
|
## How to use it
|
|
39
39
|
|
|
@@ -392,7 +392,7 @@ This `xml-twig` module focus on reading a XML files. In principle it would be po
|
|
|
392
392
|
|
|
393
393
|
Accessing Twig-Elements by [XML-Path](https://www.w3.org/TR/xpath/) language is not supported. One reason it, the `Twig` class models more an [Element](https://www.w3schools.com/xml/xml_elements.asp) rather than a [Node](https://www.w3schools.com/xml/dom_nodes.asp) which would be more generic.
|
|
394
394
|
|
|
395
|
-
Despite [W3C Recommendations](https://www.w3.org/TR/xml/#charencoding) ("All XML processors MUST be able to read entities in both the UTF-8 and UTF-16 encodings"), the `sax`
|
|
395
|
+
Despite [W3C Recommendations](https://www.w3.org/TR/xml/#charencoding) ("All XML processors MUST be able to read entities in both the UTF-8 and UTF-16 encodings"), the `sax` and `saxophone` parsers do not support UTF-16 encodings. When you have a XML-File encoded in UTF-16, then you must use the `expat` parser.
|
|
396
396
|
|
|
397
397
|
|
|
398
398
|
|
package/doc/twig.md
CHANGED
|
@@ -34,6 +34,15 @@
|
|
|
34
34
|
<dt><a href="#createParser">createParser(handler, [options])</a> ⇒ <code><a href="#Parser">Parser</a></code></dt>
|
|
35
35
|
<dd><p>Create a new Twig parser</p>
|
|
36
36
|
</dd>
|
|
37
|
+
<dt><a href="#onStart">onStart(binds, node, attrs)</a></dt>
|
|
38
|
+
<dd><p>Common Event hanlder for starting tag</p>
|
|
39
|
+
</dd>
|
|
40
|
+
<dt><a href="#onClose">onClose(handler, options, name)</a></dt>
|
|
41
|
+
<dd><p>Common Event hanlder for closing tag</p>
|
|
42
|
+
</dd>
|
|
43
|
+
<dt><a href="#reset">reset()</a></dt>
|
|
44
|
+
<dd><p>Reset global variable if one like to parse multiple files</p>
|
|
45
|
+
</dd>
|
|
37
46
|
</dl>
|
|
38
47
|
|
|
39
48
|
## Typedefs
|
|
@@ -77,7 +86,7 @@ You can specify a <code>function</code> or a <code>event</code> name</p>
|
|
|
77
86
|
<dt><a href="#ElementConditionFilter">ElementConditionFilter</a> ⇒ <code>boolean</code></dt>
|
|
78
87
|
<dd><p>Custom filter function to select desired elements</p>
|
|
79
88
|
</dd>
|
|
80
|
-
<dt><a href="#Parser">Parser</a> ⇒ <code><a href="https://www.npmjs.com/package/sax">sax</a></code> | <code><a href="https://www.npmjs.com/package/node-expat">node-expat</a></code></dt>
|
|
89
|
+
<dt><a href="#Parser">Parser</a> ⇒ <code><a href="https://www.npmjs.com/package/sax">sax</a></code> | <code><a href="https://www.npmjs.com/package/node-expat">node-expat</a></code> | <code><a href="https://www.npmjs.com/package/saxophone">saxophone</a></code></dt>
|
|
81
90
|
<dd></dd>
|
|
82
91
|
<dt><a href="#AttributeCondition">AttributeCondition</a> : <code>string</code> | <code>RegExp</code> | <code><a href="#AttributeConditionFilter">AttributeConditionFilter</a></code></dt>
|
|
83
92
|
<dd><p>Optional condition to get attributes<br> </p>
|
|
@@ -134,6 +143,7 @@ You can specify a <code>function</code> or a <code>event</code> name</p>
|
|
|
134
143
|
* [.pin](#Twig+pin)
|
|
135
144
|
* [.pinned](#Twig+pinned) ⇒ <code>boolean</code>
|
|
136
145
|
* [.close](#Twig+close)
|
|
146
|
+
* [.debug](#Twig+debug) ⇒ <code>string</code>
|
|
137
147
|
* [.addChild](#Twig+addChild) ℗
|
|
138
148
|
* [.writer](#Twig+writer) ⇒ [<code>XMLWriter</code>](https://www.npmjs.com/package/xml-writer)
|
|
139
149
|
* [.attr](#Twig+attr) ⇒ <code>string</code> \| <code>number</code> \| <code>object</code>
|
|
@@ -346,6 +356,13 @@ Checks if element is pinned
|
|
|
346
356
|
Closes the element
|
|
347
357
|
|
|
348
358
|
**Kind**: instance property of [<code>Twig</code>](#Twig)
|
|
359
|
+
<a name="Twig+debug"></a>
|
|
360
|
+
|
|
361
|
+
### twig.debug ⇒ <code>string</code>
|
|
362
|
+
XML-Twig for dummies :-)
|
|
363
|
+
|
|
364
|
+
**Kind**: instance property of [<code>Twig</code>](#Twig)
|
|
365
|
+
**Returns**: <code>string</code> - The XML-Tree which is currently available in RAM - no valid XML Structure
|
|
349
366
|
<a name="Twig+addChild"></a>
|
|
350
367
|
|
|
351
368
|
### twig.addChild ℗
|
|
@@ -743,6 +760,7 @@ Common function to filter Twig element
|
|
|
743
760
|
* [.pin](#Twig+pin)
|
|
744
761
|
* [.pinned](#Twig+pinned) ⇒ <code>boolean</code>
|
|
745
762
|
* [.close](#Twig+close)
|
|
763
|
+
* [.debug](#Twig+debug) ⇒ <code>string</code>
|
|
746
764
|
* [.addChild](#Twig+addChild) ℗
|
|
747
765
|
* [.writer](#Twig+writer) ⇒ [<code>XMLWriter</code>](https://www.npmjs.com/package/xml-writer)
|
|
748
766
|
* [.attr](#Twig+attr) ⇒ <code>string</code> \| <code>number</code> \| <code>object</code>
|
|
@@ -955,6 +973,13 @@ Checks if element is pinned
|
|
|
955
973
|
Closes the element
|
|
956
974
|
|
|
957
975
|
**Kind**: instance property of [<code>Twig</code>](#Twig)
|
|
976
|
+
<a name="Twig+debug"></a>
|
|
977
|
+
|
|
978
|
+
### twig.debug ⇒ <code>string</code>
|
|
979
|
+
XML-Twig for dummies :-)
|
|
980
|
+
|
|
981
|
+
**Kind**: instance property of [<code>Twig</code>](#Twig)
|
|
982
|
+
**Returns**: <code>string</code> - The XML-Tree which is currently available in RAM - no valid XML Structure
|
|
958
983
|
<a name="Twig+addChild"></a>
|
|
959
984
|
|
|
960
985
|
### twig.addChild ℗
|
|
@@ -1409,6 +1434,38 @@ Create a new Twig parser
|
|
|
1409
1434
|
| handler | [<code>TwigHandler</code>](#TwigHandler) \| [<code>Array.<TwigHandler></code>](#TwigHandler) | Object or array of element specification and function to handle elements |
|
|
1410
1435
|
| [options] | [<code>ParserOptions</code>](#ParserOptions) | Object of optional options |
|
|
1411
1436
|
|
|
1437
|
+
<a name="onStart"></a>
|
|
1438
|
+
|
|
1439
|
+
## onStart(binds, node, attrs)
|
|
1440
|
+
Common Event hanlder for starting tag
|
|
1441
|
+
|
|
1442
|
+
**Kind**: global function
|
|
1443
|
+
|
|
1444
|
+
| Param | Type | Description |
|
|
1445
|
+
| --- | --- | --- |
|
|
1446
|
+
| binds | <code>object</code> | Additional parameter object |
|
|
1447
|
+
| node | <code>object</code> \| <code>string</code> | Node or Node name |
|
|
1448
|
+
| attrs | <code>object</code> | Node Attributes |
|
|
1449
|
+
|
|
1450
|
+
<a name="onClose"></a>
|
|
1451
|
+
|
|
1452
|
+
## onClose(handler, options, name)
|
|
1453
|
+
Common Event hanlder for closing tag
|
|
1454
|
+
|
|
1455
|
+
**Kind**: global function
|
|
1456
|
+
|
|
1457
|
+
| Param | Type | Description |
|
|
1458
|
+
| --- | --- | --- |
|
|
1459
|
+
| handler | [<code>TwigHandler</code>](#TwigHandler) \| [<code>Array.<TwigHandler></code>](#TwigHandler) | Object or array of element specification and function to handle elements |
|
|
1460
|
+
| options | [<code>ParserOptions</code>](#ParserOptions) | Object of optional options |
|
|
1461
|
+
| name | <code>string</code> | Event handler parameter |
|
|
1462
|
+
|
|
1463
|
+
<a name="reset"></a>
|
|
1464
|
+
|
|
1465
|
+
## reset()
|
|
1466
|
+
Reset global variable if one like to parse multiple files
|
|
1467
|
+
|
|
1468
|
+
**Kind**: global function
|
|
1412
1469
|
<a name="ParserOptions"></a>
|
|
1413
1470
|
|
|
1414
1471
|
## ParserOptions
|
|
@@ -1420,7 +1477,7 @@ Optional settings for the Twig parser
|
|
|
1420
1477
|
|
|
1421
1478
|
| Name | Type | Description |
|
|
1422
1479
|
| --- | --- | --- |
|
|
1423
|
-
| [method] | <code>'sax'</code> \| <code>'expat'</code> | The underlying parser. Either `'sax'` or `'
|
|
1480
|
+
| [method] | <code>'sax'</code> \| <code>'expat'</code> \| <code>'saxophone'</code> | The underlying parser. Either `'sax'`, `'expat'` or `'saxophone'`. |
|
|
1424
1481
|
| [xmlns] | <code>boolean</code> | If `true`, then namespaces are accessible by `namespace` property. |
|
|
1425
1482
|
| [trim] | <code>boolean</code> | If `true`, then turn any whitespace into a single space. Text and comments are trimmed. |
|
|
1426
1483
|
| [resumeAfterError] | <code>boolean</code> | If `true` then parser continues reading after an error. Otherwise it throws exception. |
|
|
@@ -1494,15 +1551,15 @@ Custom filter function to select desired elements
|
|
|
1494
1551
|
|
|
1495
1552
|
<a name="Parser"></a>
|
|
1496
1553
|
|
|
1497
|
-
## Parser ⇒ [<code>sax</code>](https://www.npmjs.com/package/sax) \| [<code>node-expat</code>](https://www.npmjs.com/package/node-expat)
|
|
1554
|
+
## Parser ⇒ [<code>sax</code>](https://www.npmjs.com/package/sax) \| [<code>node-expat</code>](https://www.npmjs.com/package/node-expat) \| [<code>saxophone</code>](https://www.npmjs.com/package/saxophone)
|
|
1498
1555
|
**Kind**: global typedef
|
|
1499
|
-
**Returns**: [<code>sax</code>](https://www.npmjs.com/package/sax) \| [<code>node-expat</code>](https://www.npmjs.com/package/node-expat) - The parser Object
|
|
1556
|
+
**Returns**: [<code>sax</code>](https://www.npmjs.com/package/sax) \| [<code>node-expat</code>](https://www.npmjs.com/package/node-expat) \| [<code>saxophone</code>](https://www.npmjs.com/package/saxophone) - The parser Object
|
|
1500
1557
|
**Properties**
|
|
1501
1558
|
|
|
1502
1559
|
| Name | Type | Description |
|
|
1503
1560
|
| --- | --- | --- |
|
|
1504
|
-
| [currentLine] | <code>number</code> | The currently processed line in the XML-File |
|
|
1505
|
-
| [currentColumn] | <code>number</code> | The currently processed column in the XML-File |
|
|
1561
|
+
| [currentLine] | <code>number</code> | The currently processed line in the XML-File.<br/>Not available on `saxophone` parser. |
|
|
1562
|
+
| [currentColumn] | <code>number</code> | The currently processed column in the XML-File.<br/>Not available on `saxophone` parser. |
|
|
1506
1563
|
|
|
1507
1564
|
<a name="AttributeCondition"></a>
|
|
1508
1565
|
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
},
|
|
6
6
|
"name": "xml-twig",
|
|
7
7
|
"description": "Node module for processing huge XML documents in tree mode",
|
|
8
|
-
"version": "1.
|
|
8
|
+
"version": "1.3.1",
|
|
9
9
|
"main": "twig.js",
|
|
10
10
|
"directories": {
|
|
11
11
|
"doc": "doc"
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"jsdoc-to-markdown": "^8.0.0",
|
|
15
15
|
"luxon": "^2.1.1",
|
|
16
|
-
"node-expat": "^2.4.0"
|
|
16
|
+
"node-expat": "^2.4.0",
|
|
17
|
+
"saxophone": "^0.8.0"
|
|
17
18
|
},
|
|
18
19
|
"scripts": {
|
|
19
20
|
"test": "node demo.js"
|
package/samples/bookstore.xml
CHANGED
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
<food>
|
|
20
20
|
<name>Berry-Berry Belgian Waffles</name>
|
|
21
21
|
<price>$8.95</price>
|
|
22
|
-
<description
|
|
23
|
-
|
|
22
|
+
<description><![CDATA[
|
|
23
|
+
Belgian waffles covered with assorted <bold>fresh</bold> berries and whipped cream
|
|
24
|
+
]]>
|
|
24
25
|
</description>
|
|
25
26
|
<calories>900</calories>
|
|
26
27
|
</food>
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
node speed-test.js expat
|
|
2
|
+
node speed-test.js expat
|
|
3
|
+
node speed-test.js expat
|
|
4
|
+
node speed-test.js expat
|
|
5
|
+
node speed-test.js expat
|
|
6
|
+
|
|
7
|
+
node speed-test.js sax
|
|
8
|
+
node speed-test.js sax
|
|
9
|
+
node speed-test.js sax
|
|
10
|
+
node speed-test.js sax
|
|
11
|
+
node speed-test.js sax
|
|
12
|
+
|
|
13
|
+
node speed-test.js saxophone
|
|
14
|
+
node speed-test.js saxophone
|
|
15
|
+
node speed-test.js saxophone
|
|
16
|
+
node speed-test.js saxophone
|
|
17
|
+
node speed-test.js saxophone
|
package/samples/speed-test.js
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
const { DateTime } = require('luxon');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
-
const twig = require('
|
|
3
|
+
const twig = require('../twig.js');
|
|
4
4
|
|
|
5
5
|
let NE = 0;
|
|
6
6
|
const startTime = DateTime.now();
|
|
7
|
-
|
|
8
|
-
let parser = twig.createParser([{ tag: 'subsession', function: anyHandler }], { method:
|
|
9
|
-
let reader = fs.createReadStream(`${__dirname}/
|
|
7
|
+
|
|
8
|
+
let parser = twig.createParser([{ tag: 'subsession', function: anyHandler }], { method: process.argv[2] })
|
|
9
|
+
let reader = fs.createReadStream(`${__dirname}/20240304015234.9-MSRAN.xml`);
|
|
10
|
+
console.log(`Starting with ${parser.method}...`)
|
|
10
11
|
reader.pipe(parser);
|
|
11
12
|
|
|
12
13
|
function anyHandler(elt) {
|
|
13
14
|
NE++;
|
|
14
15
|
if (NE % 25 === 0) {
|
|
15
16
|
let d = DateTime.now().diff(startTime);
|
|
16
|
-
console.log(
|
|
17
|
+
console.log(`\t${NE} NE in ${d.toFormat('mm:ss.S')}`);
|
|
17
18
|
}
|
|
18
19
|
elt.purge();
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
reader.on('end', () => {
|
|
22
23
|
let d = DateTime.now().diff(startTime);
|
|
23
|
-
console.log(`All done in ${d.toFormat('mm:ss.S')}`);
|
|
24
|
+
console.log(`All done with ${parser.method} in ${d.toFormat('mm:ss.S')}`);
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
|
|
@@ -30,38 +31,25 @@ reader.on('end', () => {
|
|
|
30
31
|
* Results
|
|
31
32
|
**********************
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
125 NE in 01:29.996
|
|
39
|
-
150 NE in 01:46.358
|
|
40
|
-
175 NE in 02:02.486
|
|
41
|
-
200 NE in 02:21.25
|
|
42
|
-
All done in 02:21.31
|
|
34
|
+
All done with expat in 02:52.676
|
|
35
|
+
All done with expat in 02:53.644
|
|
36
|
+
All done with expat in 02:54.894
|
|
37
|
+
All done with expat in 03:02.545
|
|
38
|
+
All done with expat in 03:23.34
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
125 NE in 02:47.781
|
|
50
|
-
150 NE in 03:17.601
|
|
51
|
-
175 NE in 03:48.676
|
|
52
|
-
200 NE in 04:22.523
|
|
53
|
-
All done in 04:22.528
|
|
40
|
+
All done with sax in 04:28.915
|
|
41
|
+
All done with sax in 04:05.760
|
|
42
|
+
All done with sax in 04:06.718
|
|
43
|
+
All done with sax in 04:03.962
|
|
44
|
+
All done with sax in 03:54.540
|
|
54
45
|
|
|
46
|
+
All done with saxophone in 02:12.565
|
|
47
|
+
All done with saxophone in 02:10.86
|
|
48
|
+
All done with saxophone in 02:13.447
|
|
49
|
+
All done with saxophone in 02:12.14
|
|
50
|
+
All done with saxophone in 02:08.658
|
|
55
51
|
|
|
56
52
|
Good old Perl XML::Twig
|
|
57
|
-
25 NE in 1:14
|
|
58
|
-
50 NE in 2:03
|
|
59
|
-
75 NE in 3:43
|
|
60
|
-
100 NE in 5:02
|
|
61
|
-
125 NE in 6:12
|
|
62
|
-
150 NE in 7:16
|
|
63
|
-
175 NE in 8:17
|
|
64
|
-
200 NE in 9:24
|
|
65
53
|
All done in 9:24
|
|
66
54
|
|
|
67
55
|
*/
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js expat
|
|
2
|
+
Starting with expat...
|
|
3
|
+
25 NE in 00:15.36
|
|
4
|
+
50 NE in 00:33.382
|
|
5
|
+
75 NE in 00:50.253
|
|
6
|
+
100 NE in 01:13.944
|
|
7
|
+
125 NE in 01:42.205
|
|
8
|
+
150 NE in 02:13.746
|
|
9
|
+
175 NE in 02:35.650
|
|
10
|
+
All done with expat in 02:52.676
|
|
11
|
+
|
|
12
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js expat
|
|
13
|
+
Starting with expat...
|
|
14
|
+
25 NE in 00:18.216
|
|
15
|
+
50 NE in 00:37.92
|
|
16
|
+
75 NE in 00:54.888
|
|
17
|
+
100 NE in 01:17.224
|
|
18
|
+
125 NE in 01:44.272
|
|
19
|
+
150 NE in 02:15.283
|
|
20
|
+
175 NE in 02:36.463
|
|
21
|
+
All done with expat in 02:53.644
|
|
22
|
+
|
|
23
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js expat
|
|
24
|
+
Starting with expat...
|
|
25
|
+
25 NE in 00:18.639
|
|
26
|
+
50 NE in 00:39.182
|
|
27
|
+
75 NE in 00:59.446
|
|
28
|
+
100 NE in 01:19.566
|
|
29
|
+
125 NE in 01:46.400
|
|
30
|
+
150 NE in 02:16.608
|
|
31
|
+
175 NE in 02:37.242
|
|
32
|
+
All done with expat in 02:54.894
|
|
33
|
+
|
|
34
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js expat
|
|
35
|
+
Starting with expat...
|
|
36
|
+
25 NE in 00:17.702
|
|
37
|
+
50 NE in 00:36.765
|
|
38
|
+
75 NE in 00:55.178
|
|
39
|
+
100 NE in 01:16.259
|
|
40
|
+
125 NE in 01:47.37
|
|
41
|
+
150 NE in 02:18.862
|
|
42
|
+
175 NE in 02:42.671
|
|
43
|
+
All done with expat in 03:02.545
|
|
44
|
+
|
|
45
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js expat
|
|
46
|
+
Starting with expat...
|
|
47
|
+
25 NE in 00:18.746
|
|
48
|
+
50 NE in 00:38.461
|
|
49
|
+
75 NE in 00:56.901
|
|
50
|
+
100 NE in 01:17.314
|
|
51
|
+
125 NE in 01:44.938
|
|
52
|
+
150 NE in 02:26.995
|
|
53
|
+
175 NE in 02:58.149
|
|
54
|
+
All done with expat in 03:23.34
|
|
55
|
+
|
|
56
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js sax
|
|
57
|
+
Starting with sax...
|
|
58
|
+
25 NE in 00:33.781
|
|
59
|
+
50 NE in 01:08.679
|
|
60
|
+
75 NE in 01:39.504
|
|
61
|
+
100 NE in 02:11.68
|
|
62
|
+
125 NE in 02:51.186
|
|
63
|
+
150 NE in 03:36.142
|
|
64
|
+
175 NE in 04:04.731
|
|
65
|
+
All done with sax in 04:28.915
|
|
66
|
+
|
|
67
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js sax
|
|
68
|
+
Starting with sax...
|
|
69
|
+
25 NE in 00:25.21
|
|
70
|
+
50 NE in 00:52.164
|
|
71
|
+
75 NE in 01:17.550
|
|
72
|
+
100 NE in 01:51.854
|
|
73
|
+
125 NE in 02:30.192
|
|
74
|
+
150 NE in 03:11.461
|
|
75
|
+
175 NE in 03:39.338
|
|
76
|
+
All done with sax in 04:05.760
|
|
77
|
+
|
|
78
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js sax
|
|
79
|
+
Starting with sax...
|
|
80
|
+
25 NE in 00:25.778
|
|
81
|
+
50 NE in 00:52.603
|
|
82
|
+
75 NE in 01:19.686
|
|
83
|
+
100 NE in 01:52.268
|
|
84
|
+
125 NE in 02:30.279
|
|
85
|
+
150 NE in 03:13.159
|
|
86
|
+
175 NE in 03:42.640
|
|
87
|
+
All done with sax in 04:06.718
|
|
88
|
+
|
|
89
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js sax
|
|
90
|
+
Starting with sax...
|
|
91
|
+
25 NE in 00:24.549
|
|
92
|
+
50 NE in 00:50.774
|
|
93
|
+
75 NE in 01:14.500
|
|
94
|
+
100 NE in 01:44.116
|
|
95
|
+
125 NE in 02:22.861
|
|
96
|
+
150 NE in 03:03.357
|
|
97
|
+
175 NE in 03:39.237
|
|
98
|
+
All done with sax in 04:03.962
|
|
99
|
+
|
|
100
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js sax
|
|
101
|
+
Starting with sax...
|
|
102
|
+
25 NE in 00:25.36
|
|
103
|
+
50 NE in 00:51.544
|
|
104
|
+
75 NE in 01:15.580
|
|
105
|
+
100 NE in 01:45.389
|
|
106
|
+
125 NE in 02:22.834
|
|
107
|
+
150 NE in 03:03.487
|
|
108
|
+
175 NE in 03:31.416
|
|
109
|
+
All done with sax in 03:54.540
|
|
110
|
+
|
|
111
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js saxophone
|
|
112
|
+
Starting with saxophone...
|
|
113
|
+
25 NE in 00:14.96
|
|
114
|
+
50 NE in 00:31.316
|
|
115
|
+
75 NE in 00:44.478
|
|
116
|
+
100 NE in 01:00.580
|
|
117
|
+
125 NE in 01:21.74
|
|
118
|
+
150 NE in 01:43.881
|
|
119
|
+
175 NE in 01:59.119
|
|
120
|
+
All done with saxophone in 02:12.565
|
|
121
|
+
|
|
122
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js saxophone
|
|
123
|
+
Starting with saxophone...
|
|
124
|
+
25 NE in 00:14.565
|
|
125
|
+
50 NE in 00:28.947
|
|
126
|
+
75 NE in 00:42.211
|
|
127
|
+
100 NE in 00:58.533
|
|
128
|
+
125 NE in 01:19.194
|
|
129
|
+
150 NE in 01:41.595
|
|
130
|
+
175 NE in 01:57.24
|
|
131
|
+
All done with saxophone in 02:10.86
|
|
132
|
+
|
|
133
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js saxophone
|
|
134
|
+
Starting with saxophone...
|
|
135
|
+
25 NE in 00:13.307
|
|
136
|
+
50 NE in 00:27.849
|
|
137
|
+
75 NE in 00:41.466
|
|
138
|
+
100 NE in 00:59.190
|
|
139
|
+
125 NE in 01:22.230
|
|
140
|
+
150 NE in 01:44.605
|
|
141
|
+
175 NE in 02:00.299
|
|
142
|
+
All done with saxophone in 02:13.447
|
|
143
|
+
|
|
144
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js saxophone
|
|
145
|
+
Starting with saxophone...
|
|
146
|
+
25 NE in 00:13.884
|
|
147
|
+
50 NE in 00:28.340
|
|
148
|
+
75 NE in 00:41.720
|
|
149
|
+
100 NE in 00:57.897
|
|
150
|
+
125 NE in 01:19.260
|
|
151
|
+
150 NE in 01:42.974
|
|
152
|
+
175 NE in 01:58.791
|
|
153
|
+
All done with saxophone in 02:12.14
|
|
154
|
+
|
|
155
|
+
C:\Developing\Source\xml-twig\samples>node speed-test.js saxophone
|
|
156
|
+
Starting with saxophone...
|
|
157
|
+
25 NE in 00:13.443
|
|
158
|
+
50 NE in 00:28.99
|
|
159
|
+
75 NE in 00:40.821
|
|
160
|
+
100 NE in 00:56.989
|
|
161
|
+
125 NE in 01:16.787
|
|
162
|
+
150 NE in 01:40.205
|
|
163
|
+
175 NE in 01:55.451
|
|
164
|
+
All done with saxophone in 02:08.658
|
package/samples/xmlns.xml
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
<f:width>80</f:width>
|
|
11
11
|
<f:length>120</f:length>
|
|
12
12
|
</f:table>
|
|
13
|
-
<book category="web">
|
|
13
|
+
<book xmlns:a="http://www.w3.org/" category="web">
|
|
14
14
|
<title lang="en">Learning XML</title>
|
|
15
|
-
<author>Erik T. Ray</author>
|
|
15
|
+
<a:author>Erik T. Ray</a:author>
|
|
16
16
|
<year>2003</year>
|
|
17
17
|
<price>39.95</price>
|
|
18
18
|
</book>
|
package/test.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const twig = require('./twig.js');
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
//const parser = twig.createParser({ tag: twig.Any, function: anyHandler }, { method: "sax" });
|
|
6
|
+
//const parser = twig.createParser({ tag: twig.Any, function: anyHandler }, { method: "expat" });
|
|
7
|
+
//const parser = twig.createParser({ tag: twig.Any, function: anyHandler }, { method: "saxophone" });
|
|
8
|
+
//fs.createReadStream(`${__dirname}/samples/bookstore.xml`).pipe(parser);
|
|
9
|
+
//fs.createReadStream(`${__dirname}/samples/breakfast-menu.xml`).pipe(parser);
|
|
10
|
+
|
|
11
|
+
//const parser = twig.createParser({ tag: twig.Any, function: nsHandler }, { method: "sax", xmlns: true });
|
|
12
|
+
//const parser = twig.createParser({ tag: twig.Any, function: nsHandler }, { method: "expat", xmlns: true });
|
|
13
|
+
//const parser = twig.createParser({ tag: twig.Any, function: nsHandler }, { method: "saxophone", xmlns: true });
|
|
14
|
+
//fs.createReadStream(`${__dirname}/samples/xmlns.xml`).pipe(parser);
|
|
15
|
+
|
|
16
|
+
//const parser = twig.createParser({ tag: twig.Root, function: piHandler }, { method: "sax" });
|
|
17
|
+
//const parser = twig.createParser({ tag: twig.Root, function: piHandler }, { method: "expat" });
|
|
18
|
+
//const parser = twig.createParser({ tag: twig.Root, function: piHandler }, { method: "saxophone" });
|
|
19
|
+
//fs.createReadStream(`${__dirname}/samples/processingInstruction.xml`).pipe(parser);
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
//fs.createReadStream(`${__dirname}/samples/encoding-UTF-8.xml`).pipe(parser);
|
|
23
|
+
//fs.createReadStream(`${__dirname}/samples/encoding-UTF-16LE.xml`).pipe(parser);
|
|
24
|
+
//fs.createReadStream(`${__dirname}/samples/encoding-UTF-16BE.xml`).pipe(parser);
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
function anyHandler(elt) {
|
|
28
|
+
console.log(`<${elt.name}> => ${elt.text} -> ${JSON.stringify(elt.attributes)}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
function nsHandler(elt) {
|
|
33
|
+
console.log(`${elt.name} => ${JSON.stringify(elt.namespace)} -> ${JSON.stringify(elt.attributes)}`);
|
|
34
|
+
//console.log(`${elt.name} => isRoot = ${elt.isRoot}`);
|
|
35
|
+
//console.log(`${elt.name} -> ${JSON.stringify(elt.PI)}`);
|
|
36
|
+
//console.log(`${elt.name}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function piHandler(elt) {
|
|
40
|
+
console.log(`${elt.name} -> ${JSON.stringify(elt.PI)}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
package/twig.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const SAX = 'sax';
|
|
2
2
|
const EXPAT = 'expat';
|
|
3
|
+
const SAXOPHONE = 'saxophone';
|
|
3
4
|
|
|
4
5
|
let tree;
|
|
5
6
|
let current;
|
|
@@ -19,6 +20,25 @@ let current;
|
|
|
19
20
|
* @see {@link https://www.npmjs.com/package/node-expat|node-expat}
|
|
20
21
|
*/
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @external saxophone
|
|
25
|
+
* @see {@link https://www.npmjs.com/package/saxophone|saxophone}
|
|
26
|
+
* @see {@link https://www.npmjs.com/package/@alexbosworth/saxophone|@alexbosworth/saxophone}
|
|
27
|
+
* @see {@link https://www.npmjs.com/package/@pirxpilot/saxophone|@pirxpilot/saxophone}
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @external libxmljs
|
|
32
|
+
* @todo Not yet implemented, maybe later. Not sure if `WritableStream` is supported
|
|
33
|
+
* @see {@link https://www.npmjs.com/package/libxmljs|libxmljs}
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @external sax-wasm
|
|
38
|
+
* @todo Not yet implemented, maybe later. Not sure if `WritableStream` is supported
|
|
39
|
+
* @see {@link https://www.npmjs.com/package/sax-wasm|sax-wasm}
|
|
40
|
+
*/
|
|
41
|
+
|
|
22
42
|
|
|
23
43
|
class RootHandler { }
|
|
24
44
|
class AnyHandler { }
|
|
@@ -40,7 +60,7 @@ const Any = new AnyHandler();
|
|
|
40
60
|
/**
|
|
41
61
|
* Optional settings for the Twig parser
|
|
42
62
|
* @typedef ParserOptions
|
|
43
|
-
* @property {'sax' | 'expat'} [method] - The underlying parser. Either `'sax'` or `'
|
|
63
|
+
* @property {'sax' | 'expat' | 'saxophone'} [method] - The underlying parser. Either `'sax'`, `'expat'` or `'saxophone'`.
|
|
44
64
|
* @property {boolean} [xmlns] - If `true`, then namespaces are accessible by `namespace` property.
|
|
45
65
|
* @property {boolean} [trim] - If `true`, then turn any whitespace into a single space. Text and comments are trimmed.
|
|
46
66
|
* @property {boolean} [resumeAfterError] - If `true` then parser continues reading after an error. Otherwise it throws exception.
|
|
@@ -103,11 +123,12 @@ const Any = new AnyHandler();
|
|
|
103
123
|
|
|
104
124
|
/**
|
|
105
125
|
* @typedef Parser
|
|
106
|
-
* @property {number} [currentLine] - The currently processed line in the XML-File
|
|
107
|
-
* @property {number} [currentColumn] - The currently processed column in the XML-File
|
|
108
|
-
* @returns {external:sax|external:node-expat} The parser Object
|
|
126
|
+
* @property {number} [currentLine] - The currently processed line in the XML-File.<br/>Not available on `saxophone` parser.
|
|
127
|
+
* @property {number} [currentColumn] - The currently processed column in the XML-File.<br/>Not available on `saxophone` parser.
|
|
128
|
+
* @returns {external:sax|external:node-expat|external:saxophone} The parser Object
|
|
109
129
|
*/
|
|
110
130
|
|
|
131
|
+
|
|
111
132
|
/**
|
|
112
133
|
* Create a new Twig parser
|
|
113
134
|
* @param {TwigHandler|TwigHandler[]} handler - Object or array of element specification and function to handle elements
|
|
@@ -119,7 +140,6 @@ function createParser(handler, options = {}) {
|
|
|
119
140
|
options = Object.assign({ method: SAX, xmlns: false, trim: true, resumeAfterError: false, partial: false }, options);
|
|
120
141
|
let parser;
|
|
121
142
|
let namespaces = {};
|
|
122
|
-
let closeEvent;
|
|
123
143
|
|
|
124
144
|
if (options.partial) {
|
|
125
145
|
const handle1 = Array.isArray(handler) ? handler : [handler];
|
|
@@ -142,44 +162,12 @@ function createParser(handler, options = {}) {
|
|
|
142
162
|
get() { return parser._parser.column + 1; }
|
|
143
163
|
});
|
|
144
164
|
|
|
145
|
-
|
|
146
|
-
parser.on("opentagstart",
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
current.setRoot(node.name);
|
|
152
|
-
} else {
|
|
153
|
-
let elt = new Twig(node.name, current);
|
|
154
|
-
if (options.partial) {
|
|
155
|
-
for (let hndl of Array.isArray(handler) ? handler : [handler]) {
|
|
156
|
-
if (typeof hndl.tag === 'string' && node.name === hndl.tag) {
|
|
157
|
-
elt.pin();
|
|
158
|
-
break;
|
|
159
|
-
} else if (hndl.tag instanceof RegExp && hndl.tag.test(node.name)) {
|
|
160
|
-
elt.pin();
|
|
161
|
-
break;
|
|
162
|
-
} else if (typeof hndl.tag === 'function' && hndl.tag(node.name, current ?? tree)) {
|
|
163
|
-
elt.pin();
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
if (options.xmlns) {
|
|
171
|
-
if (node.name.includes(':')) {
|
|
172
|
-
let prefix = node.name.split(':')[0];
|
|
173
|
-
if (namespaces[prefix] !== undefined) {
|
|
174
|
-
Object.defineProperty(current, 'namespace', {
|
|
175
|
-
value: { local: prefix, uri: namespaces[prefix] },
|
|
176
|
-
writable: false,
|
|
177
|
-
enumerable: true
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
});
|
|
165
|
+
parser.on("closetag", onClose.bind(null, handler, options));
|
|
166
|
+
parser.on("opentagstart", onStart.bind(null, {
|
|
167
|
+
handler: Array.isArray(handler) ? handler : [handler],
|
|
168
|
+
options: options,
|
|
169
|
+
namespaces: namespaces
|
|
170
|
+
}));
|
|
183
171
|
|
|
184
172
|
parser.on("processinginstruction", function (pi) {
|
|
185
173
|
if (pi.name === 'xml') {
|
|
@@ -195,7 +183,7 @@ function createParser(handler, options = {}) {
|
|
|
195
183
|
writable: false,
|
|
196
184
|
enumerable: true
|
|
197
185
|
});
|
|
198
|
-
} else {
|
|
186
|
+
} else if (tree.PI === undefined) {
|
|
199
187
|
Object.defineProperty(tree, 'PI', {
|
|
200
188
|
value: { target: pi.name, data: pi.body },
|
|
201
189
|
writable: false,
|
|
@@ -207,17 +195,21 @@ function createParser(handler, options = {}) {
|
|
|
207
195
|
parser.on("attribute", function (attr) {
|
|
208
196
|
if (options.xmlns && (attr.uri ?? '') !== '' && attr.local !== undefined) {
|
|
209
197
|
namespaces[attr.local] = attr.uri;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
198
|
+
if (current.name.includes(':')) {
|
|
199
|
+
Object.defineProperty(current, 'namespace', {
|
|
200
|
+
value: { local: attr.local, uri: attr.uri },
|
|
201
|
+
writable: false,
|
|
202
|
+
enumerable: true
|
|
203
|
+
});
|
|
204
|
+
} else {
|
|
205
|
+
current.attribute(attr.name, attr.value);
|
|
206
|
+
}
|
|
215
207
|
} else {
|
|
216
208
|
current.attribute(attr.name, attr.value);
|
|
217
209
|
}
|
|
218
210
|
});
|
|
219
211
|
parser.on("cdata", function (str) {
|
|
220
|
-
current.text =
|
|
212
|
+
current.text = options.trim ? str.trim() : str;
|
|
221
213
|
});
|
|
222
214
|
|
|
223
215
|
const hndl = Array.isArray(handler) ? handler : [handler];
|
|
@@ -237,52 +229,13 @@ function createParser(handler, options = {}) {
|
|
|
237
229
|
enumerable: true,
|
|
238
230
|
get() { return parser.parser.getCurrentColumnNumber(); }
|
|
239
231
|
});
|
|
240
|
-
closeEvent = "endElement";
|
|
241
232
|
|
|
242
|
-
parser.on("
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (tree === undefined) {
|
|
249
|
-
tree = new Twig(name, current, options.xmlns ? attr : attrs);
|
|
250
|
-
} else {
|
|
251
|
-
if (current.isRoot && current.name === undefined) {
|
|
252
|
-
current.setRoot(name);
|
|
253
|
-
} else {
|
|
254
|
-
let elt = new Twig(name, current, options.xmlns ? attr : attrs);
|
|
255
|
-
if (options.partial) {
|
|
256
|
-
for (let hndl of Array.isArray(handler) ? handler : [handler]) {
|
|
257
|
-
if (typeof hndl.tag === 'string' && name === hndl.tag) {
|
|
258
|
-
elt.pin();
|
|
259
|
-
break;
|
|
260
|
-
} else if (hndl.tag instanceof RegExp && hndl.tag.test(name)) {
|
|
261
|
-
elt.pin();
|
|
262
|
-
break;
|
|
263
|
-
} else if (typeof hndl.tag === 'function' && hndl.tag(name, current ?? tree)) {
|
|
264
|
-
elt.pin();
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
if (options.xmlns) {
|
|
272
|
-
for (let key of Object.keys(attrs).filter(x => x.startsWith('xmlns:')))
|
|
273
|
-
namespaces[key.split(':')[1]] = attrs[key];
|
|
274
|
-
if (name.includes(':')) {
|
|
275
|
-
let prefix = name.split(':')[0];
|
|
276
|
-
if (namespaces[prefix] !== undefined) {
|
|
277
|
-
Object.defineProperty(current, 'namespace', {
|
|
278
|
-
value: { local: prefix, uri: namespaces[prefix] },
|
|
279
|
-
writable: false,
|
|
280
|
-
enumerable: true
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
});
|
|
233
|
+
parser.on("endElement", onClose.bind(null, handler, options));
|
|
234
|
+
parser.on("startElement", onStart.bind(null, {
|
|
235
|
+
handler: Array.isArray(handler) ? handler : [handler],
|
|
236
|
+
options: options,
|
|
237
|
+
namespaces: namespaces
|
|
238
|
+
}));
|
|
286
239
|
|
|
287
240
|
parser.on('xmlDecl', function (version, encoding, standalone) {
|
|
288
241
|
tree = new Twig(null);
|
|
@@ -300,50 +253,82 @@ function createParser(handler, options = {}) {
|
|
|
300
253
|
parser.on('processingInstruction', function (target, data) {
|
|
301
254
|
tree.PI = { target: target, data: data };
|
|
302
255
|
});
|
|
303
|
-
} else {
|
|
304
|
-
throw new UnsupportedParser(options.method);
|
|
305
|
-
}
|
|
306
256
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
257
|
+
} else if (options.method === SAXOPHONE) {
|
|
258
|
+
const Saxophone = require('saxophone');
|
|
259
|
+
//const Saxophone = require('@alexbosworth/saxophone');
|
|
260
|
+
//const Saxophone = require('@pirxpilot/saxophone');
|
|
261
|
+
parser = new Saxophone();
|
|
262
|
+
|
|
263
|
+
parser.on("tagclose", onClose.bind(null, handler, options));
|
|
264
|
+
parser.on("tagopen", onStart.bind(null, {
|
|
265
|
+
handler: Array.isArray(handler) ? handler : [handler],
|
|
266
|
+
options: options,
|
|
267
|
+
namespaces: namespaces,
|
|
268
|
+
parser: parser,
|
|
269
|
+
Saxophone: Saxophone
|
|
270
|
+
}));
|
|
271
|
+
|
|
272
|
+
parser.on("cdata", function (str) {
|
|
273
|
+
current.text = options.trim ? str.contents.trim() : str.contents;
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
parser.on('processinginstruction', function (pi) {
|
|
277
|
+
if (pi.contents.startsWith('xml ')) {
|
|
278
|
+
let declaration = {};
|
|
279
|
+
for (let item of pi.contents.split(' ')) {
|
|
280
|
+
let [k, v] = item.split('=');
|
|
281
|
+
if (k === 'xml') continue;
|
|
282
|
+
declaration[k] = v.replaceAll('"', '').replaceAll("'", '');
|
|
283
|
+
}
|
|
284
|
+
tree = new Twig(null);
|
|
285
|
+
Object.defineProperty(tree, 'declaration', {
|
|
286
|
+
value: declaration,
|
|
287
|
+
writable: false,
|
|
288
|
+
enumerable: true
|
|
289
|
+
});
|
|
290
|
+
} else if (tree.PI === undefined) {
|
|
291
|
+
let instruction = { body: {} };
|
|
292
|
+
for (let item of pi.contents.split(' ')) {
|
|
293
|
+
let [k, v] = item.split('=');
|
|
294
|
+
if (v === undefined) {
|
|
295
|
+
instruction.name = k;
|
|
296
|
+
} else {
|
|
297
|
+
instruction.body[k] = v.replaceAll('"', '').replaceAll("'", '');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
Object.defineProperty(tree, 'PI', {
|
|
301
|
+
value: { target: instruction.name, data: instruction.body },
|
|
302
|
+
writable: false,
|
|
303
|
+
enumerable: true
|
|
304
|
+
});
|
|
332
305
|
}
|
|
333
|
-
}
|
|
334
306
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
} else {
|
|
310
|
+
throw new UnsupportedParser(options.method);
|
|
311
|
+
}
|
|
338
312
|
|
|
313
|
+
Object.defineProperty(parser, 'method', {
|
|
314
|
+
value: options.method,
|
|
315
|
+
writable: false,
|
|
316
|
+
enumerable: true
|
|
339
317
|
});
|
|
340
318
|
|
|
341
319
|
// Common events
|
|
342
320
|
parser.on('text', function (str) {
|
|
343
|
-
|
|
321
|
+
if (current === undefined || current === null) return;
|
|
322
|
+
if (options.method === SAXOPHONE) {
|
|
323
|
+
current.text = options.trim ? str.contents.trim() : str.contents;
|
|
324
|
+
} else {
|
|
325
|
+
current.text = options.trim ? str.trim() : str;
|
|
326
|
+
}
|
|
344
327
|
});
|
|
345
328
|
|
|
346
329
|
parser.on("comment", function (str) {
|
|
330
|
+
if (options.method === SAXOPHONE)
|
|
331
|
+
str = str.contents;
|
|
347
332
|
if (current.hasOwnProperty('comment')) {
|
|
348
333
|
if (typeof current.comment === 'string') {
|
|
349
334
|
current.comment = [current.comment, str.trim()];
|
|
@@ -358,20 +343,148 @@ function createParser(handler, options = {}) {
|
|
|
358
343
|
configurable: true
|
|
359
344
|
});
|
|
360
345
|
}
|
|
361
|
-
|
|
362
346
|
});
|
|
363
347
|
|
|
364
348
|
parser.on('error', function (err) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
parser.
|
|
349
|
+
if (options.method === SAXOPHONE) {
|
|
350
|
+
console.error(err);
|
|
351
|
+
} else {
|
|
352
|
+
console.error(`error at line [${parser.currentLine}], column [${parser.currentColumn}]`, err);
|
|
353
|
+
if (options.resumeAfterError) {
|
|
354
|
+
parser.underlyingParser.error = null;
|
|
355
|
+
parser.underlyingParser.resume();
|
|
356
|
+
}
|
|
369
357
|
}
|
|
370
358
|
});
|
|
371
359
|
|
|
372
360
|
return parser;
|
|
373
361
|
}
|
|
374
362
|
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Common Event hanlder for starting tag
|
|
366
|
+
* @param {object} binds - Additional parameter object
|
|
367
|
+
* @param {object|string} node - Node or Node name
|
|
368
|
+
* @param {object} attrs - Node Attributes
|
|
369
|
+
*/
|
|
370
|
+
function onStart(binds, node, attrs) {
|
|
371
|
+
|
|
372
|
+
const name = typeof node === 'string' ? node : node.name;
|
|
373
|
+
const handler = binds.handler;
|
|
374
|
+
const options = binds.options;
|
|
375
|
+
let namespaces = binds.namespaces;
|
|
376
|
+
|
|
377
|
+
if (attrs === undefined && options.method === SAXOPHONE)
|
|
378
|
+
attrs = binds.Saxophone.parseAttrs(node.attrs);
|
|
379
|
+
|
|
380
|
+
let attrNS = {};
|
|
381
|
+
if (options.xmlns && attrs !== undefined) {
|
|
382
|
+
for (let key of Object.keys(attrs).filter(x => !(x.startsWith('xmlns:') && name.includes(':'))))
|
|
383
|
+
attrNS[key] = attrs[key];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (tree === undefined) {
|
|
387
|
+
tree = new Twig(name, current, options.xmlns ? attrNS : attrs);
|
|
388
|
+
} else {
|
|
389
|
+
if (current.isRoot && current.name === undefined) {
|
|
390
|
+
current.setRoot(name);
|
|
391
|
+
if (attrs !== undefined) {
|
|
392
|
+
const att = options.xmlns ? attrNS : attrs;
|
|
393
|
+
for (let key of Object.keys(att))
|
|
394
|
+
current.attribute(key, att[key]);
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
let elt = new Twig(name, current, options.xmlns ? attrNS : attrs);
|
|
398
|
+
if (options.partial) {
|
|
399
|
+
for (let hndl of handler) {
|
|
400
|
+
if (typeof hndl.tag === 'string' && name === hndl.tag) {
|
|
401
|
+
elt.pin();
|
|
402
|
+
break;
|
|
403
|
+
} else if (hndl.tag instanceof RegExp && hndl.tag.test(name)) {
|
|
404
|
+
elt.pin();
|
|
405
|
+
break;
|
|
406
|
+
} else if (typeof hndl.tag === 'function' && hndl.tag(name, current ?? tree)) {
|
|
407
|
+
elt.pin();
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (options.xmlns) {
|
|
416
|
+
if ([EXPAT, SAXOPHONE].includes(options.method)) {
|
|
417
|
+
for (let key of Object.keys(attrs).filter(x => x.startsWith('xmlns:')))
|
|
418
|
+
namespaces[key.split(':')[1]] = attrs[key];
|
|
419
|
+
}
|
|
420
|
+
if (name.includes(':')) {
|
|
421
|
+
let prefix = name.split(':')[0];
|
|
422
|
+
if (namespaces[prefix] !== undefined) {
|
|
423
|
+
Object.defineProperty(current, 'namespace', {
|
|
424
|
+
value: { local: prefix, uri: namespaces[prefix] },
|
|
425
|
+
writable: false,
|
|
426
|
+
enumerable: true
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (options.method === SAXOPHONE && node.isSelfClosing)
|
|
432
|
+
binds.parser.emit("tagclose", node);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Common Event hanlder for closing tag
|
|
437
|
+
* @param {TwigHandler|TwigHandler[]} handler - Object or array of element specification and function to handle elements
|
|
438
|
+
* @param {ParserOptions} options - Object of optional options
|
|
439
|
+
* @param {string} name - Event handler parameter
|
|
440
|
+
*/
|
|
441
|
+
function onClose(handler, options, name) {
|
|
442
|
+
current.close();
|
|
443
|
+
let purge = true;
|
|
444
|
+
|
|
445
|
+
if (options.method === SAXOPHONE)
|
|
446
|
+
name = name.name;
|
|
447
|
+
for (let hndl of Array.isArray(handler) ? handler : [handler]) {
|
|
448
|
+
if (hndl.tag instanceof AnyHandler) {
|
|
449
|
+
if (typeof hndl.function === 'function') hndl.function(current ?? tree);
|
|
450
|
+
if (typeof hndl.event === 'string') parser.emit(hndl.event, current ?? tree);
|
|
451
|
+
purge = false;
|
|
452
|
+
} else if (hndl.tag instanceof RootHandler && [EXPAT, SAXOPHONE].includes(options.method) && current.isRoot) {
|
|
453
|
+
if (typeof hndl.function === 'function') hndl.function(tree);
|
|
454
|
+
if (typeof hndl.event === 'string') parser.emit(hndl.event, tree);
|
|
455
|
+
purge = false;
|
|
456
|
+
} else if (typeof hndl.tag === 'string' && name === hndl.tag) {
|
|
457
|
+
if (typeof hndl.function === 'function') hndl.function(current ?? tree);
|
|
458
|
+
if (typeof hndl.event === 'string') parser.emit(hndl.event, current ?? tree);
|
|
459
|
+
purge = false;
|
|
460
|
+
} else if (hndl.tag instanceof RegExp && hndl.tag.test(name)) {
|
|
461
|
+
if (typeof hndl.function === 'function') hndl.function(current ?? tree);
|
|
462
|
+
if (typeof hndl.event === 'string') parser.emit(hndl.event, current ?? tree);
|
|
463
|
+
purge = false;
|
|
464
|
+
} else if (typeof hndl.tag === 'function' && hndl.tag(name, current ?? tree)) {
|
|
465
|
+
if (typeof hndl.function === 'function') hndl.function(current ?? tree);
|
|
466
|
+
if (typeof hndl.event === 'string') parser.emit(hndl.event, current ?? tree);
|
|
467
|
+
purge = false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (options.partial && purge && !current.pinned && !current.isRoot)
|
|
472
|
+
current.purge();
|
|
473
|
+
current = current.parent();
|
|
474
|
+
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Reset global variable if one like to parse multiple files
|
|
480
|
+
*/
|
|
481
|
+
function reset() {
|
|
482
|
+
tree = undefined;
|
|
483
|
+
current = undefined;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
let closeXMLWriterElement = true;
|
|
487
|
+
|
|
375
488
|
/**
|
|
376
489
|
* Generic class modeling a XML Node
|
|
377
490
|
* @class Twig
|
|
@@ -617,10 +730,11 @@ class Twig {
|
|
|
617
730
|
* @throws {UnsupportedType} - If value is not a string, boolean or numeric type
|
|
618
731
|
*/
|
|
619
732
|
set text(value) {
|
|
733
|
+
if (this.#text === null) this.#text = '';
|
|
620
734
|
if (typeof value === 'string')
|
|
621
|
-
this.#text
|
|
735
|
+
this.#text += value;
|
|
622
736
|
else if (['number', 'bigint', 'boolean'].includes(typeof value))
|
|
623
|
-
this.#text
|
|
737
|
+
this.#text += value.toString();
|
|
624
738
|
else
|
|
625
739
|
throw new UnsupportedType(value);
|
|
626
740
|
}
|
|
@@ -647,21 +761,30 @@ class Twig {
|
|
|
647
761
|
Object.seal(this);
|
|
648
762
|
};
|
|
649
763
|
|
|
764
|
+
/**
|
|
765
|
+
* XML-Twig for dummies :-)
|
|
766
|
+
* @returns {string} The XML-Tree which is currently available in RAM - no valid XML Structure
|
|
767
|
+
*/
|
|
768
|
+
debug = function () {
|
|
769
|
+
return this.root().writer(true, this).output;
|
|
770
|
+
};
|
|
771
|
+
|
|
650
772
|
/**
|
|
651
773
|
* Internal recursive function used by `writer()`
|
|
652
774
|
* @param {external:XMLWriter} xw - The writer object
|
|
653
775
|
* @param {Twig[]} childArray - Array of child elements
|
|
654
776
|
*/
|
|
655
|
-
#addChild = function (xw, childArray) {
|
|
777
|
+
#addChild = function (xw, childArray, startElement) {
|
|
656
778
|
for (let elt of childArray) {
|
|
657
779
|
xw.startElement(elt.name);
|
|
658
780
|
for (let [key, val] of Object.entries(elt.attributes))
|
|
659
781
|
xw.writeAttribute(key, val);
|
|
660
782
|
if (elt.text !== null)
|
|
661
783
|
xw.text(elt.text);
|
|
662
|
-
this.#addChild(xw, elt.children());
|
|
784
|
+
this.#addChild(xw, elt.children(), startElement);
|
|
785
|
+
if (elt === startElement) closeXMLWriterElement = false;
|
|
663
786
|
}
|
|
664
|
-
xw.endElement();
|
|
787
|
+
if (closeXMLWriterElement) xw.endElement();
|
|
665
788
|
};
|
|
666
789
|
|
|
667
790
|
/**
|
|
@@ -669,17 +792,18 @@ class Twig {
|
|
|
669
792
|
* @param {?boolean|string|external:XMLWriter} par - `true` or intention character or an already created XMLWriter
|
|
670
793
|
* @returns {external:XMLWriter}
|
|
671
794
|
*/
|
|
672
|
-
writer = function (par) {
|
|
795
|
+
writer = function (par, startElement) {
|
|
673
796
|
const XMLWriter = require('xml-writer');
|
|
674
797
|
let xw = par instanceof XMLWriter ? par : new XMLWriter(par);
|
|
798
|
+
closeXMLWriterElement = true
|
|
675
799
|
|
|
676
800
|
xw.startElement(this.#name);
|
|
677
801
|
for (let [key, val] of Object.entries(this.#attributes))
|
|
678
802
|
xw.writeAttribute(key, val);
|
|
679
803
|
if (this.#text !== null)
|
|
680
804
|
xw.text(this.#text);
|
|
681
|
-
this.#addChild(xw, this.#children);
|
|
682
|
-
xw.endElement();
|
|
805
|
+
this.#addChild(xw, this.#children, startElement);
|
|
806
|
+
if (closeXMLWriterElement) xw.endElement();
|
|
683
807
|
return xw;
|
|
684
808
|
};
|
|
685
809
|
|
|
@@ -1157,7 +1281,7 @@ class UnsupportedParser extends TypeError {
|
|
|
1157
1281
|
* @param {string} t Parser type
|
|
1158
1282
|
*/
|
|
1159
1283
|
constructor(t) {
|
|
1160
|
-
super(`Parser '${t}' is not supported. Use 'expat'
|
|
1284
|
+
super(`Parser '${t}' is not supported. Use 'expat', 'sax' (default) or 'saxophone'`);
|
|
1161
1285
|
}
|
|
1162
1286
|
}
|
|
1163
1287
|
|
|
@@ -1189,4 +1313,4 @@ class UnsupportedCondition extends TypeError {
|
|
|
1189
1313
|
}
|
|
1190
1314
|
|
|
1191
1315
|
|
|
1192
|
-
module.exports = { createParser, Twig, Any, Root };
|
|
1316
|
+
module.exports = { createParser, Twig, Any, Root, reset };
|