@xmldom/xmldom 0.8.4 → 0.8.6

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,6 +4,24 @@ 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.6](https://github.com/xmldom/xmldom/compare/0.8.5...0.8.6)
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.8.5](https://github.com/xmldom/xmldom/compare/0.8.4...0.8.5)
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
+
7
25
  ## [0.8.4](https://github.com/xmldom/xmldom/compare/0.8.3...0.8.4)
8
26
 
9
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,
@@ -165,6 +197,7 @@ var NAMESPACE = freeze({
165
197
  })
166
198
 
167
199
  exports.assign = assign;
200
+ exports.find = find;
168
201
  exports.freeze = freeze;
169
202
  exports.MIME_TYPE = MIME_TYPE;
170
203
  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
  }
@@ -597,6 +590,7 @@ function _visitNode(node,callback){
597
590
 
598
591
 
599
592
  function Document(){
593
+ this.ownerDocument = this;
600
594
  }
601
595
 
602
596
  function _onAddAttribute(doc,el,newAttr){
@@ -748,14 +742,41 @@ function isTextNode(node) {
748
742
  */
749
743
  function isElementInsertionPossible(doc, child) {
750
744
  var parentChildNodes = doc.childNodes || [];
751
- if (parentChildNodes.find(isElementNode) || isDocTypeNode(child)) {
745
+ if (find(parentChildNodes, isElementNode) || isDocTypeNode(child)) {
746
+ return false;
747
+ }
748
+ var docTypeNode = find(parentChildNodes, isDocTypeNode);
749
+ return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
750
+ }
751
+
752
+ /**
753
+ * Check if en element node can be inserted before `child`, or at the end if child is falsy,
754
+ * according to the presence and position of a doctype node on the same level.
755
+ *
756
+ * @param {Node} doc The document node
757
+ * @param {Node} child the node that would become the nextSibling if the element would be inserted
758
+ * @returns {boolean} `true` if an element can be inserted before child
759
+ * @private
760
+ * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
761
+ */
762
+ function isElementReplacementPossible(doc, child) {
763
+ var parentChildNodes = doc.childNodes || [];
764
+
765
+ function hasElementChildThatIsNotChild(node) {
766
+ return isElementNode(node) && node !== child;
767
+ }
768
+
769
+ if (find(parentChildNodes, hasElementChildThatIsNotChild)) {
752
770
  return false;
753
771
  }
754
- var docTypeNode = parentChildNodes.find(isDocTypeNode);
772
+ var docTypeNode = find(parentChildNodes, isDocTypeNode);
755
773
  return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
756
774
  }
775
+
757
776
  /**
758
777
  * @private
778
+ * Steps 1-5 of the checks before inserting and before replacing a child are the same.
779
+ *
759
780
  * @param {Node} parent the parent node to insert `node` into
760
781
  * @param {Node} node the node to insert
761
782
  * @param {Node=} child the node that should become the `nextSibling` of `node`
@@ -763,18 +784,26 @@ function isElementInsertionPossible(doc, child) {
763
784
  * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
764
785
  * @throws DOMException if `child` is provided but is not a child of `parent`.
765
786
  * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
787
+ * @see https://dom.spec.whatwg.org/#concept-node-replace
766
788
  */
767
- function _insertBefore(parent, node, child) {
789
+ function assertPreInsertionValidity1to5(parent, node, child) {
790
+ // 1. If `parent` is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
768
791
  if (!hasValidParentNodeType(parent)) {
769
792
  throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType);
770
793
  }
794
+ // 2. If `node` is a host-including inclusive ancestor of `parent`, then throw a "HierarchyRequestError" DOMException.
795
+ // not implemented!
796
+ // 3. If `child` is non-null and its parent is not `parent`, then throw a "NotFoundError" DOMException.
771
797
  if (child && child.parentNode !== parent) {
772
798
  throw new DOMException(NOT_FOUND_ERR, 'child not in parent');
773
799
  }
774
800
  if (
801
+ // 4. If `node` is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
775
802
  !hasInsertableNodeType(node) ||
803
+ // 5. If either `node` is a Text node and `parent` is a document,
776
804
  // the sax parser currently adds top level text nodes, this will be fixed in 0.9.0
777
805
  // || (node.nodeType === Node.TEXT_NODE && parent.nodeType === Node.DOCUMENT_NODE)
806
+ // or `node` is a doctype and `parent` is not a document, then throw a "HierarchyRequestError" DOMException.
778
807
  (isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE)
779
808
  ) {
780
809
  throw new DOMException(
@@ -782,36 +811,137 @@ function _insertBefore(parent, node, child) {
782
811
  'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType
783
812
  );
784
813
  }
814
+ }
815
+
816
+ /**
817
+ * @private
818
+ * Step 6 of the checks before inserting and before replacing a child are different.
819
+ *
820
+ * @param {Document} parent the parent node to insert `node` into
821
+ * @param {Node} node the node to insert
822
+ * @param {Node | undefined} child the node that should become the `nextSibling` of `node`
823
+ * @returns {Node}
824
+ * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
825
+ * @throws DOMException if `child` is provided but is not a child of `parent`.
826
+ * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
827
+ * @see https://dom.spec.whatwg.org/#concept-node-replace
828
+ */
829
+ function assertPreInsertionValidityInDocument(parent, node, child) {
785
830
  var parentChildNodes = parent.childNodes || [];
786
831
  var nodeChildNodes = node.childNodes || [];
787
- if (parent.nodeType === Node.DOCUMENT_NODE) {
788
- if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
789
- let nodeChildElements = nodeChildNodes.filter(isElementNode);
790
- if (nodeChildElements.length > 1 || nodeChildNodes.find(isTextNode)) {
791
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
792
- }
793
- if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) {
794
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
795
- }
832
+
833
+ // DocumentFragment
834
+ if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
835
+ var nodeChildElements = nodeChildNodes.filter(isElementNode);
836
+ // If node has more than one element child or has a Text node child.
837
+ if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
838
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
796
839
  }
797
- if (isElementNode(node)) {
798
- if (parentChildNodes.find(isElementNode) || !isElementInsertionPossible(parent, child)) {
799
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
800
- }
840
+ // Otherwise, if `node` has one element child and either `parent` has an element child,
841
+ // `child` is a doctype, or `child` is non-null and a doctype is following `child`.
842
+ if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) {
843
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
801
844
  }
802
- if (isDocTypeNode(node)) {
803
- if (parentChildNodes.find(isDocTypeNode)) {
804
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
805
- }
806
- let parentElementChild = parentChildNodes.find(isElementNode);
807
- if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
808
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
809
- }
810
- if (!child && parentElementChild) {
811
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present');
812
- }
845
+ }
846
+ // Element
847
+ if (isElementNode(node)) {
848
+ // `parent` has an element child, `child` is a doctype,
849
+ // or `child` is non-null and a doctype is following `child`.
850
+ if (!isElementInsertionPossible(parent, child)) {
851
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
852
+ }
853
+ }
854
+ // DocumentType
855
+ if (isDocTypeNode(node)) {
856
+ // `parent` has a doctype child,
857
+ if (find(parentChildNodes, isDocTypeNode)) {
858
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
859
+ }
860
+ var parentElementChild = find(parentChildNodes, isElementNode);
861
+ // `child` is non-null and an element is preceding `child`,
862
+ if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
863
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
864
+ }
865
+ // or `child` is null and `parent` has an element child.
866
+ if (!child && parentElementChild) {
867
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present');
868
+ }
869
+ }
870
+ }
871
+
872
+ /**
873
+ * @private
874
+ * Step 6 of the checks before inserting and before replacing a child are different.
875
+ *
876
+ * @param {Document} parent the parent node to insert `node` into
877
+ * @param {Node} node the node to insert
878
+ * @param {Node | undefined} child the node that should become the `nextSibling` of `node`
879
+ * @returns {Node}
880
+ * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
881
+ * @throws DOMException if `child` is provided but is not a child of `parent`.
882
+ * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
883
+ * @see https://dom.spec.whatwg.org/#concept-node-replace
884
+ */
885
+ function assertPreReplacementValidityInDocument(parent, node, child) {
886
+ var parentChildNodes = parent.childNodes || [];
887
+ var nodeChildNodes = node.childNodes || [];
888
+
889
+ // DocumentFragment
890
+ if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
891
+ var nodeChildElements = nodeChildNodes.filter(isElementNode);
892
+ // If `node` has more than one element child or has a Text node child.
893
+ if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
894
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
895
+ }
896
+ // Otherwise, if `node` has one element child and either `parent` has an element child that is not `child` or a doctype is following `child`.
897
+ if (nodeChildElements.length === 1 && !isElementReplacementPossible(parent, child)) {
898
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
899
+ }
900
+ }
901
+ // Element
902
+ if (isElementNode(node)) {
903
+ // `parent` has an element child that is not `child` or a doctype is following `child`.
904
+ if (!isElementReplacementPossible(parent, child)) {
905
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
813
906
  }
814
907
  }
908
+ // DocumentType
909
+ if (isDocTypeNode(node)) {
910
+ function hasDoctypeChildThatIsNotChild(node) {
911
+ return isDocTypeNode(node) && node !== child;
912
+ }
913
+
914
+ // `parent` has a doctype child that is not `child`,
915
+ if (find(parentChildNodes, hasDoctypeChildThatIsNotChild)) {
916
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
917
+ }
918
+ var parentElementChild = find(parentChildNodes, isElementNode);
919
+ // or an element is preceding `child`.
920
+ if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
921
+ throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
922
+ }
923
+ }
924
+ }
925
+
926
+ /**
927
+ * @private
928
+ * @param {Node} parent the parent node to insert `node` into
929
+ * @param {Node} node the node to insert
930
+ * @param {Node=} child the node that should become the `nextSibling` of `node`
931
+ * @returns {Node}
932
+ * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
933
+ * @throws DOMException if `child` is provided but is not a child of `parent`.
934
+ * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
935
+ */
936
+ function _insertBefore(parent, node, child, _inDocumentAssertion) {
937
+ // To ensure pre-insertion validity of a node into a parent before a child, run these steps:
938
+ assertPreInsertionValidity1to5(parent, node, child);
939
+
940
+ // If parent is a document, and any of the statements below, switched on the interface node implements,
941
+ // are true, then throw a "HierarchyRequestError" DOMException.
942
+ if (parent.nodeType === Node.DOCUMENT_NODE) {
943
+ (_inDocumentAssertion || assertPreInsertionValidityInDocument)(parent, node, child);
944
+ }
815
945
 
816
946
  var cp = node.parentNode;
817
947
  if(cp){
@@ -919,6 +1049,17 @@ Document.prototype = {
919
1049
  }
920
1050
  return _removeChild(this,oldChild);
921
1051
  },
1052
+ replaceChild: function (newChild, oldChild) {
1053
+ //raises
1054
+ _insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument);
1055
+ newChild.ownerDocument = this;
1056
+ if (oldChild) {
1057
+ this.removeChild(oldChild);
1058
+ }
1059
+ if (isElementNode(newChild)) {
1060
+ this.documentElement = newChild;
1061
+ }
1062
+ },
922
1063
  // Introduced in DOM Level 2:
923
1064
  importNode : function(importedNode,deep){
924
1065
  return importNode(this,importedNode,deep);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmldom/xmldom",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "description": "A pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module.",
5
5
  "keywords": [
6
6
  "w3c",
@@ -29,13 +29,15 @@
29
29
  ],
30
30
  "scripts": {
31
31
  "lint": "eslint lib test",
32
+ "format": "prettier --write test",
32
33
  "changelog": "auto-changelog --unreleased-only",
33
34
  "start": "nodemon --watch package.json --watch lib --watch test --exec 'npm --silent run test && npm --silent run lint'",
34
35
  "stryker": "stryker run",
35
36
  "stryker:dry-run": "stryker run -m '' --reporters progress",
36
37
  "test": "jest",
38
+ "testrelease": "npm test && eslint lib",
37
39
  "version": "./changelog-has-version.sh",
38
- "release": "np --no-yarn"
40
+ "release": "np --no-yarn --test-script testrelease --branch release-0.8.x patch"
39
41
  },
40
42
  "engines": {
41
43
  "node": ">=10.0.0"