@xmldom/xmldom 0.8.12 → 0.8.13
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/CHANGELOG.md +24 -1
- package/index.d.ts +36 -2
- package/lib/dom.js +546 -311
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,16 +4,39 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.8.13](https://github.com/xmldom/xmldom/compare/0.8.12...0.8.13)
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Security: `XMLSerializer.serializeToString()` (and `Node.toString()`, `NodeList.toString()`) now accept a `requireWellFormed` option (fourth argument, after `isHtml` and `nodeFilter`). When `{ requireWellFormed: true }` is passed, the serializer throws `InvalidStateError` for injection-prone node content, preventing XML injection via attacker-controlled node data. [`GHSA-j759-j44w-7fr8`](https://github.com/xmldom/xmldom/security/advisories/GHSA-j759-j44w-7fr8) [`GHSA-x6wf-f3px-wcqx`](https://github.com/xmldom/xmldom/security/advisories/GHSA-x6wf-f3px-wcqx) [`GHSA-f6ww-3ggp-fr8h`](https://github.com/xmldom/xmldom/security/advisories/GHSA-f6ww-3ggp-fr8h)
|
|
12
|
+
- Comment: throws when `data` contains `-->`
|
|
13
|
+
- ProcessingInstruction: throws when `data` contains `?>`
|
|
14
|
+
- DocumentType: throws when `publicId` fails `PubidLiteral`, `systemId` fails `SystemLiteral`, or `internalSubset` contains `]>`
|
|
15
|
+
- Security: DOM traversal operations (`XMLSerializer.serializeToString()`, `Node.prototype.normalize()`, `Node.prototype.cloneNode(true)`, `Document.prototype.importNode(node, true)`, `node.textContent` getter, `getElementsByTagName()` / `getElementsByTagNameNS()` / `getElementsByClassName()` / `getElementById()`) are now iterative. Previously, deeply nested DOM trees would exhaust the JavaScript call stack and throw an unrecoverable `RangeError`. [`GHSA-2v35-w6hq-6mfw`](https://github.com/xmldom/xmldom/security/advisories/GHSA-2v35-w6hq-6mfw)
|
|
16
|
+
|
|
17
|
+
Thank you,
|
|
18
|
+
[@Jvr2022](https://github.com/Jvr2022),
|
|
19
|
+
[@praveen-kv](https://github.com/praveen-kv),
|
|
20
|
+
[@TharVid](https://github.com/TharVid),
|
|
21
|
+
[@decsecre583](https://github.com/decsecre583),
|
|
22
|
+
[@tlsbollei](https://github.com/tlsbollei),
|
|
23
|
+
[@KarimTantawey](https://github.com/KarimTantawey),
|
|
24
|
+
for your contributions
|
|
25
|
+
|
|
7
26
|
## [0.8.12](https://github.com/xmldom/xmldom/compare/0.8.11...0.8.12)
|
|
8
27
|
|
|
9
28
|
### Fixed
|
|
10
29
|
|
|
30
|
+
- preserve trailing whitespace in ProcessingInstruction data [`#962`](https://github.com/xmldom/xmldom/pull/962) / [`#42`](https://github.com/xmldom/xmldom/issues/42)
|
|
11
31
|
- Security: `createCDATASection` now throws `InvalidCharacterError` when `data` contains `"]]>"`, as required by the [WHATWG DOM spec](https://dom.spec.whatwg.org/#dom-document-createcdatasection). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp)
|
|
12
32
|
- Security: `XMLSerializer` now splits CDATASection nodes whose data contains `"]]>"` into adjacent CDATA sections at serialization time, preventing XML injection via mutation methods (`appendData`, `replaceData`, `.data =`, `.textContent =`). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp)
|
|
13
33
|
|
|
14
34
|
Code that passes a string containing `"]]>"` to `createCDATASection` and relied on the previously unsafe behavior will now receive `InvalidCharacterError`. Use a mutation method such as `appendData` if you intentionally need `"]]>"` in a CDATASection node's data.
|
|
15
35
|
|
|
16
|
-
Thank you,
|
|
36
|
+
Thank you,
|
|
37
|
+
[@thesmartshadow](https://github.com/thesmartshadow),
|
|
38
|
+
[@stevenobiajulu](https://github.com/stevenobiajulu),
|
|
39
|
+
for your contributions
|
|
17
40
|
|
|
18
41
|
## [0.8.11](https://github.com/xmldom/xmldom/compare/0.8.10...0.8.11)
|
|
19
42
|
|
package/index.d.ts
CHANGED
|
@@ -22,18 +22,52 @@ declare module "@xmldom/xmldom" {
|
|
|
22
22
|
parseFromString(xmlsource: string, mimeType?: string): Document;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/** Options accepted by `XMLSerializer.prototype.serializeToString`. */
|
|
26
|
+
interface XMLSerializerOptions {
|
|
27
|
+
/**
|
|
28
|
+
* When `true`, the serializer throws a DOMException with code `INVALID_STATE_ERR` if:
|
|
29
|
+
* - A CDATASection node's data contains `"]]>"`.
|
|
30
|
+
* - A Comment node's data contains `"-->"` (the injection sequence that terminates a
|
|
31
|
+
* comment). Comments whose data contains `"--"` but not `"-->"` are accepted on this
|
|
32
|
+
* branch — the 0.8.x parser does not validate bare `"--"` in comment content.
|
|
33
|
+
* - A ProcessingInstruction's data contains `"?>"` (W3C DOM Parsing §3.2.1.7).
|
|
34
|
+
*
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
requireWellFormed?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
25
40
|
interface XMLSerializer {
|
|
26
41
|
/**
|
|
27
42
|
* Returns the result of serializing `node` to XML.
|
|
28
43
|
*
|
|
44
|
+
* When `options.requireWellFormed` is `true`, the serializer throws for content that would
|
|
45
|
+
* produce ill-formed XML.
|
|
46
|
+
*
|
|
29
47
|
* __This implementation differs from the specification:__
|
|
30
48
|
* - CDATASection nodes whose data contains `]]>` are serialized by splitting the section
|
|
31
49
|
* at each `]]>` occurrence (following W3C DOM Level 3 Core `split-cdata-sections`
|
|
32
|
-
* default behaviour)
|
|
50
|
+
* default behaviour) unless `requireWellFormed` is `true`.
|
|
51
|
+
* - when `requireWellFormed` is `true`, `DOMException` with code `INVALID_STATE_ERR`
|
|
52
|
+
* is only thrown to prevent injection vectors, not for all the spec mandated checks.
|
|
33
53
|
*
|
|
54
|
+
* @throws {DOMException}
|
|
55
|
+
* With code `INVALID_STATE_ERR` when `requireWellFormed` is `true` and:
|
|
56
|
+
* - a CDATASection node's data contains `"]]>"`,
|
|
57
|
+
* - a Comment node's data contains `"-->"` (bare `"--"` does not throw on this branch),
|
|
58
|
+
* - a ProcessingInstruction's data contains `"?>"`,
|
|
59
|
+
* - a DocumentType's `publicId` is non-empty and does not match the XML `PubidLiteral`
|
|
60
|
+
* production,
|
|
61
|
+
* - a DocumentType's `systemId` is non-empty and does not match the XML `SystemLiteral`
|
|
62
|
+
* production, or
|
|
63
|
+
* - a DocumentType's `internalSubset` contains `"]>"`.
|
|
64
|
+
* Note: xmldom does not enforce `readonly` on DocumentType fields — direct property
|
|
65
|
+
* writes succeed and are covered by the serializer-level checks above.
|
|
34
66
|
* @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring
|
|
67
|
+
* @see https://w3c.github.io/DOM-Parsing/#xml-serialization
|
|
68
|
+
* @see https://github.com/w3c/DOM-Parsing/issues/84
|
|
35
69
|
*/
|
|
36
|
-
serializeToString(node: Node): string;
|
|
70
|
+
serializeToString(node: Node, isHtml?: boolean, nodeFilter?: (node: Node) => Node | null | undefined, options?: XMLSerializerOptions): string;
|
|
37
71
|
}
|
|
38
72
|
|
|
39
73
|
interface Options {
|
package/lib/dom.js
CHANGED
|
@@ -171,9 +171,10 @@ NodeList.prototype = {
|
|
|
171
171
|
item: function(index) {
|
|
172
172
|
return index >= 0 && index < this.length ? this[index] : null;
|
|
173
173
|
},
|
|
174
|
-
toString:function(isHTML,nodeFilter){
|
|
174
|
+
toString:function(isHTML,nodeFilter,options){
|
|
175
|
+
var requireWellFormed = !!options && !!options.requireWellFormed;
|
|
175
176
|
for(var buf = [], i = 0;i<this.length;i++){
|
|
176
|
-
serializeToString(this[i],buf,isHTML,nodeFilter);
|
|
177
|
+
serializeToString(this[i],buf,isHTML,nodeFilter,null,requireWellFormed);
|
|
177
178
|
}
|
|
178
179
|
return buf.join('');
|
|
179
180
|
},
|
|
@@ -419,13 +420,28 @@ DOMImplementation.prototype = {
|
|
|
419
420
|
/**
|
|
420
421
|
* Returns a doctype, with the given `qualifiedName`, `publicId`, and `systemId`.
|
|
421
422
|
*
|
|
422
|
-
* __This
|
|
423
|
+
* __This implementation differs from the specification:__
|
|
423
424
|
* - this implementation is not validating names or qualified names
|
|
424
425
|
* (when parsing XML strings, the SAX parser takes care of that)
|
|
425
426
|
*
|
|
427
|
+
* Note: `internalSubset` can only be introduced via a direct property write to `node.internalSubset` after creation.
|
|
428
|
+
* Creation-time validation of `publicId`, `systemId` is not enforced.
|
|
429
|
+
* The serializer-level check covers all mutation vectors, including direct property writes.
|
|
430
|
+
* `internalSubset` is only serialized as `[ ... ]` when both `publicId` and `systemId` are
|
|
431
|
+
* absent (empty or `'.'`) — if either external identifier is present, `internalSubset` is
|
|
432
|
+
* silently omitted from the serialized output.
|
|
433
|
+
*
|
|
426
434
|
* @param {string} qualifiedName
|
|
427
435
|
* @param {string} [publicId]
|
|
436
|
+
* The external subset public identifier. Stored verbatim including surrounding quotes.
|
|
437
|
+
* When serialized with `requireWellFormed: true` (via the 4th-parameter options object),
|
|
438
|
+
* throws `DOMException` with code `INVALID_STATE_ERR` if the value is non-empty and does
|
|
439
|
+
* not match the XML `PubidLiteral` production (W3C DOM Parsing §3.2.1.3; XML 1.0 [12]).
|
|
428
440
|
* @param {string} [systemId]
|
|
441
|
+
* The external subset system identifier. Stored verbatim including surrounding quotes.
|
|
442
|
+
* When serialized with `requireWellFormed: true`, throws `DOMException` with code
|
|
443
|
+
* `INVALID_STATE_ERR` if the value is non-empty and does not match the XML `SystemLiteral`
|
|
444
|
+
* production (W3C DOM Parsing §3.2.1.3; XML 1.0 [11]).
|
|
429
445
|
* @returns {DocumentType} which can either be used with `DOMImplementation.createDocument` upon document creation
|
|
430
446
|
* or can be put into the document via methods like `Node.insertBefore()` or `Node.replaceChild()`
|
|
431
447
|
*
|
|
@@ -492,18 +508,44 @@ Node.prototype = {
|
|
|
492
508
|
return cloneNode(this.ownerDocument||this,this,deep);
|
|
493
509
|
},
|
|
494
510
|
// Modified in DOM Level 2:
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
511
|
+
/**
|
|
512
|
+
* Puts the specified node and all of its subtree into a "normalized" form. In a normalized
|
|
513
|
+
* subtree, no text nodes in the subtree are empty and there are no adjacent text nodes.
|
|
514
|
+
*
|
|
515
|
+
* Specifically, this method merges any adjacent text nodes (i.e., nodes for which `nodeType`
|
|
516
|
+
* is `TEXT_NODE`) into a single node with the combined data. It also removes any empty text
|
|
517
|
+
* nodes.
|
|
518
|
+
*
|
|
519
|
+
* This method iteratively traverses all child nodes to normalize all descendant nodes within
|
|
520
|
+
* the subtree.
|
|
521
|
+
*
|
|
522
|
+
* @throws {DOMException}
|
|
523
|
+
* May throw a DOMException if operations within removeChild or appendData (which are
|
|
524
|
+
* potentially invoked in this method) do not meet their specific constraints.
|
|
525
|
+
* @see {@link Node.removeChild}
|
|
526
|
+
* @see {@link CharacterData.appendData}
|
|
527
|
+
* @see ../docs/walk-dom.md.
|
|
528
|
+
*/
|
|
529
|
+
normalize: function () {
|
|
530
|
+
walkDOM(this, null, {
|
|
531
|
+
enter: function (node) {
|
|
532
|
+
// Merge adjacent text children of node before walkDOM schedules them.
|
|
533
|
+
// walkDOM reads lastChild/previousSibling after enter returns, so the
|
|
534
|
+
// surviving post-merge children are what it descends into.
|
|
535
|
+
var child = node.firstChild;
|
|
536
|
+
while (child) {
|
|
537
|
+
var next = child.nextSibling;
|
|
538
|
+
if (next !== null && next.nodeType === TEXT_NODE && child.nodeType === TEXT_NODE) {
|
|
539
|
+
node.removeChild(next);
|
|
540
|
+
child.appendData(next.data);
|
|
541
|
+
// Do not advance child: re-check new nextSibling for another text run
|
|
542
|
+
} else {
|
|
543
|
+
child = next;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return true; // descend into surviving children
|
|
547
|
+
},
|
|
548
|
+
});
|
|
507
549
|
},
|
|
508
550
|
// Introduced in DOM Level 2:
|
|
509
551
|
isSupported:function(feature, version){
|
|
@@ -579,21 +621,103 @@ copy(NodeType,Node);
|
|
|
579
621
|
copy(NodeType,Node.prototype);
|
|
580
622
|
|
|
581
623
|
/**
|
|
582
|
-
* @param
|
|
583
|
-
*
|
|
624
|
+
* @param {Node} node
|
|
625
|
+
* Root of the subtree to visit.
|
|
626
|
+
* @param {function(Node): boolean} callback
|
|
627
|
+
* Called for each node in depth-first pre-order. Return a truthy value to stop traversal early.
|
|
628
|
+
* @return {boolean} `true` if traversal was aborted by the callback, `false` otherwise.
|
|
584
629
|
*/
|
|
585
|
-
function _visitNode(node,callback){
|
|
586
|
-
|
|
587
|
-
return true;
|
|
588
|
-
}
|
|
589
|
-
if(node = node.firstChild){
|
|
590
|
-
do{
|
|
591
|
-
if(_visitNode(node,callback)){return true}
|
|
592
|
-
}while(node=node.nextSibling)
|
|
593
|
-
}
|
|
630
|
+
function _visitNode(node, callback) {
|
|
631
|
+
return walkDOM(node, null, { enter: function (n) { return callback(n) ? walkDOM.STOP : true; } }) === walkDOM.STOP;
|
|
594
632
|
}
|
|
595
633
|
|
|
634
|
+
/**
|
|
635
|
+
* Depth-first pre/post-order DOM tree walker.
|
|
636
|
+
*
|
|
637
|
+
* Visits every node in the subtree rooted at `node`. For each node:
|
|
638
|
+
*
|
|
639
|
+
* 1. Calls `callbacks.enter(node, context)` before descending into the node's children. The
|
|
640
|
+
* return value becomes the `context` passed to each child's `enter` call and to the matching
|
|
641
|
+
* `exit` call.
|
|
642
|
+
* 2. If `enter` returns `null` or `undefined`, the node's children are skipped;
|
|
643
|
+
* sibling traversal continues normally.
|
|
644
|
+
* 3. If `enter` returns `walkDOM.STOP`, the entire traversal is aborted immediately — no
|
|
645
|
+
* further `enter` or `exit` calls are made.
|
|
646
|
+
* 4. `lastChild` and `previousSibling` are read **after** `enter` returns, so `enter` may
|
|
647
|
+
* safely modify the node's own child list before the walker descends. Modifying siblings of
|
|
648
|
+
* the current node or any other part of the tree produces unpredictable results: nodes already
|
|
649
|
+
* queued on the stack are visited regardless of DOM changes, and newly inserted nodes outside
|
|
650
|
+
* the current child list are never visited.
|
|
651
|
+
* 5. Calls `callbacks.exit(node, context)` (if provided) after all of a node's children have
|
|
652
|
+
* been visited, passing the same `context` that `enter`
|
|
653
|
+
* returned for that node.
|
|
654
|
+
*
|
|
655
|
+
* This implementation uses an explicit stack and does not recurse — it is safe on arbitrarily
|
|
656
|
+
* deep trees.
|
|
657
|
+
*
|
|
658
|
+
* @param {Node} node
|
|
659
|
+
* Root of the subtree to walk.
|
|
660
|
+
* @param {*} context
|
|
661
|
+
* Initial context value passed to the root node's `enter`.
|
|
662
|
+
* @param {{ enter: function(Node, *): *, exit?: function(Node, *): void }} callbacks
|
|
663
|
+
* @returns {void | walkDOM.STOP}
|
|
664
|
+
* @see ../docs/walk-dom.md.
|
|
665
|
+
*/
|
|
666
|
+
function walkDOM(node, context, callbacks) {
|
|
667
|
+
// Each stack frame is {node, context, phase}:
|
|
668
|
+
// walkDOM.ENTER — call enter, then push children
|
|
669
|
+
// walkDOM.EXIT — call exit
|
|
670
|
+
var stack = [{ node: node, context: context, phase: walkDOM.ENTER }];
|
|
671
|
+
while (stack.length > 0) {
|
|
672
|
+
var frame = stack.pop();
|
|
673
|
+
if (frame.phase === walkDOM.ENTER) {
|
|
674
|
+
var childContext = callbacks.enter(frame.node, frame.context);
|
|
675
|
+
if (childContext === walkDOM.STOP) {
|
|
676
|
+
return walkDOM.STOP;
|
|
677
|
+
}
|
|
678
|
+
// Push exit frame before children so it fires after all children are processed (Last In First Out)
|
|
679
|
+
stack.push({ node: frame.node, context: childContext, phase: walkDOM.EXIT });
|
|
680
|
+
if (childContext === null || childContext === undefined) {
|
|
681
|
+
continue; // skip children
|
|
682
|
+
}
|
|
683
|
+
// lastChild is read after enter returns, so enter may modify the child list.
|
|
684
|
+
var child = frame.node.lastChild;
|
|
685
|
+
// Traverse from lastChild backwards so that pushing onto the stack
|
|
686
|
+
// naturally yields firstChild on top (processed first).
|
|
687
|
+
while (child) {
|
|
688
|
+
stack.push({ node: child, context: childContext, phase: walkDOM.ENTER });
|
|
689
|
+
child = child.previousSibling;
|
|
690
|
+
}
|
|
691
|
+
} else {
|
|
692
|
+
// frame.phase === walkDOM.EXIT
|
|
693
|
+
if (callbacks.exit) {
|
|
694
|
+
callbacks.exit(frame.node, frame.context);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
596
699
|
|
|
700
|
+
/**
|
|
701
|
+
* Sentinel value returned from a `walkDOM` `enter` callback to abort the entire traversal
|
|
702
|
+
* immediately.
|
|
703
|
+
*
|
|
704
|
+
* @type {symbol}
|
|
705
|
+
*/
|
|
706
|
+
walkDOM.STOP = Symbol('walkDOM.STOP');
|
|
707
|
+
/**
|
|
708
|
+
* Phase constant for a stack frame that has not yet been visited.
|
|
709
|
+
* The `enter` callback is called and children are scheduled.
|
|
710
|
+
*
|
|
711
|
+
* @type {number}
|
|
712
|
+
*/
|
|
713
|
+
walkDOM.ENTER = 0;
|
|
714
|
+
/**
|
|
715
|
+
* Phase constant for a stack frame whose subtree has been fully visited.
|
|
716
|
+
* The `exit` callback is called.
|
|
717
|
+
*
|
|
718
|
+
* @type {number}
|
|
719
|
+
*/
|
|
720
|
+
walkDOM.EXIT = 1;
|
|
597
721
|
|
|
598
722
|
function Document(){
|
|
599
723
|
this.ownerDocument = this;
|
|
@@ -1218,6 +1342,23 @@ Document.prototype = {
|
|
|
1218
1342
|
node.appendData(data)
|
|
1219
1343
|
return node;
|
|
1220
1344
|
},
|
|
1345
|
+
/**
|
|
1346
|
+
* Returns a ProcessingInstruction node whose target is target and data is data.
|
|
1347
|
+
*
|
|
1348
|
+
* __This implementation differs from the specification:__
|
|
1349
|
+
* - it does not do any input validation on the arguments and doesn't throw "InvalidCharacterError".
|
|
1350
|
+
*
|
|
1351
|
+
* Note: When the resulting document is serialized with `requireWellFormed: true`, the
|
|
1352
|
+
* serializer throws with code `INVALID_STATE_ERR` if `.data` contains `?>` (W3C DOM Parsing
|
|
1353
|
+
* §3.2.1.7). Without that option the data is emitted verbatim.
|
|
1354
|
+
*
|
|
1355
|
+
* @param {string} target
|
|
1356
|
+
* @param {string} data
|
|
1357
|
+
* @returns {ProcessingInstruction}
|
|
1358
|
+
* @see https://developer.mozilla.org/docs/Web/API/Document/createProcessingInstruction
|
|
1359
|
+
* @see https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction
|
|
1360
|
+
* @see https://www.w3.org/TR/DOM-Parsing/#dfn-concept-serialize-xml §3.2.1.7
|
|
1361
|
+
*/
|
|
1221
1362
|
createProcessingInstruction : function(target,data){
|
|
1222
1363
|
var node = new ProcessingInstruction();
|
|
1223
1364
|
node.ownerDocument = this;
|
|
@@ -1449,6 +1590,19 @@ CDATASection.prototype = {
|
|
|
1449
1590
|
_extends(CDATASection,CharacterData);
|
|
1450
1591
|
|
|
1451
1592
|
|
|
1593
|
+
/**
|
|
1594
|
+
* Represents a DocumentType node (the `<!DOCTYPE ...>` declaration).
|
|
1595
|
+
*
|
|
1596
|
+
* `publicId`, `systemId`, and `internalSubset` are plain own-property assignments.
|
|
1597
|
+
* xmldom does not enforce the `readonly` constraint declared by the WHATWG DOM spec —
|
|
1598
|
+
* direct property writes succeed silently. Values are serialized verbatim when
|
|
1599
|
+
* `requireWellFormed` is false (the default). When the serializer is invoked with
|
|
1600
|
+
* `requireWellFormed: true` (via the 4th-parameter options object), it validates each
|
|
1601
|
+
* field and throws `DOMException` with code `INVALID_STATE_ERR` on invalid values.
|
|
1602
|
+
*
|
|
1603
|
+
* @class
|
|
1604
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/DocumentType MDN
|
|
1605
|
+
*/
|
|
1452
1606
|
function DocumentType() {
|
|
1453
1607
|
};
|
|
1454
1608
|
DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE;
|
|
@@ -1484,22 +1638,45 @@ function XMLSerializer(){}
|
|
|
1484
1638
|
/**
|
|
1485
1639
|
* Returns the result of serializing `node` to XML.
|
|
1486
1640
|
*
|
|
1641
|
+
* When `options.requireWellFormed` is `true`, the serializer throws for content that would
|
|
1642
|
+
* produce ill-formed XML.
|
|
1643
|
+
*
|
|
1487
1644
|
* __This implementation differs from the specification:__
|
|
1488
1645
|
* - CDATASection nodes whose data contains `]]>` are serialized by splitting the section
|
|
1489
1646
|
* at each `]]>` occurrence (following W3C DOM Level 3 Core `split-cdata-sections`
|
|
1490
|
-
* default behaviour)
|
|
1647
|
+
* default behaviour) unless `requireWellFormed` is `true`.
|
|
1648
|
+
* - when `requireWellFormed` is `true`, `DOMException` with code `INVALID_STATE_ERR`
|
|
1649
|
+
* is only thrown to prevent injection vectors, not for all the spec mandated checks.
|
|
1491
1650
|
*
|
|
1492
1651
|
* @param {Node} node
|
|
1493
1652
|
* @param {boolean} [isHtml]
|
|
1494
1653
|
* @param {function} [nodeFilter]
|
|
1654
|
+
* @param {Object} [options]
|
|
1655
|
+
* @param {boolean} [options.requireWellFormed=false]
|
|
1656
|
+
* When `true`, throws for content that would produce ill-formed XML.
|
|
1495
1657
|
* @returns {string}
|
|
1658
|
+
* @throws {DOMException}
|
|
1659
|
+
* With code `INVALID_STATE_ERR` when `requireWellFormed` is `true` and:
|
|
1660
|
+
* - a CDATASection node's data contains `"]]>"`,
|
|
1661
|
+
* - a Comment node's data contains `"-->"` (bare `"--"` does not throw on this branch),
|
|
1662
|
+
* - a ProcessingInstruction's data contains `"?>"`,
|
|
1663
|
+
* - a DocumentType's `publicId` is non-empty and does not match the XML `PubidLiteral`
|
|
1664
|
+
* production,
|
|
1665
|
+
* - a DocumentType's `systemId` is non-empty and does not match the XML `SystemLiteral`
|
|
1666
|
+
* production, or
|
|
1667
|
+
* - a DocumentType's `internalSubset` contains `"]>"`.
|
|
1668
|
+
* Note: xmldom does not enforce `readonly` on DocumentType fields — direct property
|
|
1669
|
+
* writes succeed and are covered by the serializer-level checks above.
|
|
1496
1670
|
* @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring
|
|
1671
|
+
* @see https://w3c.github.io/DOM-Parsing/#xml-serialization
|
|
1672
|
+
* @see https://github.com/w3c/DOM-Parsing/issues/84
|
|
1497
1673
|
*/
|
|
1498
|
-
XMLSerializer.prototype.serializeToString = function(node,isHtml,nodeFilter){
|
|
1499
|
-
return nodeSerializeToString.call(node,isHtml,nodeFilter);
|
|
1674
|
+
XMLSerializer.prototype.serializeToString = function(node,isHtml,nodeFilter,options){
|
|
1675
|
+
return nodeSerializeToString.call(node,isHtml,nodeFilter,options);
|
|
1500
1676
|
}
|
|
1501
1677
|
Node.prototype.toString = nodeSerializeToString;
|
|
1502
|
-
function nodeSerializeToString(isHtml,nodeFilter){
|
|
1678
|
+
function nodeSerializeToString(isHtml,nodeFilter,options){
|
|
1679
|
+
var requireWellFormed = !!options && !!options.requireWellFormed;
|
|
1503
1680
|
var buf = [];
|
|
1504
1681
|
var refNode = this.nodeType == 9 && this.documentElement || this;
|
|
1505
1682
|
var prefix = refNode.prefix;
|
|
@@ -1516,7 +1693,7 @@ function nodeSerializeToString(isHtml,nodeFilter){
|
|
|
1516
1693
|
]
|
|
1517
1694
|
}
|
|
1518
1695
|
}
|
|
1519
|
-
serializeToString(this,buf,isHtml,nodeFilter,visibleNamespaces);
|
|
1696
|
+
serializeToString(this,buf,isHtml,nodeFilter,visibleNamespaces,requireWellFormed);
|
|
1520
1697
|
//console.log('###',this.nodeType,uri,prefix,buf.join(''))
|
|
1521
1698
|
return buf.join('');
|
|
1522
1699
|
}
|
|
@@ -1565,272 +1742,323 @@ function addSerializedAttribute(buf, qualifiedName, value) {
|
|
|
1565
1742
|
buf.push(' ', qualifiedName, '="', value.replace(/[<>&"\t\n\r]/g, _xmlEncoder), '"')
|
|
1566
1743
|
}
|
|
1567
1744
|
|
|
1568
|
-
function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){
|
|
1745
|
+
function serializeToString(node, buf, isHTML, nodeFilter, visibleNamespaces, requireWellFormed) {
|
|
1569
1746
|
if (!visibleNamespaces) {
|
|
1570
1747
|
visibleNamespaces = [];
|
|
1571
1748
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
//buf.sort.apply(attrs, attributeSorter);
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
switch(node.nodeType){
|
|
1587
|
-
case ELEMENT_NODE:
|
|
1588
|
-
var attrs = node.attributes;
|
|
1589
|
-
var len = attrs.length;
|
|
1590
|
-
var child = node.firstChild;
|
|
1591
|
-
var nodeName = node.tagName;
|
|
1592
|
-
|
|
1593
|
-
isHTML = NAMESPACE.isHTML(node.namespaceURI) || isHTML
|
|
1594
|
-
|
|
1595
|
-
var prefixedNodeName = nodeName
|
|
1596
|
-
if (!isHTML && !node.prefix && node.namespaceURI) {
|
|
1597
|
-
var defaultNS
|
|
1598
|
-
// lookup current default ns from `xmlns` attribute
|
|
1599
|
-
for (var ai = 0; ai < attrs.length; ai++) {
|
|
1600
|
-
if (attrs.item(ai).name === 'xmlns') {
|
|
1601
|
-
defaultNS = attrs.item(ai).value
|
|
1602
|
-
break
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
if (!defaultNS) {
|
|
1606
|
-
// lookup current default ns in visibleNamespaces
|
|
1607
|
-
for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) {
|
|
1608
|
-
var namespace = visibleNamespaces[nsi]
|
|
1609
|
-
if (namespace.prefix === '' && namespace.namespace === node.namespaceURI) {
|
|
1610
|
-
defaultNS = namespace.namespace
|
|
1611
|
-
break
|
|
1749
|
+
walkDOM(node, { ns: visibleNamespaces, isHTML: isHTML }, {
|
|
1750
|
+
enter: function (n, ctx) {
|
|
1751
|
+
var ns = ctx.ns;
|
|
1752
|
+
var html = ctx.isHTML;
|
|
1753
|
+
|
|
1754
|
+
if (nodeFilter) {
|
|
1755
|
+
n = nodeFilter(n);
|
|
1756
|
+
if (n) {
|
|
1757
|
+
if (typeof n == 'string') {
|
|
1758
|
+
buf.push(n);
|
|
1759
|
+
return null;
|
|
1612
1760
|
}
|
|
1761
|
+
} else {
|
|
1762
|
+
return null;
|
|
1613
1763
|
}
|
|
1614
1764
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1765
|
+
|
|
1766
|
+
switch (n.nodeType) {
|
|
1767
|
+
case ELEMENT_NODE:
|
|
1768
|
+
var attrs = n.attributes;
|
|
1769
|
+
var len = attrs.length;
|
|
1770
|
+
var nodeName = n.tagName;
|
|
1771
|
+
|
|
1772
|
+
html = NAMESPACE.isHTML(n.namespaceURI) || html;
|
|
1773
|
+
|
|
1774
|
+
var prefixedNodeName = nodeName;
|
|
1775
|
+
if (!html && !n.prefix && n.namespaceURI) {
|
|
1776
|
+
var defaultNS;
|
|
1777
|
+
// lookup current default ns from `xmlns` attribute
|
|
1778
|
+
for (var ai = 0; ai < attrs.length; ai++) {
|
|
1779
|
+
if (attrs.item(ai).name === 'xmlns') {
|
|
1780
|
+
defaultNS = attrs.item(ai).value;
|
|
1781
|
+
break;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
if (!defaultNS) {
|
|
1785
|
+
// lookup current default ns in visibleNamespaces
|
|
1786
|
+
for (var nsi = ns.length - 1; nsi >= 0; nsi--) {
|
|
1787
|
+
var nsEntry = ns[nsi];
|
|
1788
|
+
if (nsEntry.prefix === '' && nsEntry.namespace === n.namespaceURI) {
|
|
1789
|
+
defaultNS = nsEntry.namespace;
|
|
1790
|
+
break;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
if (defaultNS !== n.namespaceURI) {
|
|
1795
|
+
for (var nsi = ns.length - 1; nsi >= 0; nsi--) {
|
|
1796
|
+
var nsEntry = ns[nsi];
|
|
1797
|
+
if (nsEntry.namespace === n.namespaceURI) {
|
|
1798
|
+
if (nsEntry.prefix) {
|
|
1799
|
+
prefixedNodeName = nsEntry.prefix + ':' + nodeName;
|
|
1800
|
+
}
|
|
1801
|
+
break;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1621
1804
|
}
|
|
1622
|
-
break
|
|
1623
1805
|
}
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
1806
|
|
|
1628
|
-
|
|
1807
|
+
buf.push('<', prefixedNodeName);
|
|
1808
|
+
|
|
1809
|
+
// Build a fresh namespace snapshot for this element's children.
|
|
1810
|
+
// The slice prevents sibling elements from inheriting each other's declarations.
|
|
1811
|
+
var childNs = ns.slice();
|
|
1812
|
+
for (var i = 0; i < len; i++) {
|
|
1813
|
+
var attr = attrs.item(i);
|
|
1814
|
+
if (attr.prefix == 'xmlns') {
|
|
1815
|
+
childNs.push({ prefix: attr.localName, namespace: attr.value });
|
|
1816
|
+
} else if (attr.nodeName == 'xmlns') {
|
|
1817
|
+
childNs.push({ prefix: '', namespace: attr.value });
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1629
1820
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1821
|
+
for (var i = 0; i < len; i++) {
|
|
1822
|
+
var attr = attrs.item(i);
|
|
1823
|
+
if (needNamespaceDefine(attr, html, childNs)) {
|
|
1824
|
+
var attrPrefix = attr.prefix || '';
|
|
1825
|
+
var uri = attr.namespaceURI;
|
|
1826
|
+
addSerializedAttribute(buf, attrPrefix ? 'xmlns:' + attrPrefix : 'xmlns', uri);
|
|
1827
|
+
childNs.push({ prefix: attrPrefix, namespace: uri });
|
|
1828
|
+
}
|
|
1829
|
+
// Apply nodeFilter and serialize the attribute.
|
|
1830
|
+
var filteredAttr = nodeFilter ? nodeFilter(attr) : attr;
|
|
1831
|
+
if (filteredAttr) {
|
|
1832
|
+
if (typeof filteredAttr === 'string') {
|
|
1833
|
+
buf.push(filteredAttr);
|
|
1834
|
+
} else {
|
|
1835
|
+
addSerializedAttribute(buf, filteredAttr.name, filteredAttr.value);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1639
1839
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
}
|
|
1648
|
-
serializeToString(attr,buf,isHTML,nodeFilter,visibleNamespaces);
|
|
1649
|
-
}
|
|
1840
|
+
// add namespace for current node
|
|
1841
|
+
if (nodeName === prefixedNodeName && needNamespaceDefine(n, html, childNs)) {
|
|
1842
|
+
var nodePrefix = n.prefix || '';
|
|
1843
|
+
var uri = n.namespaceURI;
|
|
1844
|
+
addSerializedAttribute(buf, nodePrefix ? 'xmlns:' + nodePrefix : 'xmlns', uri);
|
|
1845
|
+
childNs.push({ prefix: nodePrefix, namespace: uri });
|
|
1846
|
+
}
|
|
1650
1847
|
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1848
|
+
var child = n.firstChild;
|
|
1849
|
+
if (child || html && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)) {
|
|
1850
|
+
buf.push('>');
|
|
1851
|
+
if (html && /^script$/i.test(nodeName)) {
|
|
1852
|
+
// Inline serialization for <script> children; return null to skip walkDOM descent.
|
|
1853
|
+
while (child) {
|
|
1854
|
+
if (child.data) {
|
|
1855
|
+
buf.push(child.data);
|
|
1856
|
+
} else {
|
|
1857
|
+
serializeToString(child, buf, html, nodeFilter, childNs.slice(), requireWellFormed);
|
|
1858
|
+
}
|
|
1859
|
+
child = child.nextSibling;
|
|
1860
|
+
}
|
|
1861
|
+
buf.push('</', nodeName, '>');
|
|
1862
|
+
return null;
|
|
1863
|
+
}
|
|
1864
|
+
// Return child context; walkDOM descends and exit emits the closing tag.
|
|
1865
|
+
return { ns: childNs, isHTML: html, tag: prefixedNodeName };
|
|
1866
|
+
} else {
|
|
1867
|
+
buf.push('/>');
|
|
1868
|
+
return null;
|
|
1869
|
+
}
|
|
1658
1870
|
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1871
|
+
case DOCUMENT_NODE:
|
|
1872
|
+
case DOCUMENT_FRAGMENT_NODE:
|
|
1873
|
+
// Descend into children; exit is a no-op (tag is null).
|
|
1874
|
+
return { ns: ns.slice(), isHTML: html, tag: null };
|
|
1875
|
+
|
|
1876
|
+
case ATTRIBUTE_NODE:
|
|
1877
|
+
addSerializedAttribute(buf, n.name, n.value);
|
|
1878
|
+
return null;
|
|
1879
|
+
|
|
1880
|
+
case TEXT_NODE:
|
|
1881
|
+
/**
|
|
1882
|
+
* The ampersand character (&) and the left angle bracket (<) must not appear in their literal form,
|
|
1883
|
+
* except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section.
|
|
1884
|
+
* If they are needed elsewhere, they must be escaped using either numeric character references or the strings
|
|
1885
|
+
* `&` and `<` respectively.
|
|
1886
|
+
* The right angle bracket (>) may be represented using the string " > ", and must, for compatibility,
|
|
1887
|
+
* be escaped using either `>` or a character reference when it appears in the string `]]>` in content,
|
|
1888
|
+
* when that string is not marking the end of a CDATA section.
|
|
1889
|
+
*
|
|
1890
|
+
* In the content of elements, character data is any string of characters
|
|
1891
|
+
* which does not contain the start-delimiter of any markup
|
|
1892
|
+
* and does not include the CDATA-section-close delimiter, `]]>`.
|
|
1893
|
+
*
|
|
1894
|
+
* @see https://www.w3.org/TR/xml/#NT-CharData
|
|
1895
|
+
* @see https://w3c.github.io/DOM-Parsing/#xml-serializing-a-text-node
|
|
1896
|
+
*/
|
|
1897
|
+
buf.push(n.data.replace(/[<&>]/g, _xmlEncoder));
|
|
1898
|
+
return null;
|
|
1899
|
+
|
|
1900
|
+
case CDATA_SECTION_NODE:
|
|
1901
|
+
if (requireWellFormed && n.data.indexOf(']]>') !== -1) {
|
|
1902
|
+
throw new DOMException(INVALID_STATE_ERR, 'The CDATASection data contains "]]>"');
|
|
1668
1903
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1904
|
+
buf.push('<![CDATA[', n.data.replace(/]]>/g, ']]]]><![CDATA[>'), ']]>');
|
|
1905
|
+
return null;
|
|
1906
|
+
|
|
1907
|
+
case COMMENT_NODE:
|
|
1908
|
+
if (requireWellFormed && n.data.indexOf('-->') !== -1) {
|
|
1909
|
+
throw new DOMException(INVALID_STATE_ERR, 'The comment node data contains "-->"');
|
|
1910
|
+
}
|
|
1911
|
+
buf.push('<!--', n.data, '-->');
|
|
1912
|
+
return null;
|
|
1913
|
+
|
|
1914
|
+
case DOCUMENT_TYPE_NODE:
|
|
1915
|
+
if (requireWellFormed) {
|
|
1916
|
+
if (n.publicId && !/^("[\x20\r\na-zA-Z0-9\-()+,.\/:=?;!*#@$_%']*"|'[\x20\r\na-zA-Z0-9\-()+,.\/:=?;!*#@$_%'"]*')$/.test(n.publicId)) {
|
|
1917
|
+
throw new DOMException(INVALID_STATE_ERR, 'DocumentType publicId is not a valid PubidLiteral');
|
|
1918
|
+
}
|
|
1919
|
+
if (n.systemId && !/^("[^"]*"|'[^']*')$/.test(n.systemId)) {
|
|
1920
|
+
throw new DOMException(INVALID_STATE_ERR, 'DocumentType systemId is not a valid SystemLiteral');
|
|
1921
|
+
}
|
|
1922
|
+
if (n.internalSubset && n.internalSubset.indexOf(']>') !== -1) {
|
|
1923
|
+
throw new DOMException(INVALID_STATE_ERR, 'DocumentType internalSubset contains "]>"');
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
var pubid = n.publicId;
|
|
1927
|
+
var sysid = n.systemId;
|
|
1928
|
+
buf.push('<!DOCTYPE ', n.name);
|
|
1929
|
+
if (pubid) {
|
|
1930
|
+
buf.push(' PUBLIC ', pubid);
|
|
1931
|
+
if (sysid && sysid != '.') {
|
|
1932
|
+
buf.push(' ', sysid);
|
|
1933
|
+
}
|
|
1934
|
+
buf.push('>');
|
|
1935
|
+
} else if (sysid && sysid != '.') {
|
|
1936
|
+
buf.push(' SYSTEM ', sysid, '>');
|
|
1937
|
+
} else {
|
|
1938
|
+
var sub = n.internalSubset;
|
|
1939
|
+
if (sub) {
|
|
1940
|
+
buf.push(' [', sub, ']');
|
|
1941
|
+
}
|
|
1942
|
+
buf.push('>');
|
|
1943
|
+
}
|
|
1944
|
+
return null;
|
|
1945
|
+
|
|
1946
|
+
case PROCESSING_INSTRUCTION_NODE:
|
|
1947
|
+
if (requireWellFormed && n.data.indexOf('?>') !== -1) {
|
|
1948
|
+
throw new DOMException(INVALID_STATE_ERR, 'The ProcessingInstruction data contains "?>"');
|
|
1949
|
+
}
|
|
1950
|
+
buf.push('<?', n.target, ' ', n.data, '?>');
|
|
1951
|
+
return null;
|
|
1952
|
+
|
|
1953
|
+
case ENTITY_REFERENCE_NODE:
|
|
1954
|
+
buf.push('&', n.nodeName, ';');
|
|
1955
|
+
return null;
|
|
1956
|
+
|
|
1957
|
+
//case ENTITY_NODE:
|
|
1958
|
+
//case NOTATION_NODE:
|
|
1959
|
+
default:
|
|
1960
|
+
buf.push('??', n.nodeName);
|
|
1961
|
+
return null;
|
|
1727
1962
|
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
var sub = node.internalSubset;
|
|
1733
|
-
if(sub){
|
|
1734
|
-
buf.push(" [",sub,"]");
|
|
1963
|
+
},
|
|
1964
|
+
exit: function (n, childCtx) {
|
|
1965
|
+
if (childCtx && childCtx.tag) {
|
|
1966
|
+
buf.push('</', childCtx.tag, '>');
|
|
1735
1967
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
return;
|
|
1739
|
-
case PROCESSING_INSTRUCTION_NODE:
|
|
1740
|
-
return buf.push( "<?",node.target," ",node.data,"?>");
|
|
1741
|
-
case ENTITY_REFERENCE_NODE:
|
|
1742
|
-
return buf.push( '&',node.nodeName,';');
|
|
1743
|
-
//case ENTITY_NODE:
|
|
1744
|
-
//case NOTATION_NODE:
|
|
1745
|
-
default:
|
|
1746
|
-
buf.push('??',node.nodeName);
|
|
1747
|
-
}
|
|
1968
|
+
},
|
|
1969
|
+
});
|
|
1748
1970
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
var child = node.firstChild;
|
|
1787
|
-
while(child){
|
|
1788
|
-
node2.appendChild(importNode(doc,child,deep));
|
|
1789
|
-
child = child.nextSibling;
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
return node2;
|
|
1971
|
+
/**
|
|
1972
|
+
* Imports a node from a different document into `doc`, creating a new copy.
|
|
1973
|
+
* Delegates to {@link walkDOM} for traversal. Each node in the subtree is shallow-cloned,
|
|
1974
|
+
* stamped with `doc` as its `ownerDocument`, and detached (`parentNode` set to `null`).
|
|
1975
|
+
* Children are imported recursively when `deep` is `true`; for {@link Attr} nodes `deep` is
|
|
1976
|
+
* always forced to `true`
|
|
1977
|
+
* because an attribute's value lives in a child text node.
|
|
1978
|
+
*
|
|
1979
|
+
* @param {Document} doc
|
|
1980
|
+
* The document that will own the imported node.
|
|
1981
|
+
* @param {Node} node
|
|
1982
|
+
* The node to import.
|
|
1983
|
+
* @param {boolean} deep
|
|
1984
|
+
* If `true`, descendants are imported recursively.
|
|
1985
|
+
* @returns {Node}
|
|
1986
|
+
* The newly imported node, now owned by `doc`.
|
|
1987
|
+
*/
|
|
1988
|
+
function importNode(doc, node, deep) {
|
|
1989
|
+
var destRoot;
|
|
1990
|
+
walkDOM(node, null, {
|
|
1991
|
+
enter: function (srcNode, destParent) {
|
|
1992
|
+
// Shallow-clone the node and stamp it into the target document.
|
|
1993
|
+
var destNode = srcNode.cloneNode(false);
|
|
1994
|
+
destNode.ownerDocument = doc;
|
|
1995
|
+
destNode.parentNode = null;
|
|
1996
|
+
// capture as the root of the imported subtree or attach to parent.
|
|
1997
|
+
if (destParent === null) {
|
|
1998
|
+
destRoot = destNode;
|
|
1999
|
+
} else {
|
|
2000
|
+
destParent.appendChild(destNode);
|
|
2001
|
+
}
|
|
2002
|
+
// ATTRIBUTE_NODE must always be imported deeply: its value lives in a child text node.
|
|
2003
|
+
var shouldDeep = srcNode.nodeType === ATTRIBUTE_NODE || deep;
|
|
2004
|
+
return shouldDeep ? destNode : null;
|
|
2005
|
+
},
|
|
2006
|
+
});
|
|
2007
|
+
return destRoot;
|
|
1793
2008
|
}
|
|
1794
2009
|
//
|
|
1795
2010
|
//var _relationMap = {firstChild:1,lastChild:1,previousSibling:1,nextSibling:1,
|
|
1796
2011
|
// attributes:1,childNodes:1,parentNode:1,documentElement:1,doctype,};
|
|
1797
|
-
function cloneNode(doc,node,deep){
|
|
1798
|
-
var
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
2012
|
+
function cloneNode(doc, node, deep) {
|
|
2013
|
+
var destRoot;
|
|
2014
|
+
walkDOM(node, null, {
|
|
2015
|
+
enter: function (srcNode, destParent) {
|
|
2016
|
+
// 1. Create a blank node of the same type and copy all scalar own properties.
|
|
2017
|
+
var destNode = new srcNode.constructor();
|
|
2018
|
+
for (var n in srcNode) {
|
|
2019
|
+
if (Object.prototype.hasOwnProperty.call(srcNode, n)) {
|
|
2020
|
+
var v = srcNode[n];
|
|
2021
|
+
if (typeof v != 'object') {
|
|
2022
|
+
if (v != destNode[n]) {
|
|
2023
|
+
destNode[n] = v;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
1805
2026
|
}
|
|
1806
2027
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
2028
|
+
if (srcNode.childNodes) {
|
|
2029
|
+
destNode.childNodes = new NodeList();
|
|
2030
|
+
}
|
|
2031
|
+
destNode.ownerDocument = doc;
|
|
2032
|
+
// 2. Handle node-type-specific setup.
|
|
2033
|
+
// Attributes are not DOM children, so they are cloned inline here
|
|
2034
|
+
// rather than by walkDOM descent.
|
|
2035
|
+
// ATTRIBUTE_NODE forces deep=true so its own children are walked.
|
|
2036
|
+
var shouldDeep = deep;
|
|
2037
|
+
switch (destNode.nodeType) {
|
|
2038
|
+
case ELEMENT_NODE:
|
|
2039
|
+
var attrs = srcNode.attributes;
|
|
2040
|
+
var attrs2 = (destNode.attributes = new NamedNodeMap());
|
|
2041
|
+
var len = attrs.length;
|
|
2042
|
+
attrs2._ownerElement = destNode;
|
|
2043
|
+
for (var i = 0; i < len; i++) {
|
|
2044
|
+
destNode.setAttributeNode(cloneNode(doc, attrs.item(i), true));
|
|
2045
|
+
}
|
|
2046
|
+
break;
|
|
2047
|
+
case ATTRIBUTE_NODE:
|
|
2048
|
+
shouldDeep = true;
|
|
2049
|
+
}
|
|
2050
|
+
// 3. Attach to parent, or capture as the root of the cloned subtree.
|
|
2051
|
+
if (destParent !== null) {
|
|
2052
|
+
destParent.appendChild(destNode);
|
|
2053
|
+
} else {
|
|
2054
|
+
destRoot = destNode;
|
|
2055
|
+
}
|
|
2056
|
+
// 4. Return destNode as the context for children (causes walkDOM to descend),
|
|
2057
|
+
// or null to skip children (shallow clone).
|
|
2058
|
+
return shouldDeep ? destNode : null;
|
|
2059
|
+
},
|
|
2060
|
+
});
|
|
2061
|
+
return destRoot;
|
|
1834
2062
|
}
|
|
1835
2063
|
|
|
1836
2064
|
function __set__(object,key,value){
|
|
@@ -1846,49 +2074,55 @@ try{
|
|
|
1846
2074
|
}
|
|
1847
2075
|
});
|
|
1848
2076
|
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
2077
|
+
/**
|
|
2078
|
+
* The text content of this node and its descendants.
|
|
2079
|
+
*
|
|
2080
|
+
* Setting `textContent` on an element or document fragment replaces all child nodes with a
|
|
2081
|
+
* single text node; on other nodes it sets `data`, `value`, and `nodeValue` directly.
|
|
2082
|
+
*
|
|
2083
|
+
* @type {string | null}
|
|
2084
|
+
* @see {@link https://dom.spec.whatwg.org/#dom-node-textcontent}
|
|
2085
|
+
*/
|
|
2086
|
+
Object.defineProperty(Node.prototype, 'textContent', {
|
|
2087
|
+
get: function () {
|
|
2088
|
+
if (this.nodeType === ELEMENT_NODE || this.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
2089
|
+
var buf = [];
|
|
2090
|
+
walkDOM(this, null, {
|
|
2091
|
+
enter: function (n) {
|
|
2092
|
+
if (n.nodeType === ELEMENT_NODE || n.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
2093
|
+
return true; // enter children
|
|
2094
|
+
}
|
|
2095
|
+
if (n.nodeType === PROCESSING_INSTRUCTION_NODE || n.nodeType === COMMENT_NODE) {
|
|
2096
|
+
return null; // excluded from text content
|
|
2097
|
+
}
|
|
2098
|
+
buf.push(n.nodeValue);
|
|
2099
|
+
},
|
|
2100
|
+
});
|
|
2101
|
+
return buf.join('');
|
|
2102
|
+
}
|
|
2103
|
+
return this.nodeValue;
|
|
1852
2104
|
},
|
|
1853
2105
|
|
|
1854
|
-
set:function(data){
|
|
1855
|
-
switch(this.nodeType){
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
2106
|
+
set: function (data) {
|
|
2107
|
+
switch (this.nodeType) {
|
|
2108
|
+
case ELEMENT_NODE:
|
|
2109
|
+
case DOCUMENT_FRAGMENT_NODE:
|
|
2110
|
+
while (this.firstChild) {
|
|
2111
|
+
this.removeChild(this.firstChild);
|
|
2112
|
+
}
|
|
2113
|
+
if (data || String(data)) {
|
|
2114
|
+
this.appendChild(this.ownerDocument.createTextNode(data));
|
|
2115
|
+
}
|
|
2116
|
+
break;
|
|
1865
2117
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
2118
|
+
default:
|
|
2119
|
+
this.data = data;
|
|
2120
|
+
this.value = data;
|
|
2121
|
+
this.nodeValue = data;
|
|
1870
2122
|
}
|
|
1871
|
-
}
|
|
2123
|
+
},
|
|
1872
2124
|
})
|
|
1873
2125
|
|
|
1874
|
-
function getTextContent(node){
|
|
1875
|
-
switch(node.nodeType){
|
|
1876
|
-
case ELEMENT_NODE:
|
|
1877
|
-
case DOCUMENT_FRAGMENT_NODE:
|
|
1878
|
-
var buf = [];
|
|
1879
|
-
node = node.firstChild;
|
|
1880
|
-
while(node){
|
|
1881
|
-
if(node.nodeType!==7 && node.nodeType !==8){
|
|
1882
|
-
buf.push(getTextContent(node));
|
|
1883
|
-
}
|
|
1884
|
-
node = node.nextSibling;
|
|
1885
|
-
}
|
|
1886
|
-
return buf.join('');
|
|
1887
|
-
default:
|
|
1888
|
-
return node.nodeValue;
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
2126
|
__set__ = function(object,key,value){
|
|
1893
2127
|
//console.log(value)
|
|
1894
2128
|
object['$$'+key] = value
|
|
@@ -1904,5 +2138,6 @@ try{
|
|
|
1904
2138
|
exports.Element = Element;
|
|
1905
2139
|
exports.Node = Node;
|
|
1906
2140
|
exports.NodeList = NodeList;
|
|
2141
|
+
exports.walkDOM = walkDOM;
|
|
1907
2142
|
exports.XMLSerializer = XMLSerializer;
|
|
1908
2143
|
//}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmldom/xmldom",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.13",
|
|
4
4
|
"description": "A pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"w3c",
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"index.d.ts",
|
|
28
28
|
"lib"
|
|
29
29
|
],
|
|
30
|
+
"config": {
|
|
31
|
+
"test_stack_size": 256
|
|
32
|
+
},
|
|
30
33
|
"scripts": {
|
|
31
34
|
"lint": "eslint lib test",
|
|
32
35
|
"format": "prettier --write test",
|
|
@@ -35,7 +38,7 @@
|
|
|
35
38
|
"start": "nodemon --watch package.json --watch lib --watch test --exec 'npm --silent run test && npm --silent run lint'",
|
|
36
39
|
"stryker": "stryker run",
|
|
37
40
|
"stryker:dry-run": "stryker run -m '' --reporters progress",
|
|
38
|
-
"test": "jest",
|
|
41
|
+
"test": "node --stack-size=$npm_package_config_test_stack_size ./node_modules/.bin/jest",
|
|
39
42
|
"testrelease": "npm test && eslint lib",
|
|
40
43
|
"version": "./changelog-has-version.sh",
|
|
41
44
|
"release": "np --no-yarn --test-script testrelease --branch release-0.8.x patch"
|