@xmldom/xmldom 0.8.5 → 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.
Files changed (3) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/lib/dom.js +174 -26
  3. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ 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
+
7
16
  ## [0.8.5](https://github.com/xmldom/xmldom/compare/0.8.4...0.8.5)
8
17
 
9
18
  ### Fixed
package/lib/dom.js CHANGED
@@ -468,7 +468,7 @@ Node.prototype = {
468
468
  return _insertBefore(this,newChild,refChild);
469
469
  },
470
470
  replaceChild:function(newChild, oldChild){//raises
471
- this.insertBefore(newChild,oldChild);
471
+ _insertBefore(this, newChild,oldChild, assertPreReplacementValidityInDocument);
472
472
  if(oldChild){
473
473
  this.removeChild(oldChild);
474
474
  }
@@ -590,6 +590,7 @@ function _visitNode(node,callback){
590
590
 
591
591
 
592
592
  function Document(){
593
+ this.ownerDocument = this;
593
594
  }
594
595
 
595
596
  function _onAddAttribute(doc,el,newAttr){
@@ -747,8 +748,35 @@ function isElementInsertionPossible(doc, child) {
747
748
  var docTypeNode = find(parentChildNodes, isDocTypeNode);
748
749
  return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
749
750
  }
751
+
750
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
751
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)) {
770
+ return false;
771
+ }
772
+ var docTypeNode = find(parentChildNodes, isDocTypeNode);
773
+ return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
774
+ }
775
+
776
+ /**
777
+ * @private
778
+ * Steps 1-5 of the checks before inserting and before replacing a child are the same.
779
+ *
752
780
  * @param {Node} parent the parent node to insert `node` into
753
781
  * @param {Node} node the node to insert
754
782
  * @param {Node=} child the node that should become the `nextSibling` of `node`
@@ -756,18 +784,26 @@ function isElementInsertionPossible(doc, child) {
756
784
  * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
757
785
  * @throws DOMException if `child` is provided but is not a child of `parent`.
758
786
  * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
787
+ * @see https://dom.spec.whatwg.org/#concept-node-replace
759
788
  */
760
- 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.
761
791
  if (!hasValidParentNodeType(parent)) {
762
792
  throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType);
763
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.
764
797
  if (child && child.parentNode !== parent) {
765
798
  throw new DOMException(NOT_FOUND_ERR, 'child not in parent');
766
799
  }
767
800
  if (
801
+ // 4. If `node` is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
768
802
  !hasInsertableNodeType(node) ||
803
+ // 5. If either `node` is a Text node and `parent` is a document,
769
804
  // the sax parser currently adds top level text nodes, this will be fixed in 0.9.0
770
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.
771
807
  (isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE)
772
808
  ) {
773
809
  throw new DOMException(
@@ -775,36 +811,137 @@ function _insertBefore(parent, node, child) {
775
811
  'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType
776
812
  );
777
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) {
778
830
  var parentChildNodes = parent.childNodes || [];
779
831
  var nodeChildNodes = node.childNodes || [];
780
- if (parent.nodeType === Node.DOCUMENT_NODE) {
781
- if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
782
- var nodeChildElements = nodeChildNodes.filter(isElementNode);
783
- if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
784
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
785
- }
786
- if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) {
787
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
788
- }
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');
789
839
  }
790
- if (isElementNode(node)) {
791
- if (find(parentChildNodes, isElementNode) || !isElementInsertionPossible(parent, child)) {
792
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
793
- }
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');
794
844
  }
795
- if (isDocTypeNode(node)) {
796
- if (find(parentChildNodes, isDocTypeNode)) {
797
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
798
- }
799
- var parentElementChild = find(parentChildNodes, isElementNode);
800
- if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
801
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
802
- }
803
- if (!child && parentElementChild) {
804
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present');
805
- }
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');
806
868
  }
807
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');
906
+ }
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
+ }
808
945
 
809
946
  var cp = node.parentNode;
810
947
  if(cp){
@@ -912,6 +1049,17 @@ Document.prototype = {
912
1049
  }
913
1050
  return _removeChild(this,oldChild);
914
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
+ },
915
1063
  // Introduced in DOM Level 2:
916
1064
  importNode : function(importedNode,deep){
917
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.5",
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",
@@ -35,9 +35,9 @@
35
35
  "stryker": "stryker run",
36
36
  "stryker:dry-run": "stryker run -m '' --reporters progress",
37
37
  "test": "jest",
38
- "testrelease": "npm test && npm run lint",
38
+ "testrelease": "npm test && eslint lib",
39
39
  "version": "./changelog-has-version.sh",
40
- "release": "np --no-yarn --test-script testrelease"
40
+ "release": "np --no-yarn --test-script testrelease --branch release-0.8.x patch"
41
41
  },
42
42
  "engines": {
43
43
  "node": ">=10.0.0"