@xmldom/xmldom 0.8.11 → 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 CHANGED
@@ -4,11 +4,45 @@ 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
+
26
+ ## [0.8.12](https://github.com/xmldom/xmldom/compare/0.8.11...0.8.12)
27
+
28
+ ### Fixed
29
+
30
+ - preserve trailing whitespace in ProcessingInstruction data [`#962`](https://github.com/xmldom/xmldom/pull/962) / [`#42`](https://github.com/xmldom/xmldom/issues/42)
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)
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)
33
+
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.
35
+
36
+ Thank you,
37
+ [@thesmartshadow](https://github.com/thesmartshadow),
38
+ [@stevenobiajulu](https://github.com/stevenobiajulu),
39
+ for your contributions
40
+
7
41
  ## [0.8.11](https://github.com/xmldom/xmldom/compare/0.8.10...0.8.11)
8
42
 
9
43
  ### Fixed
10
44
 
11
- - update `ownerDocument` when moving nodes between documents
45
+ - update `ownerDocument` when moving nodes between documents [`#933`](https://github.com/xmldom/xmldom/pull/933) / [`#932`](https://github.com/xmldom/xmldom/issues/932)
12
46
 
13
47
  Thank you, [@shunkica](https://github.com/shunkica), for your contributions
14
48
 
package/index.d.ts CHANGED
@@ -22,8 +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
- serializeToString(node: Node): string;
41
+ /**
42
+ * Returns the result of serializing `node` to XML.
43
+ *
44
+ * When `options.requireWellFormed` is `true`, the serializer throws for content that would
45
+ * produce ill-formed XML.
46
+ *
47
+ * __This implementation differs from the specification:__
48
+ * - CDATASection nodes whose data contains `]]>` are serialized by splitting the section
49
+ * at each `]]>` occurrence (following W3C DOM Level 3 Core `split-cdata-sections`
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.
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.
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
69
+ */
70
+ serializeToString(node: Node, isHtml?: boolean, nodeFilter?: (node: Node) => Node | null | undefined, options?: XMLSerializerOptions): string;
27
71
  }
28
72
 
29
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 behavior is slightly different from the in the specs__:
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
- normalize:function(){
496
- var child = this.firstChild;
497
- while(child){
498
- var next = child.nextSibling;
499
- if(next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE){
500
- this.removeChild(next);
501
- child.appendData(next.data);
502
- }else{
503
- child.normalize();
504
- child = next;
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 callback return true for continue,false for break
583
- * @return boolean true: break visit;
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
- if(callback(node)){
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;
@@ -1197,12 +1321,44 @@ Document.prototype = {
1197
1321
  node.appendData(data)
1198
1322
  return node;
1199
1323
  },
1324
+ /**
1325
+ * Returns a new CDATASection node whose data is `data`.
1326
+ *
1327
+ * __This implementation differs from the specification:__
1328
+ * - calling this method on an HTML document does not throw `NotSupportedError`.
1329
+ *
1330
+ * @param {string} data
1331
+ * @returns {CDATASection}
1332
+ * @throws DOMException with code `INVALID_CHARACTER_ERR` if `data` contains `"]]>"`.
1333
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createCDATASection
1334
+ * @see https://dom.spec.whatwg.org/#dom-document-createcdatasection
1335
+ */
1200
1336
  createCDATASection : function(data){
1337
+ if (data.indexOf(']]>') !== -1) {
1338
+ throw new DOMException(INVALID_CHARACTER_ERR, 'data contains "]]>"');
1339
+ }
1201
1340
  var node = new CDATASection();
1202
1341
  node.ownerDocument = this;
1203
1342
  node.appendData(data)
1204
1343
  return node;
1205
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
+ */
1206
1362
  createProcessingInstruction : function(target,data){
1207
1363
  var node = new ProcessingInstruction();
1208
1364
  node.ownerDocument = this;
@@ -1434,6 +1590,19 @@ CDATASection.prototype = {
1434
1590
  _extends(CDATASection,CharacterData);
1435
1591
 
1436
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
+ */
1437
1606
  function DocumentType() {
1438
1607
  };
1439
1608
  DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE;
@@ -1466,11 +1635,48 @@ function ProcessingInstruction() {
1466
1635
  ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE;
1467
1636
  _extends(ProcessingInstruction,Node);
1468
1637
  function XMLSerializer(){}
1469
- XMLSerializer.prototype.serializeToString = function(node,isHtml,nodeFilter){
1470
- return nodeSerializeToString.call(node,isHtml,nodeFilter);
1638
+ /**
1639
+ * Returns the result of serializing `node` to XML.
1640
+ *
1641
+ * When `options.requireWellFormed` is `true`, the serializer throws for content that would
1642
+ * produce ill-formed XML.
1643
+ *
1644
+ * __This implementation differs from the specification:__
1645
+ * - CDATASection nodes whose data contains `]]>` are serialized by splitting the section
1646
+ * at each `]]>` occurrence (following W3C DOM Level 3 Core `split-cdata-sections`
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.
1650
+ *
1651
+ * @param {Node} node
1652
+ * @param {boolean} [isHtml]
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.
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.
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
1673
+ */
1674
+ XMLSerializer.prototype.serializeToString = function(node,isHtml,nodeFilter,options){
1675
+ return nodeSerializeToString.call(node,isHtml,nodeFilter,options);
1471
1676
  }
1472
1677
  Node.prototype.toString = nodeSerializeToString;
1473
- function nodeSerializeToString(isHtml,nodeFilter){
1678
+ function nodeSerializeToString(isHtml,nodeFilter,options){
1679
+ var requireWellFormed = !!options && !!options.requireWellFormed;
1474
1680
  var buf = [];
1475
1681
  var refNode = this.nodeType == 9 && this.documentElement || this;
1476
1682
  var prefix = refNode.prefix;
@@ -1487,7 +1693,7 @@ function nodeSerializeToString(isHtml,nodeFilter){
1487
1693
  ]
1488
1694
  }
1489
1695
  }
1490
- serializeToString(this,buf,isHtml,nodeFilter,visibleNamespaces);
1696
+ serializeToString(this,buf,isHtml,nodeFilter,visibleNamespaces,requireWellFormed);
1491
1697
  //console.log('###',this.nodeType,uri,prefix,buf.join(''))
1492
1698
  return buf.join('');
1493
1699
  }
@@ -1536,272 +1742,323 @@ function addSerializedAttribute(buf, qualifiedName, value) {
1536
1742
  buf.push(' ', qualifiedName, '="', value.replace(/[<>&"\t\n\r]/g, _xmlEncoder), '"')
1537
1743
  }
1538
1744
 
1539
- function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){
1745
+ function serializeToString(node, buf, isHTML, nodeFilter, visibleNamespaces, requireWellFormed) {
1540
1746
  if (!visibleNamespaces) {
1541
1747
  visibleNamespaces = [];
1542
1748
  }
1543
-
1544
- if(nodeFilter){
1545
- node = nodeFilter(node);
1546
- if(node){
1547
- if(typeof node == 'string'){
1548
- buf.push(node);
1549
- return;
1550
- }
1551
- }else{
1552
- return;
1553
- }
1554
- //buf.sort.apply(attrs, attributeSorter);
1555
- }
1556
-
1557
- switch(node.nodeType){
1558
- case ELEMENT_NODE:
1559
- var attrs = node.attributes;
1560
- var len = attrs.length;
1561
- var child = node.firstChild;
1562
- var nodeName = node.tagName;
1563
-
1564
- isHTML = NAMESPACE.isHTML(node.namespaceURI) || isHTML
1565
-
1566
- var prefixedNodeName = nodeName
1567
- if (!isHTML && !node.prefix && node.namespaceURI) {
1568
- var defaultNS
1569
- // lookup current default ns from `xmlns` attribute
1570
- for (var ai = 0; ai < attrs.length; ai++) {
1571
- if (attrs.item(ai).name === 'xmlns') {
1572
- defaultNS = attrs.item(ai).value
1573
- break
1574
- }
1575
- }
1576
- if (!defaultNS) {
1577
- // lookup current default ns in visibleNamespaces
1578
- for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) {
1579
- var namespace = visibleNamespaces[nsi]
1580
- if (namespace.prefix === '' && namespace.namespace === node.namespaceURI) {
1581
- defaultNS = namespace.namespace
1582
- 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;
1583
1760
  }
1761
+ } else {
1762
+ return null;
1584
1763
  }
1585
1764
  }
1586
- if (defaultNS !== node.namespaceURI) {
1587
- for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) {
1588
- var namespace = visibleNamespaces[nsi]
1589
- if (namespace.namespace === node.namespaceURI) {
1590
- if (namespace.prefix) {
1591
- prefixedNodeName = namespace.prefix + ':' + nodeName
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
+ }
1592
1804
  }
1593
- break
1594
1805
  }
1595
- }
1596
- }
1597
- }
1598
1806
 
1599
- buf.push('<', prefixedNodeName);
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
+ }
1600
1820
 
1601
- for(var i=0;i<len;i++){
1602
- // add namespaces for attributes
1603
- var attr = attrs.item(i);
1604
- if (attr.prefix == 'xmlns') {
1605
- visibleNamespaces.push({ prefix: attr.localName, namespace: attr.value });
1606
- }else if(attr.nodeName == 'xmlns'){
1607
- visibleNamespaces.push({ prefix: '', namespace: attr.value });
1608
- }
1609
- }
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
+ }
1610
1839
 
1611
- for(var i=0;i<len;i++){
1612
- var attr = attrs.item(i);
1613
- if (needNamespaceDefine(attr,isHTML, visibleNamespaces)) {
1614
- var prefix = attr.prefix||'';
1615
- var uri = attr.namespaceURI;
1616
- addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri);
1617
- visibleNamespaces.push({ prefix: prefix, namespace:uri });
1618
- }
1619
- serializeToString(attr,buf,isHTML,nodeFilter,visibleNamespaces);
1620
- }
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
+ }
1621
1847
 
1622
- // add namespace for current node
1623
- if (nodeName === prefixedNodeName && needNamespaceDefine(node, isHTML, visibleNamespaces)) {
1624
- var prefix = node.prefix||'';
1625
- var uri = node.namespaceURI;
1626
- addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri);
1627
- visibleNamespaces.push({ prefix: prefix, namespace:uri });
1628
- }
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
+ }
1629
1870
 
1630
- if(child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)){
1631
- buf.push('>');
1632
- //if is cdata child node
1633
- if(isHTML && /^script$/i.test(nodeName)){
1634
- while(child){
1635
- if(child.data){
1636
- buf.push(child.data);
1637
- }else{
1638
- serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
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
+ * `&amp;` and `&lt;` respectively.
1886
+ * The right angle bracket (>) may be represented using the string " &gt; ", and must, for compatibility,
1887
+ * be escaped using either `&gt;` 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 "]]>"');
1639
1903
  }
1640
- child = child.nextSibling;
1641
- }
1642
- }else
1643
- {
1644
- while(child){
1645
- serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
1646
- child = child.nextSibling;
1647
- }
1648
- }
1649
- buf.push('</',prefixedNodeName,'>');
1650
- }else{
1651
- buf.push('/>');
1652
- }
1653
- // remove added visible namespaces
1654
- //visibleNamespaces.length = startVisibleNamespaces;
1655
- return;
1656
- case DOCUMENT_NODE:
1657
- case DOCUMENT_FRAGMENT_NODE:
1658
- var child = node.firstChild;
1659
- while(child){
1660
- serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
1661
- child = child.nextSibling;
1662
- }
1663
- return;
1664
- case ATTRIBUTE_NODE:
1665
- return addSerializedAttribute(buf, node.name, node.value);
1666
- case TEXT_NODE:
1667
- /**
1668
- * The ampersand character (&) and the left angle bracket (<) must not appear in their literal form,
1669
- * except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section.
1670
- * If they are needed elsewhere, they must be escaped using either numeric character references or the strings
1671
- * `&amp;` and `&lt;` respectively.
1672
- * The right angle bracket (>) may be represented using the string " &gt; ", and must, for compatibility,
1673
- * be escaped using either `&gt;` or a character reference when it appears in the string `]]>` in content,
1674
- * when that string is not marking the end of a CDATA section.
1675
- *
1676
- * In the content of elements, character data is any string of characters
1677
- * which does not contain the start-delimiter of any markup
1678
- * and does not include the CDATA-section-close delimiter, `]]>`.
1679
- *
1680
- * @see https://www.w3.org/TR/xml/#NT-CharData
1681
- * @see https://w3c.github.io/DOM-Parsing/#xml-serializing-a-text-node
1682
- */
1683
- return buf.push(node.data
1684
- .replace(/[<&>]/g,_xmlEncoder)
1685
- );
1686
- case CDATA_SECTION_NODE:
1687
- return buf.push( '<![CDATA[',node.data,']]>');
1688
- case COMMENT_NODE:
1689
- return buf.push( "<!--",node.data,"-->");
1690
- case DOCUMENT_TYPE_NODE:
1691
- var pubid = node.publicId;
1692
- var sysid = node.systemId;
1693
- buf.push('<!DOCTYPE ',node.name);
1694
- if(pubid){
1695
- buf.push(' PUBLIC ', pubid);
1696
- if (sysid && sysid!='.') {
1697
- buf.push(' ', sysid);
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;
1698
1962
  }
1699
- buf.push('>');
1700
- }else if(sysid && sysid!='.'){
1701
- buf.push(' SYSTEM ', sysid, '>');
1702
- }else{
1703
- var sub = node.internalSubset;
1704
- if(sub){
1705
- buf.push(" [",sub,"]");
1963
+ },
1964
+ exit: function (n, childCtx) {
1965
+ if (childCtx && childCtx.tag) {
1966
+ buf.push('</', childCtx.tag, '>');
1706
1967
  }
1707
- buf.push(">");
1708
- }
1709
- return;
1710
- case PROCESSING_INSTRUCTION_NODE:
1711
- return buf.push( "<?",node.target," ",node.data,"?>");
1712
- case ENTITY_REFERENCE_NODE:
1713
- return buf.push( '&',node.nodeName,';');
1714
- //case ENTITY_NODE:
1715
- //case NOTATION_NODE:
1716
- default:
1717
- buf.push('??',node.nodeName);
1718
- }
1968
+ },
1969
+ });
1719
1970
  }
1720
- function importNode(doc,node,deep){
1721
- var node2;
1722
- switch (node.nodeType) {
1723
- case ELEMENT_NODE:
1724
- node2 = node.cloneNode(false);
1725
- node2.ownerDocument = doc;
1726
- //var attrs = node2.attributes;
1727
- //var len = attrs.length;
1728
- //for(var i=0;i<len;i++){
1729
- //node2.setAttributeNodeNS(importNode(doc,attrs.item(i),deep));
1730
- //}
1731
- case DOCUMENT_FRAGMENT_NODE:
1732
- break;
1733
- case ATTRIBUTE_NODE:
1734
- deep = true;
1735
- break;
1736
- //case ENTITY_REFERENCE_NODE:
1737
- //case PROCESSING_INSTRUCTION_NODE:
1738
- ////case TEXT_NODE:
1739
- //case CDATA_SECTION_NODE:
1740
- //case COMMENT_NODE:
1741
- // deep = false;
1742
- // break;
1743
- //case DOCUMENT_NODE:
1744
- //case DOCUMENT_TYPE_NODE:
1745
- //cannot be imported.
1746
- //case ENTITY_NODE:
1747
- //case NOTATION_NODE:
1748
- //can not hit in level3
1749
- //default:throw e;
1750
- }
1751
- if(!node2){
1752
- node2 = node.cloneNode(false);//false
1753
- }
1754
- node2.ownerDocument = doc;
1755
- node2.parentNode = null;
1756
- if(deep){
1757
- var child = node.firstChild;
1758
- while(child){
1759
- node2.appendChild(importNode(doc,child,deep));
1760
- child = child.nextSibling;
1761
- }
1762
- }
1763
- 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;
1764
2008
  }
1765
2009
  //
1766
2010
  //var _relationMap = {firstChild:1,lastChild:1,previousSibling:1,nextSibling:1,
1767
2011
  // attributes:1,childNodes:1,parentNode:1,documentElement:1,doctype,};
1768
- function cloneNode(doc,node,deep){
1769
- var node2 = new node.constructor();
1770
- for (var n in node) {
1771
- if (Object.prototype.hasOwnProperty.call(node, n)) {
1772
- var v = node[n];
1773
- if (typeof v != "object") {
1774
- if (v != node2[n]) {
1775
- node2[n] = v;
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
+ }
1776
2026
  }
1777
2027
  }
1778
- }
1779
- }
1780
- if(node.childNodes){
1781
- node2.childNodes = new NodeList();
1782
- }
1783
- node2.ownerDocument = doc;
1784
- switch (node2.nodeType) {
1785
- case ELEMENT_NODE:
1786
- var attrs = node.attributes;
1787
- var attrs2 = node2.attributes = new NamedNodeMap();
1788
- var len = attrs.length
1789
- attrs2._ownerElement = node2;
1790
- for(var i=0;i<len;i++){
1791
- node2.setAttributeNode(cloneNode(doc,attrs.item(i),true));
1792
- }
1793
- break;;
1794
- case ATTRIBUTE_NODE:
1795
- deep = true;
1796
- }
1797
- if(deep){
1798
- var child = node.firstChild;
1799
- while(child){
1800
- node2.appendChild(cloneNode(doc,child,deep));
1801
- child = child.nextSibling;
1802
- }
1803
- }
1804
- return node2;
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;
1805
2062
  }
1806
2063
 
1807
2064
  function __set__(object,key,value){
@@ -1817,49 +2074,55 @@ try{
1817
2074
  }
1818
2075
  });
1819
2076
 
1820
- Object.defineProperty(Node.prototype,'textContent',{
1821
- get:function(){
1822
- return getTextContent(this);
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;
1823
2104
  },
1824
2105
 
1825
- set:function(data){
1826
- switch(this.nodeType){
1827
- case ELEMENT_NODE:
1828
- case DOCUMENT_FRAGMENT_NODE:
1829
- while(this.firstChild){
1830
- this.removeChild(this.firstChild);
1831
- }
1832
- if(data || String(data)){
1833
- this.appendChild(this.ownerDocument.createTextNode(data));
1834
- }
1835
- break;
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;
1836
2117
 
1837
- default:
1838
- this.data = data;
1839
- this.value = data;
1840
- this.nodeValue = data;
2118
+ default:
2119
+ this.data = data;
2120
+ this.value = data;
2121
+ this.nodeValue = data;
1841
2122
  }
1842
- }
2123
+ },
1843
2124
  })
1844
2125
 
1845
- function getTextContent(node){
1846
- switch(node.nodeType){
1847
- case ELEMENT_NODE:
1848
- case DOCUMENT_FRAGMENT_NODE:
1849
- var buf = [];
1850
- node = node.firstChild;
1851
- while(node){
1852
- if(node.nodeType!==7 && node.nodeType !==8){
1853
- buf.push(getTextContent(node));
1854
- }
1855
- node = node.nextSibling;
1856
- }
1857
- return buf.join('');
1858
- default:
1859
- return node.nodeValue;
1860
- }
1861
- }
1862
-
1863
2126
  __set__ = function(object,key,value){
1864
2127
  //console.log(value)
1865
2128
  object['$$'+key] = value
@@ -1875,5 +2138,6 @@ try{
1875
2138
  exports.Element = Element;
1876
2139
  exports.Node = Node;
1877
2140
  exports.NodeList = NodeList;
2141
+ exports.walkDOM = walkDOM;
1878
2142
  exports.XMLSerializer = XMLSerializer;
1879
2143
  //}
package/lib/sax.js CHANGED
@@ -597,7 +597,7 @@ function parseDCC(source,start,domBuilder,errorHandler){//sure start with '<!'
597
597
  function parseInstruction(source,start,domBuilder){
598
598
  var end = source.indexOf('?>',start);
599
599
  if(end){
600
- var match = source.substring(start,end).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/);
600
+ var match = source.substring(start,end).match(/^<\?(\S*)\s*([\s\S]*?)$/);
601
601
  if(match){
602
602
  var len = match[0].length;
603
603
  domBuilder.processingInstruction(match[1], match[2]) ;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmldom/xmldom",
3
- "version": "0.8.11",
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,14 +27,18 @@
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",
36
+ "format:check": "prettier --check test",
33
37
  "changelog": "auto-changelog --unreleased-only",
34
38
  "start": "nodemon --watch package.json --watch lib --watch test --exec 'npm --silent run test && npm --silent run lint'",
35
39
  "stryker": "stryker run",
36
40
  "stryker:dry-run": "stryker run -m '' --reporters progress",
37
- "test": "jest",
41
+ "test": "node --stack-size=$npm_package_config_test_stack_size ./node_modules/.bin/jest",
38
42
  "testrelease": "npm test && eslint lib",
39
43
  "version": "./changelog-has-version.sh",
40
44
  "release": "np --no-yarn --test-script testrelease --branch release-0.8.x patch"