@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 +35 -1
- package/index.d.ts +45 -1
- package/lib/dom.js +574 -310
- package/lib/sax.js +1 -1
- package/package.json +6 -2
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
|
-
|
|
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
|
|
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;
|
|
@@ -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
|
-
|
|
1470
|
-
|
|
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
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
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
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
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
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
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
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
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 "]]>"');
|
|
1639
1903
|
}
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
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
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
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
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
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
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
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
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
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
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
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]*?)
|
|
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.
|
|
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"
|