@xmldom/xmldom 0.7.8 → 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.
Files changed (3) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/lib/dom.js +183 -36
  3. package/package.json +2 -2
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.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
+
7
16
  ## [0.7.8](https://github.com/xmldom/xmldom/compare/0.7.7...0.7.8)
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
  }
@@ -592,6 +592,7 @@ function _visitNode(node,callback){
592
592
 
593
593
 
594
594
  function Document(){
595
+ this.ownerDocument = this;
595
596
  }
596
597
 
597
598
  function _onAddAttribute(doc,el,newAttr){
@@ -628,6 +629,7 @@ function _onUpdateChild(doc,el,newChild){
628
629
  child =child.nextSibling;
629
630
  }
630
631
  cs.length = i;
632
+ delete cs[cs.length];
631
633
  }
632
634
  }
633
635
  }
@@ -653,6 +655,9 @@ function _removeChild(parentNode,child){
653
655
  }else{
654
656
  parentNode.lastChild = previous;
655
657
  }
658
+ child.parentNode = null;
659
+ child.previousSibling = null;
660
+ child.nextSibling = null;
656
661
  _onUpdateChild(parentNode.ownerDocument,parentNode);
657
662
  return child;
658
663
  }
@@ -730,8 +735,35 @@ function isElementInsertionPossible(doc, child) {
730
735
  var docTypeNode = find(parentChildNodes, isDocTypeNode);
731
736
  return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
732
737
  }
738
+
733
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
734
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)) {
757
+ return false;
758
+ }
759
+ var docTypeNode = find(parentChildNodes, isDocTypeNode);
760
+ return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
761
+ }
762
+
763
+ /**
764
+ * @private
765
+ * Steps 1-5 of the checks before inserting and before replacing a child are the same.
766
+ *
735
767
  * @param {Node} parent the parent node to insert `node` into
736
768
  * @param {Node} node the node to insert
737
769
  * @param {Node=} child the node that should become the `nextSibling` of `node`
@@ -739,18 +771,26 @@ function isElementInsertionPossible(doc, child) {
739
771
  * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
740
772
  * @throws DOMException if `child` is provided but is not a child of `parent`.
741
773
  * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
774
+ * @see https://dom.spec.whatwg.org/#concept-node-replace
742
775
  */
743
- 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.
744
778
  if (!hasValidParentNodeType(parent)) {
745
779
  throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType);
746
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.
747
784
  if (child && child.parentNode !== parent) {
748
785
  throw new DOMException(NOT_FOUND_ERR, 'child not in parent');
749
786
  }
750
787
  if (
788
+ // 4. If `node` is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
751
789
  !hasInsertableNodeType(node) ||
790
+ // 5. If either `node` is a Text node and `parent` is a document,
752
791
  // the sax parser currently adds top level text nodes, this will be fixed in 0.9.0
753
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.
754
794
  (isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE)
755
795
  ) {
756
796
  throw new DOMException(
@@ -758,36 +798,137 @@ function _insertBefore(parent, node, child) {
758
798
  'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType
759
799
  );
760
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) {
761
817
  var parentChildNodes = parent.childNodes || [];
762
818
  var nodeChildNodes = node.childNodes || [];
763
- if (parent.nodeType === Node.DOCUMENT_NODE) {
764
- if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
765
- var nodeChildElements = nodeChildNodes.filter(isElementNode);
766
- if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
767
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
768
- }
769
- if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) {
770
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
771
- }
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');
772
826
  }
773
- if (isElementNode(node)) {
774
- if (find(parentChildNodes, isElementNode) || !isElementInsertionPossible(parent, child)) {
775
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
776
- }
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');
777
831
  }
778
- if (isDocTypeNode(node)) {
779
- if (find(parentChildNodes, isDocTypeNode)) {
780
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
781
- }
782
- var parentElementChild = find(parentChildNodes, isElementNode);
783
- if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
784
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
785
- }
786
- if (!child && parentElementChild) {
787
- throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present');
788
- }
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');
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');
789
909
  }
790
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);
931
+ }
791
932
 
792
933
  var cp = node.parentNode;
793
934
  if(cp){
@@ -829,25 +970,20 @@ function _insertBefore(parent, node, child) {
829
970
  return node;
830
971
  }
831
972
  function _appendSingleChild(parentNode,newChild){
832
- var cp = newChild.parentNode;
833
- if(cp){
834
- var pre = parentNode.lastChild;
835
- cp.removeChild(newChild);//remove and update
836
- var pre = parentNode.lastChild;
973
+ if (newChild.parentNode) {
974
+ newChild.parentNode.removeChild(newChild);
837
975
  }
838
- var pre = parentNode.lastChild;
839
976
  newChild.parentNode = parentNode;
840
- newChild.previousSibling = pre;
977
+ newChild.previousSibling = parentNode.lastChild;
841
978
  newChild.nextSibling = null;
842
- if(pre){
843
- pre.nextSibling = newChild;
979
+ if (newChild.previousSibling) {
980
+ newChild.previousSibling.nextSibling = newChild;
844
981
  }else{
845
982
  parentNode.firstChild = newChild;
846
983
  }
847
984
  parentNode.lastChild = newChild;
848
985
  _onUpdateChild(parentNode.ownerDocument,parentNode,newChild);
849
986
  return newChild;
850
- //console.log("__aa",parentNode.lastChild.nextSibling == null)
851
987
  }
852
988
 
853
989
  Document.prototype = {
@@ -888,6 +1024,17 @@ Document.prototype = {
888
1024
  }
889
1025
  return _removeChild(this,oldChild);
890
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
+ },
891
1038
  // Introduced in DOM Level 2:
892
1039
  importNode : function(importedNode,deep){
893
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.8",
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",
@@ -33,7 +33,7 @@
33
33
  "stryker:dry-run": "stryker run -m '' --reporters progress",
34
34
  "test": "jest",
35
35
  "testrelease": "npm test && eslint lib",
36
- "release": "np --no-yarn --test-script testrelease --branch release-0.7.x --tag lts"
36
+ "release": "np --no-yarn --test-script testrelease --branch release-0.7.x --tag lts patch"
37
37
  },
38
38
  "engines": {
39
39
  "node": ">=10.0.0"