xml-twig 1.2.94 → 1.3.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 +6 -6
- package/doc/twig.md +47 -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 +250 -137
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>
|
|
@@ -1409,6 +1418,38 @@ Create a new Twig parser
|
|
|
1409
1418
|
| handler | [<code>TwigHandler</code>](#TwigHandler) \| [<code>Array.<TwigHandler></code>](#TwigHandler) | Object or array of element specification and function to handle elements |
|
|
1410
1419
|
| [options] | [<code>ParserOptions</code>](#ParserOptions) | Object of optional options |
|
|
1411
1420
|
|
|
1421
|
+
<a name="onStart"></a>
|
|
1422
|
+
|
|
1423
|
+
## onStart(binds, node, attrs)
|
|
1424
|
+
Common Event hanlder for starting tag
|
|
1425
|
+
|
|
1426
|
+
**Kind**: global function
|
|
1427
|
+
|
|
1428
|
+
| Param | Type | Description |
|
|
1429
|
+
| --- | --- | --- |
|
|
1430
|
+
| binds | <code>object</code> | Additional parameter object |
|
|
1431
|
+
| node | <code>object</code> \| <code>string</code> | Node or Node name |
|
|
1432
|
+
| attrs | <code>object</code> | Node Attributes |
|
|
1433
|
+
|
|
1434
|
+
<a name="onClose"></a>
|
|
1435
|
+
|
|
1436
|
+
## onClose(handler, options, name)
|
|
1437
|
+
Common Event hanlder for closing tag
|
|
1438
|
+
|
|
1439
|
+
**Kind**: global function
|
|
1440
|
+
|
|
1441
|
+
| Param | Type | Description |
|
|
1442
|
+
| --- | --- | --- |
|
|
1443
|
+
| handler | [<code>TwigHandler</code>](#TwigHandler) \| [<code>Array.<TwigHandler></code>](#TwigHandler) | Object or array of element specification and function to handle elements |
|
|
1444
|
+
| options | [<code>ParserOptions</code>](#ParserOptions) | Object of optional options |
|
|
1445
|
+
| name | <code>string</code> | Event handler parameter |
|
|
1446
|
+
|
|
1447
|
+
<a name="reset"></a>
|
|
1448
|
+
|
|
1449
|
+
## reset()
|
|
1450
|
+
Reset global variable if one like to parse multiple files
|
|
1451
|
+
|
|
1452
|
+
**Kind**: global function
|
|
1412
1453
|
<a name="ParserOptions"></a>
|
|
1413
1454
|
|
|
1414
1455
|
## ParserOptions
|
|
@@ -1420,7 +1461,7 @@ Optional settings for the Twig parser
|
|
|
1420
1461
|
|
|
1421
1462
|
| Name | Type | Description |
|
|
1422
1463
|
| --- | --- | --- |
|
|
1423
|
-
| [method] | <code>'sax'</code> \| <code>'expat'</code> | The underlying parser. Either `'sax'` or `'
|
|
1464
|
+
| [method] | <code>'sax'</code> \| <code>'expat'</code> \| <code>'saxophone'</code> | The underlying parser. Either `'sax'`, `'expat'` or `'saxophone'`. |
|
|
1424
1465
|
| [xmlns] | <code>boolean</code> | If `true`, then namespaces are accessible by `namespace` property. |
|
|
1425
1466
|
| [trim] | <code>boolean</code> | If `true`, then turn any whitespace into a single space. Text and comments are trimmed. |
|
|
1426
1467
|
| [resumeAfterError] | <code>boolean</code> | If `true` then parser continues reading after an error. Otherwise it throws exception. |
|
|
@@ -1494,15 +1535,15 @@ Custom filter function to select desired elements
|
|
|
1494
1535
|
|
|
1495
1536
|
<a name="Parser"></a>
|
|
1496
1537
|
|
|
1497
|
-
## Parser ⇒ [<code>sax</code>](https://www.npmjs.com/package/sax) \| [<code>node-expat</code>](https://www.npmjs.com/package/node-expat)
|
|
1538
|
+
## 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
1539
|
**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
|
|
1540
|
+
**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
1541
|
**Properties**
|
|
1501
1542
|
|
|
1502
1543
|
| Name | Type | Description |
|
|
1503
1544
|
| --- | --- | --- |
|
|
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 |
|
|
1545
|
+
| [currentLine] | <code>number</code> | The currently processed line in the XML-File.<br/>Not available on `saxophone` parser. |
|
|
1546
|
+
| [currentColumn] | <code>number</code> | The currently processed column in the XML-File.<br/>Not available on `saxophone` parser. |
|
|
1506
1547
|
|
|
1507
1548
|
<a name="AttributeCondition"></a>
|
|
1508
1549
|
|
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.0",
|
|
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,147 @@ 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
|
+
|
|
375
487
|
/**
|
|
376
488
|
* Generic class modeling a XML Node
|
|
377
489
|
* @class Twig
|
|
@@ -617,10 +729,11 @@ class Twig {
|
|
|
617
729
|
* @throws {UnsupportedType} - If value is not a string, boolean or numeric type
|
|
618
730
|
*/
|
|
619
731
|
set text(value) {
|
|
732
|
+
if (this.#text === null) this.#text = '';
|
|
620
733
|
if (typeof value === 'string')
|
|
621
|
-
this.#text
|
|
734
|
+
this.#text += value;
|
|
622
735
|
else if (['number', 'bigint', 'boolean'].includes(typeof value))
|
|
623
|
-
this.#text
|
|
736
|
+
this.#text += value.toString();
|
|
624
737
|
else
|
|
625
738
|
throw new UnsupportedType(value);
|
|
626
739
|
}
|
|
@@ -1157,7 +1270,7 @@ class UnsupportedParser extends TypeError {
|
|
|
1157
1270
|
* @param {string} t Parser type
|
|
1158
1271
|
*/
|
|
1159
1272
|
constructor(t) {
|
|
1160
|
-
super(`Parser '${t}' is not supported. Use 'expat'
|
|
1273
|
+
super(`Parser '${t}' is not supported. Use 'expat', 'sax' (default) or 'saxophone'`);
|
|
1161
1274
|
}
|
|
1162
1275
|
}
|
|
1163
1276
|
|
|
@@ -1189,4 +1302,4 @@ class UnsupportedCondition extends TypeError {
|
|
|
1189
1302
|
}
|
|
1190
1303
|
|
|
1191
1304
|
|
|
1192
|
-
module.exports = { createParser, Twig, Any, Root };
|
|
1305
|
+
module.exports = { createParser, Twig, Any, Root, reset };
|