xml-twig 1.2.93 → 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 +253 -136
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,
|
|
@@ -205,18 +193,23 @@ function createParser(handler, options = {}) {
|
|
|
205
193
|
});
|
|
206
194
|
|
|
207
195
|
parser.on("attribute", function (attr) {
|
|
208
|
-
|
|
209
|
-
if ((attr.uri ?? '') !== '' && attr.local !== undefined) {
|
|
196
|
+
if (options.xmlns && (attr.uri ?? '') !== '' && attr.local !== undefined) {
|
|
210
197
|
namespaces[attr.local] = attr.uri;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
current.attribute(attr.name, attr.value);
|
|
216
209
|
}
|
|
217
210
|
});
|
|
218
211
|
parser.on("cdata", function (str) {
|
|
219
|
-
current.text =
|
|
212
|
+
current.text = options.trim ? str.trim() : str;
|
|
220
213
|
});
|
|
221
214
|
|
|
222
215
|
const hndl = Array.isArray(handler) ? handler : [handler];
|
|
@@ -236,49 +229,13 @@ function createParser(handler, options = {}) {
|
|
|
236
229
|
enumerable: true,
|
|
237
230
|
get() { return parser.parser.getCurrentColumnNumber(); }
|
|
238
231
|
});
|
|
239
|
-
closeEvent = "endElement";
|
|
240
232
|
|
|
241
|
-
parser.on("
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
} else {
|
|
248
|
-
let elt = new Twig(name, current, attrs);
|
|
249
|
-
if (options.partial) {
|
|
250
|
-
for (let hndl of Array.isArray(handler) ? handler : [handler]) {
|
|
251
|
-
if (typeof hndl.tag === 'string' && name === hndl.tag) {
|
|
252
|
-
elt.pin();
|
|
253
|
-
break;
|
|
254
|
-
} else if (hndl.tag instanceof RegExp && hndl.tag.test(name)) {
|
|
255
|
-
elt.pin();
|
|
256
|
-
break;
|
|
257
|
-
} else if (typeof hndl.tag === 'function' && hndl.tag(name, current ?? tree)) {
|
|
258
|
-
elt.pin();
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
for (let attr in attrs)
|
|
266
|
-
current.attribute(attr, attrs[attr]);
|
|
267
|
-
if (options.xmlns) {
|
|
268
|
-
for (let key of Object.keys(attrs).filter(x => x.startsWith('xmlns:')))
|
|
269
|
-
namespaces[key.split(':')[1]] = attrs[key];
|
|
270
|
-
if (name.includes(':')) {
|
|
271
|
-
let prefix = name.split(':')[0];
|
|
272
|
-
if (namespaces[prefix] !== undefined) {
|
|
273
|
-
Object.defineProperty(current, 'namespace', {
|
|
274
|
-
value: { local: prefix, uri: namespaces[prefix] },
|
|
275
|
-
writable: false,
|
|
276
|
-
enumerable: true
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
});
|
|
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
|
+
}));
|
|
282
239
|
|
|
283
240
|
parser.on('xmlDecl', function (version, encoding, standalone) {
|
|
284
241
|
tree = new Twig(null);
|
|
@@ -296,50 +253,82 @@ function createParser(handler, options = {}) {
|
|
|
296
253
|
parser.on('processingInstruction', function (target, data) {
|
|
297
254
|
tree.PI = { target: target, data: data };
|
|
298
255
|
});
|
|
299
|
-
} else {
|
|
300
|
-
throw new UnsupportedParser(options.method);
|
|
301
|
-
}
|
|
302
256
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
+
});
|
|
328
305
|
}
|
|
329
|
-
}
|
|
330
306
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
} else {
|
|
310
|
+
throw new UnsupportedParser(options.method);
|
|
311
|
+
}
|
|
334
312
|
|
|
313
|
+
Object.defineProperty(parser, 'method', {
|
|
314
|
+
value: options.method,
|
|
315
|
+
writable: false,
|
|
316
|
+
enumerable: true
|
|
335
317
|
});
|
|
336
318
|
|
|
337
319
|
// Common events
|
|
338
320
|
parser.on('text', function (str) {
|
|
339
|
-
|
|
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
|
+
}
|
|
340
327
|
});
|
|
341
328
|
|
|
342
329
|
parser.on("comment", function (str) {
|
|
330
|
+
if (options.method === SAXOPHONE)
|
|
331
|
+
str = str.contents;
|
|
343
332
|
if (current.hasOwnProperty('comment')) {
|
|
344
333
|
if (typeof current.comment === 'string') {
|
|
345
334
|
current.comment = [current.comment, str.trim()];
|
|
@@ -354,20 +343,147 @@ function createParser(handler, options = {}) {
|
|
|
354
343
|
configurable: true
|
|
355
344
|
});
|
|
356
345
|
}
|
|
357
|
-
|
|
358
346
|
});
|
|
359
347
|
|
|
360
348
|
parser.on('error', function (err) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
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
|
+
}
|
|
365
357
|
}
|
|
366
358
|
});
|
|
367
359
|
|
|
368
360
|
return parser;
|
|
369
361
|
}
|
|
370
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
|
+
|
|
371
487
|
/**
|
|
372
488
|
* Generic class modeling a XML Node
|
|
373
489
|
* @class Twig
|
|
@@ -613,10 +729,11 @@ class Twig {
|
|
|
613
729
|
* @throws {UnsupportedType} - If value is not a string, boolean or numeric type
|
|
614
730
|
*/
|
|
615
731
|
set text(value) {
|
|
732
|
+
if (this.#text === null) this.#text = '';
|
|
616
733
|
if (typeof value === 'string')
|
|
617
|
-
this.#text
|
|
734
|
+
this.#text += value;
|
|
618
735
|
else if (['number', 'bigint', 'boolean'].includes(typeof value))
|
|
619
|
-
this.#text
|
|
736
|
+
this.#text += value.toString();
|
|
620
737
|
else
|
|
621
738
|
throw new UnsupportedType(value);
|
|
622
739
|
}
|
|
@@ -1153,7 +1270,7 @@ class UnsupportedParser extends TypeError {
|
|
|
1153
1270
|
* @param {string} t Parser type
|
|
1154
1271
|
*/
|
|
1155
1272
|
constructor(t) {
|
|
1156
|
-
super(`Parser '${t}' is not supported. Use 'expat'
|
|
1273
|
+
super(`Parser '${t}' is not supported. Use 'expat', 'sax' (default) or 'saxophone'`);
|
|
1157
1274
|
}
|
|
1158
1275
|
}
|
|
1159
1276
|
|
|
@@ -1185,4 +1302,4 @@ class UnsupportedCondition extends TypeError {
|
|
|
1185
1302
|
}
|
|
1186
1303
|
|
|
1187
1304
|
|
|
1188
|
-
module.exports = { createParser, Twig, Any, Root };
|
|
1305
|
+
module.exports = { createParser, Twig, Any, Root, reset };
|