@xmldom/xmldom 0.7.7 → 0.7.9

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
@@ -3,6 +3,25 @@
3
3
  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
+
7
+ ## [0.7.9](https://github.com/xmldom/xmldom/compare/0.7.8...0.7.9)
8
+
9
+ ### Fixed
10
+
11
+ - Properly check nodes before replacement [`#457`](https://github.com/xmldom/xmldom/pull/457) / [`#455`](https://github.com/xmldom/xmldom/issues/455) / [`#456`](https://github.com/xmldom/xmldom/issues/456)
12
+
13
+ Thank you, [@edemaine](https://github.com/edemaine), [@pedro-l9](https://github.com/pedro-l9), for your contributions
14
+
15
+
16
+ ## [0.7.8](https://github.com/xmldom/xmldom/compare/0.7.7...0.7.8)
17
+
18
+ ### Fixed
19
+
20
+ - fix: Restore ES5 compatibility [`#452`](https://github.com/xmldom/xmldom/pull/452) / [`#453`](https://github.com/xmldom/xmldom/issues/453)
21
+
22
+ Thank you, [@fengxinming](https://github.com/fengxinming), for your contributions
23
+
24
+
6
25
  ## [0.7.7](https://github.com/xmldom/xmldom/compare/0.7.6...0.7.7)
7
26
 
8
27
  ### Fixed
@@ -1,5 +1,37 @@
1
1
  'use strict'
2
2
 
3
+ /**
4
+ * Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes.
5
+ *
6
+ * Works with anything that has a `length` property and index access properties, including NodeList.
7
+ *
8
+ * @template {unknown} T
9
+ * @param {Array<T> | ({length:number, [number]: T})} list
10
+ * @param {function (item: T, index: number, list:Array<T> | ({length:number, [number]: T})):boolean} predicate
11
+ * @param {Partial<Pick<ArrayConstructor['prototype'], 'find'>>?} ac `Array.prototype` by default,
12
+ * allows injecting a custom implementation in tests
13
+ * @returns {T | undefined}
14
+ *
15
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
16
+ * @see https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.find
17
+ */
18
+ function find(list, predicate, ac) {
19
+ if (ac === undefined) {
20
+ ac = Array.prototype;
21
+ }
22
+ if (list && typeof ac.find === 'function') {
23
+ return ac.find.call(list, predicate);
24
+ }
25
+ for (var i = 0; i < list.length; i++) {
26
+ if (Object.prototype.hasOwnProperty.call(list, i)) {
27
+ var item = list[i];
28
+ if (predicate.call(undefined, item, i, list)) {
29
+ return item;
30
+ }
31
+ }
32
+ }
33
+ }
34
+
3
35
  /**
4
36
  * "Shallow freezes" an object to render it immutable.
5
37
  * Uses `Object.freeze` if available,
@@ -139,6 +171,7 @@ var NAMESPACE = freeze({
139
171
  XMLNS: 'http://www.w3.org/2000/xmlns/',
140
172
  })
141
173
 
174
+ exports.find = find;
142
175
  exports.freeze = freeze;
143
176
  exports.MIME_TYPE = MIME_TYPE;
144
177
  exports.NAMESPACE = NAMESPACE;
package/lib/dom.js CHANGED
@@ -1,5 +1,6 @@
1
1
  var conventions = require("./conventions");
2
2
 
3
+ var find = conventions.find;
3
4
  var NAMESPACE = conventions.NAMESPACE;
4
5
 
5
6
  /**
@@ -176,14 +177,6 @@ NodeList.prototype = {
176
177
  }
177
178
  return buf.join('');
178
179
  },
179
- /**
180
- * @private
181
- * @param {function (Node):boolean} predicate
182
- * @returns {Node | undefined}
183
- */
184
- find: function (predicate) {
185
- return Array.prototype.find.call(this, predicate);
186
- },
187
180
  /**
188
181
  * @private
189
182
  * @param {function (Node):boolean} predicate
@@ -475,7 +468,7 @@ Node.prototype = {
475
468
  return _insertBefore(this,newChild,refChild);
476
469
  },
477
470
  replaceChild:function(newChild, oldChild){//raises
478
- this.insertBefore(newChild,oldChild);
471
+ _insertBefore(this, newChild,oldChild, assertPreReplacementValidityInDocument);
479
472
  if(oldChild){
480
473
  this.removeChild(oldChild);
481
474
  }
@@ -599,6 +592,7 @@ function _visitNode(node,callback){
599
592
 
600
593
 
601
594
  function Document(){
595
+ this.ownerDocument = this;
602
596
  }
603
597
 
604
598
  function _onAddAttribute(doc,el,newAttr){
@@ -635,6 +629,7 @@ function _onUpdateChild(doc,el,newChild){
635
629
  child =child.nextSibling;
636
630
  }
637
631
  cs.length = i;
632
+ delete cs[cs.length];
638
633
  }
639
634
  }
640
635
  }
@@ -660,6 +655,9 @@ function _removeChild(parentNode,child){
660
655
  }else{
661
656
  parentNode.lastChild = previous;
662
657
  }
658
+ child.parentNode = null;
659
+ child.previousSibling = null;
660
+ child.nextSibling = null;
663
661
  _onUpdateChild(parentNode.ownerDocument,parentNode);
664
662
  return child;
665
663
  }
@@ -731,14 +729,41 @@ function isTextNode(node) {
731
729
  */
732
730
  function isElementInsertionPossible(doc, child) {
733
731
  var parentChildNodes = doc.childNodes || [];
734
- if (parentChildNodes.find(isElementNode) || isDocTypeNode(child)) {
732
+ if (find(parentChildNodes, isElementNode) || isDocTypeNode(child)) {
733
+ return false;
734
+ }
735
+ var docTypeNode = find(parentChildNodes, isDocTypeNode);
736
+ return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
737
+ }
738
+
739
+ /**
740
+ * Check if en element node can be inserted before `child`, or at the end if child is falsy,
741
+ * according to the presence and position of a doctype node on the same level.
742
+ *
743
+ * @param {Node} doc The document node
744
+ * @param {Node} child the node that would become the nextSibling if the element would be inserted
745
+ * @returns {boolean} `true` if an element can be inserted before child
746
+ * @private
747
+ * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
748
+ */
749
+ function isElementReplacementPossible(doc, child) {
750
+ var parentChildNodes = doc.childNodes || [];
751
+
752
+ function hasElementChildThatIsNotChild(node) {
753
+ return isElementNode(node) && node !== child;
754
+ }
755
+
756
+ if (find(parentChildNodes, hasElementChildThatIsNotChild)) {
735
757
  return false;
736
758
  }
737
- var docTypeNode = parentChildNodes.find(isDocTypeNode);
759
+ var docTypeNode = find(parentChildNodes, isDocTypeNode);
738
760
  return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
739
761
  }
762
+
740
763
  /**
741
764
  * @private
765
+ * Steps 1-5 of the checks before inserting and before replacing a child are the same.
766
+ *
742
767
  * @param {Node} parent the parent node to insert `node` into
743
768
  * @param {Node} node the node to insert
744
769
  * @param {Node=} child the node that should become the `nextSibling` of `node`
@@ -746,18 +771,26 @@ function isElementInsertionPossible(doc, child) {
746
771
  * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
747
772
  * @throws DOMException if `child` is provided but is not a child of `parent`.
748
773
  * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
774
+ * @see https://dom.spec.whatwg.org/#concept-node-replace
749
775
  */
750
- function _insertBefore(parent, node, child) {
776
+ function assertPreInsertionValidity1to5(parent, node, child) {
777
+ // 1. If `parent` is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
751
778
  if (!hasValidParentNodeType(parent)) {
752
779
  throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType);
753
780
  }
781
+ // 2. If `node` is a host-including inclusive ancestor of `parent`, then throw a "HierarchyRequestError" DOMException.
782
+ // not implemented!
783
+ // 3. If `child` is non-null and its parent is not `parent`, then throw a "NotFoundError" DOMException.
754
784
  if (child && child.parentNode !== parent) {
755
785
  throw new DOMException(NOT_FOUND_ERR, 'child not in parent');
756
786
  }
757
787
  if (
788
+ // 4. If `node` is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
758
789
  !hasInsertableNodeType(node) ||
790
+ // 5. If either `node` is a Text node and `parent` is a document,
759
791
  // the sax parser currently adds top level text nodes, this will be fixed in 0.9.0
760
792
  // || (node.nodeType === Node.TEXT_NODE && parent.nodeType === Node.DOCUMENT_NODE)
793
+ // or `node` is a doctype and `parent` is not a document, then throw a "HierarchyRequestError" DOMException.
761
794
  (isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE)
762
795
  ) {
763
796
  throw new DOMException(
@@ -765,35 +798,136 @@ function _insertBefore(parent, node, child) {
765
798
  'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType
766
799
  );
767
800
  }
801
+ }
802
+
803
+ /**
804
+ * @private
805
+ * Step 6 of the checks before inserting and before replacing a child are different.
806
+ *
807
+ * @param {Document} parent the parent node to insert `node` into
808
+ * @param {Node} node the node to insert
809
+ * @param {Node | undefined} child the node that should become the `nextSibling` of `node`
810
+ * @returns {Node}
811
+ * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
812
+ * @throws DOMException if `child` is provided but is not a child of `parent`.
813
+ * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
814
+ * @see https://dom.spec.whatwg.org/#concept-node-replace
815
+ */
816
+ function assertPreInsertionValidityInDocument(parent, node, child) {
768
817
  var parentChildNodes = parent.childNodes || [];
769
818
  var nodeChildNodes = node.childNodes || [];
770
- if (parent.nodeType === Node.DOCUMENT_NODE) {
771
- if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
772
- let nodeChildElements = nodeChildNodes.filter(isElementNode);
773
- if (nodeChildElements.length > 1 || nodeChildNodes.find(isTextNode)) {
774
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
775
- }
776
- if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) {
777
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
778
- }
819
+
820
+ // DocumentFragment
821
+ if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
822
+ var nodeChildElements = nodeChildNodes.filter(isElementNode);
823
+ // If node has more than one element child or has a Text node child.
824
+ if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
825
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
779
826
  }
780
- if (isElementNode(node)) {
781
- if (parentChildNodes.find(isElementNode) || !isElementInsertionPossible(parent, child)) {
782
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
783
- }
827
+ // Otherwise, if `node` has one element child and either `parent` has an element child,
828
+ // `child` is a doctype, or `child` is non-null and a doctype is following `child`.
829
+ if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) {
830
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
784
831
  }
785
- if (isDocTypeNode(node)) {
786
- if (parentChildNodes.find(isDocTypeNode)) {
787
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
788
- }
789
- let parentElementChild = parentChildNodes.find(isElementNode);
790
- if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
791
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
792
- }
793
- if (!child && parentElementChild) {
794
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present');
795
- }
832
+ }
833
+ // Element
834
+ if (isElementNode(node)) {
835
+ // `parent` has an element child, `child` is a doctype,
836
+ // or `child` is non-null and a doctype is following `child`.
837
+ if (!isElementInsertionPossible(parent, child)) {
838
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
839
+ }
840
+ }
841
+ // DocumentType
842
+ if (isDocTypeNode(node)) {
843
+ // `parent` has a doctype child,
844
+ if (find(parentChildNodes, isDocTypeNode)) {
845
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
846
+ }
847
+ var parentElementChild = find(parentChildNodes, isElementNode);
848
+ // `child` is non-null and an element is preceding `child`,
849
+ if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
850
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
796
851
  }
852
+ // or `child` is null and `parent` has an element child.
853
+ if (!child && parentElementChild) {
854
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present');
855
+ }
856
+ }
857
+ }
858
+
859
+ /**
860
+ * @private
861
+ * Step 6 of the checks before inserting and before replacing a child are different.
862
+ *
863
+ * @param {Document} parent the parent node to insert `node` into
864
+ * @param {Node} node the node to insert
865
+ * @param {Node | undefined} child the node that should become the `nextSibling` of `node`
866
+ * @returns {Node}
867
+ * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
868
+ * @throws DOMException if `child` is provided but is not a child of `parent`.
869
+ * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
870
+ * @see https://dom.spec.whatwg.org/#concept-node-replace
871
+ */
872
+ function assertPreReplacementValidityInDocument(parent, node, child) {
873
+ var parentChildNodes = parent.childNodes || [];
874
+ var nodeChildNodes = node.childNodes || [];
875
+
876
+ // DocumentFragment
877
+ if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
878
+ var nodeChildElements = nodeChildNodes.filter(isElementNode);
879
+ // If `node` has more than one element child or has a Text node child.
880
+ if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
881
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
882
+ }
883
+ // Otherwise, if `node` has one element child and either `parent` has an element child that is not `child` or a doctype is following `child`.
884
+ if (nodeChildElements.length === 1 && !isElementReplacementPossible(parent, child)) {
885
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
886
+ }
887
+ }
888
+ // Element
889
+ if (isElementNode(node)) {
890
+ // `parent` has an element child that is not `child` or a doctype is following `child`.
891
+ if (!isElementReplacementPossible(parent, child)) {
892
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
893
+ }
894
+ }
895
+ // DocumentType
896
+ if (isDocTypeNode(node)) {
897
+ function hasDoctypeChildThatIsNotChild(node) {
898
+ return isDocTypeNode(node) && node !== child;
899
+ }
900
+
901
+ // `parent` has a doctype child that is not `child`,
902
+ if (find(parentChildNodes, hasDoctypeChildThatIsNotChild)) {
903
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
904
+ }
905
+ var parentElementChild = find(parentChildNodes, isElementNode);
906
+ // or an element is preceding `child`.
907
+ if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
908
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
909
+ }
910
+ }
911
+ }
912
+
913
+ /**
914
+ * @private
915
+ * @param {Node} parent the parent node to insert `node` into
916
+ * @param {Node} node the node to insert
917
+ * @param {Node=} child the node that should become the `nextSibling` of `node`
918
+ * @returns {Node}
919
+ * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
920
+ * @throws DOMException if `child` is provided but is not a child of `parent`.
921
+ * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
922
+ */
923
+ function _insertBefore(parent, node, child, _inDocumentAssertion) {
924
+ // To ensure pre-insertion validity of a node into a parent before a child, run these steps:
925
+ assertPreInsertionValidity1to5(parent, node, child);
926
+
927
+ // If parent is a document, and any of the statements below, switched on the interface node implements,
928
+ // are true, then throw a "HierarchyRequestError" DOMException.
929
+ if (parent.nodeType === Node.DOCUMENT_NODE) {
930
+ (_inDocumentAssertion || assertPreInsertionValidityInDocument)(parent, node, child);
797
931
  }
798
932
 
799
933
  var cp = node.parentNode;
@@ -836,25 +970,20 @@ function _insertBefore(parent, node, child) {
836
970
  return node;
837
971
  }
838
972
  function _appendSingleChild(parentNode,newChild){
839
- var cp = newChild.parentNode;
840
- if(cp){
841
- var pre = parentNode.lastChild;
842
- cp.removeChild(newChild);//remove and update
843
- var pre = parentNode.lastChild;
973
+ if (newChild.parentNode) {
974
+ newChild.parentNode.removeChild(newChild);
844
975
  }
845
- var pre = parentNode.lastChild;
846
976
  newChild.parentNode = parentNode;
847
- newChild.previousSibling = pre;
977
+ newChild.previousSibling = parentNode.lastChild;
848
978
  newChild.nextSibling = null;
849
- if(pre){
850
- pre.nextSibling = newChild;
979
+ if (newChild.previousSibling) {
980
+ newChild.previousSibling.nextSibling = newChild;
851
981
  }else{
852
982
  parentNode.firstChild = newChild;
853
983
  }
854
984
  parentNode.lastChild = newChild;
855
985
  _onUpdateChild(parentNode.ownerDocument,parentNode,newChild);
856
986
  return newChild;
857
- //console.log("__aa",parentNode.lastChild.nextSibling == null)
858
987
  }
859
988
 
860
989
  Document.prototype = {
@@ -895,6 +1024,17 @@ Document.prototype = {
895
1024
  }
896
1025
  return _removeChild(this,oldChild);
897
1026
  },
1027
+ replaceChild: function (newChild, oldChild) {
1028
+ //raises
1029
+ _insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument);
1030
+ newChild.ownerDocument = this;
1031
+ if (oldChild) {
1032
+ this.removeChild(oldChild);
1033
+ }
1034
+ if (isElementNode(newChild)) {
1035
+ this.documentElement = newChild;
1036
+ }
1037
+ },
898
1038
  // Introduced in DOM Level 2:
899
1039
  importNode : function(importedNode,deep){
900
1040
  return importNode(this,importedNode,deep);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmldom/xmldom",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
4
4
  "description": "A pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module.",
5
5
  "keywords": [
6
6
  "w3c",
@@ -31,7 +31,9 @@
31
31
  "start": "nodemon --watch package.json --watch lib --watch test --exec 'npm --silent run test && npm --silent run lint'",
32
32
  "stryker": "stryker run",
33
33
  "stryker:dry-run": "stryker run -m '' --reporters progress",
34
- "test": "jest"
34
+ "test": "jest",
35
+ "testrelease": "npm test && eslint lib",
36
+ "release": "np --no-yarn --test-script testrelease --branch release-0.7.x --tag lts patch"
35
37
  },
36
38
  "engines": {
37
39
  "node": ">=10.0.0"
@@ -46,6 +48,7 @@
46
48
  "get-stream": "^6.0.1",
47
49
  "jest": "^27.0.6",
48
50
  "nodemon": "^2.0.12",
51
+ "np": "7.6.2",
49
52
  "prettier": "^2.3.2",
50
53
  "xmltest": "^1.5.0",
51
54
  "yauzl": "^2.10.0"