diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py index 1663b1f1143ddc..99c94ff14ca36d 100644 --- a/Lib/test/test_minidom.py +++ b/Lib/test/test_minidom.py @@ -1,16 +1,20 @@ -# test for xml.dom.minidom +"""Tests for xml.dom.minidom.""" import copy -import pickle import io +import pickle from test import support import unittest +from xml.dom.minicompat import NodeList import xml.dom.minidom - -from xml.dom.minidom import parse, Node, Document, parseString +from xml.dom.minidom import Document +from xml.dom.minidom import DocumentFragment +from xml.dom.minidom import Element from xml.dom.minidom import getDOMImplementation - +from xml.dom.minidom import Node +from xml.dom.minidom import parse +from xml.dom.minidom import parseString tstfile = support.findfile("test.xml", subdir="xmltestdata") sample = ("\n" @@ -81,49 +85,6 @@ def testGetElementsByTagName(self): dom.documentElement.getElementsByTagName("LI")) dom.unlink() - def testInsertBefore(self): - dom = parseString("") - root = dom.documentElement - elem = root.childNodes[0] - nelem = dom.createElement("element") - root.insertBefore(nelem, elem) - self.confirm(len(root.childNodes) == 2 - and root.childNodes.length == 2 - and root.childNodes[0] is nelem - and root.childNodes.item(0) is nelem - and root.childNodes[1] is elem - and root.childNodes.item(1) is elem - and root.firstChild is nelem - and root.lastChild is elem - and root.toxml() == "" - , "testInsertBefore -- node properly placed in tree") - nelem = dom.createElement("element") - root.insertBefore(nelem, None) - self.confirm(len(root.childNodes) == 3 - and root.childNodes.length == 3 - and root.childNodes[1] is elem - and root.childNodes.item(1) is elem - and root.childNodes[2] is nelem - and root.childNodes.item(2) is nelem - and root.lastChild is nelem - and nelem.previousSibling is elem - and root.toxml() == "" - , "testInsertBefore -- node properly placed in tree") - nelem2 = dom.createElement("bar") - root.insertBefore(nelem2, nelem) - self.confirm(len(root.childNodes) == 4 - and root.childNodes.length == 4 - and root.childNodes[2] is nelem2 - and root.childNodes.item(2) is nelem2 - and root.childNodes[3] is nelem - and root.childNodes.item(3) is nelem - and nelem2.nextSibling is nelem - and nelem.previousSibling is nelem2 - and root.toxml() == - "" - , "testInsertBefore -- node properly placed in tree") - dom.unlink() - def _create_fragment_test_nodes(self): dom = parseString("") orig = dom.createTextNode("original") @@ -137,48 +98,6 @@ def _create_fragment_test_nodes(self): frag.appendChild(c3) return dom, orig, c1, c2, c3, frag - def testInsertBeforeFragment(self): - dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes() - dom.documentElement.insertBefore(frag, None) - self.confirm(tuple(dom.documentElement.childNodes) == - (orig, c1, c2, c3), - "insertBefore(, None)") - frag.unlink() - dom.unlink() - - dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes() - dom.documentElement.insertBefore(frag, orig) - self.confirm(tuple(dom.documentElement.childNodes) == - (c1, c2, c3, orig), - "insertBefore(, orig)") - frag.unlink() - dom.unlink() - - def testAppendChild(self): - dom = parse(tstfile) - dom.documentElement.appendChild(dom.createComment("Hello")) - self.confirm(dom.documentElement.childNodes[-1].nodeName == "#comment") - self.confirm(dom.documentElement.childNodes[-1].data == "Hello") - dom.unlink() - - def testAppendChildFragment(self): - dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes() - dom.documentElement.appendChild(frag) - self.confirm(tuple(dom.documentElement.childNodes) == - (orig, c1, c2, c3), - "appendChild()") - frag.unlink() - dom.unlink() - - def testReplaceChildFragment(self): - dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes() - dom.documentElement.replaceChild(frag, orig) - orig.unlink() - self.confirm(tuple(dom.documentElement.childNodes) == (c1, c2, c3), - "replaceChild()") - frag.unlink() - dom.unlink() - def testLegalChildren(self): dom = Document() elem = dom.createElement('element') @@ -380,15 +299,6 @@ def testChangeAttr(self): and el.getAttribute("spam2") == "bam2") dom.unlink() - def testGetAttrList(self): - pass - - def testGetAttrValues(self): - pass - - def testGetAttrLength(self): - pass - def testGetAttribute(self): dom = Document() child = dom.appendChild( @@ -409,8 +319,6 @@ def testGetAttributeNS(self): self.assertEqual(child2.getAttributeNS("http://www.python.org", "missing"), '') - def testGetAttributeNode(self): pass - def testGetElementsByTagNameNS(self): d=""" @@ -481,8 +389,6 @@ def testAttributeRepr(self): self.confirm(str(node) == repr(node)) dom.unlink() - def testTextNodeRepr(self): pass - def testWriteXML(self): str = '' dom = parseString(str) @@ -546,14 +452,6 @@ def testProcessingInstruction(self): and pi.localName is None and pi.namespaceURI == xml.dom.EMPTY_NAMESPACE) - def testProcessingInstructionRepr(self): pass - - def testTextRepr(self): pass - - def testWriteText(self): pass - - def testDocumentElement(self): pass - def testTooManyDocumentElements(self): doc = parseString("") elem = doc.createElement("extra") @@ -562,26 +460,6 @@ def testTooManyDocumentElements(self): elem.unlink() doc.unlink() - def testCreateElementNS(self): pass - - def testCreateAttributeNS(self): pass - - def testParse(self): pass - - def testParseString(self): pass - - def testComment(self): pass - - def testAttrListItem(self): pass - - def testAttrListItems(self): pass - - def testAttrListItemNS(self): pass - - def testAttrListKeys(self): pass - - def testAttrListKeysNS(self): pass - def testRemoveNamedItem(self): doc = parseString("") e = doc.documentElement @@ -601,88 +479,6 @@ def testRemoveNamedItemNS(self): self.assertRaises(xml.dom.NotFoundErr, attrs.removeNamedItemNS, "http://xml.python.org/", "b") - def testAttrListValues(self): pass - - def testAttrListLength(self): pass - - def testAttrList__getitem__(self): pass - - def testAttrList__setitem__(self): pass - - def testSetAttrValueandNodeValue(self): pass - - def testParseElement(self): pass - - def testParseAttributes(self): pass - - def testParseElementNamespaces(self): pass - - def testParseAttributeNamespaces(self): pass - - def testParseProcessingInstructions(self): pass - - def testChildNodes(self): pass - - def testFirstChild(self): pass - - def testHasChildNodes(self): - dom = parseString("") - doc = dom.documentElement - self.assertTrue(doc.hasChildNodes()) - dom2 = parseString("") - doc2 = dom2.documentElement - self.assertFalse(doc2.hasChildNodes()) - - def _testCloneElementCopiesAttributes(self, e1, e2, test): - attrs1 = e1.attributes - attrs2 = e2.attributes - keys1 = list(attrs1.keys()) - keys2 = list(attrs2.keys()) - keys1.sort() - keys2.sort() - self.confirm(keys1 == keys2, "clone of element has same attribute keys") - for i in range(len(keys1)): - a1 = attrs1.item(i) - a2 = attrs2.item(i) - self.confirm(a1 is not a2 - and a1.value == a2.value - and a1.nodeValue == a2.nodeValue - and a1.namespaceURI == a2.namespaceURI - and a1.localName == a2.localName - , "clone of attribute node has proper attribute values") - self.confirm(a2.ownerElement is e2, - "clone of attribute node correctly owned") - - def _setupCloneElement(self, deep): - dom = parseString("") - root = dom.documentElement - clone = root.cloneNode(deep) - self._testCloneElementCopiesAttributes( - root, clone, "testCloneElement" + (deep and "Deep" or "Shallow")) - # mutilate the original so shared data is detected - root.tagName = root.nodeName = "MODIFIED" - root.setAttribute("attr", "NEW VALUE") - root.setAttribute("added", "VALUE") - return dom, clone - - def testCloneElementShallow(self): - dom, clone = self._setupCloneElement(0) - self.confirm(len(clone.childNodes) == 0 - and clone.childNodes.length == 0 - and clone.parentNode is None - and clone.toxml() == '' - , "testCloneElementShallow") - dom.unlink() - - def testCloneElementDeep(self): - dom, clone = self._setupCloneElement(1) - self.confirm(len(clone.childNodes) == 1 - and clone.childNodes.length == 1 - and clone.parentNode is None - and clone.toxml() == '' - , "testCloneElementDeep") - dom.unlink() - def testCloneDocumentShallow(self): doc = parseString("\n" "" @@ -888,208 +684,6 @@ def testCloneNodeEntity(self): self.check_clone_node_entity(False) self.check_clone_node_entity(True) - def testNormalize(self): - doc = parseString("") - root = doc.documentElement - root.appendChild(doc.createTextNode("first")) - root.appendChild(doc.createTextNode("second")) - self.confirm(len(root.childNodes) == 2 - and root.childNodes.length == 2, - "testNormalize -- preparation") - doc.normalize() - self.confirm(len(root.childNodes) == 1 - and root.childNodes.length == 1 - and root.firstChild is root.lastChild - and root.firstChild.data == "firstsecond" - , "testNormalize -- result") - doc.unlink() - - doc = parseString("") - root = doc.documentElement - root.appendChild(doc.createTextNode("")) - doc.normalize() - self.confirm(len(root.childNodes) == 0 - and root.childNodes.length == 0, - "testNormalize -- single empty node removed") - doc.unlink() - - def testNormalizeCombineAndNextSibling(self): - doc = parseString("") - root = doc.documentElement - root.appendChild(doc.createTextNode("first")) - root.appendChild(doc.createTextNode("second")) - root.appendChild(doc.createElement("i")) - self.confirm(len(root.childNodes) == 3 - and root.childNodes.length == 3, - "testNormalizeCombineAndNextSibling -- preparation") - doc.normalize() - self.confirm(len(root.childNodes) == 2 - and root.childNodes.length == 2 - and root.firstChild.data == "firstsecond" - and root.firstChild is not root.lastChild - and root.firstChild.nextSibling is root.lastChild - and root.firstChild.previousSibling is None - and root.lastChild.previousSibling is root.firstChild - and root.lastChild.nextSibling is None - , "testNormalizeCombinedAndNextSibling -- result") - doc.unlink() - - def testNormalizeDeleteWithPrevSibling(self): - doc = parseString("") - root = doc.documentElement - root.appendChild(doc.createTextNode("first")) - root.appendChild(doc.createTextNode("")) - self.confirm(len(root.childNodes) == 2 - and root.childNodes.length == 2, - "testNormalizeDeleteWithPrevSibling -- preparation") - doc.normalize() - self.confirm(len(root.childNodes) == 1 - and root.childNodes.length == 1 - and root.firstChild.data == "first" - and root.firstChild is root.lastChild - and root.firstChild.nextSibling is None - and root.firstChild.previousSibling is None - , "testNormalizeDeleteWithPrevSibling -- result") - doc.unlink() - - def testNormalizeDeleteWithNextSibling(self): - doc = parseString("") - root = doc.documentElement - root.appendChild(doc.createTextNode("")) - root.appendChild(doc.createTextNode("second")) - self.confirm(len(root.childNodes) == 2 - and root.childNodes.length == 2, - "testNormalizeDeleteWithNextSibling -- preparation") - doc.normalize() - self.confirm(len(root.childNodes) == 1 - and root.childNodes.length == 1 - and root.firstChild.data == "second" - and root.firstChild is root.lastChild - and root.firstChild.nextSibling is None - and root.firstChild.previousSibling is None - , "testNormalizeDeleteWithNextSibling -- result") - doc.unlink() - - def testNormalizeDeleteWithTwoNonTextSiblings(self): - doc = parseString("") - root = doc.documentElement - root.appendChild(doc.createElement("i")) - root.appendChild(doc.createTextNode("")) - root.appendChild(doc.createElement("i")) - self.confirm(len(root.childNodes) == 3 - and root.childNodes.length == 3, - "testNormalizeDeleteWithTwoSiblings -- preparation") - doc.normalize() - self.confirm(len(root.childNodes) == 2 - and root.childNodes.length == 2 - and root.firstChild is not root.lastChild - and root.firstChild.nextSibling is root.lastChild - and root.firstChild.previousSibling is None - and root.lastChild.previousSibling is root.firstChild - and root.lastChild.nextSibling is None - , "testNormalizeDeleteWithTwoSiblings -- result") - doc.unlink() - - def testNormalizeDeleteAndCombine(self): - doc = parseString("") - root = doc.documentElement - root.appendChild(doc.createTextNode("")) - root.appendChild(doc.createTextNode("second")) - root.appendChild(doc.createTextNode("")) - root.appendChild(doc.createTextNode("fourth")) - root.appendChild(doc.createTextNode("")) - self.confirm(len(root.childNodes) == 5 - and root.childNodes.length == 5, - "testNormalizeDeleteAndCombine -- preparation") - doc.normalize() - self.confirm(len(root.childNodes) == 1 - and root.childNodes.length == 1 - and root.firstChild is root.lastChild - and root.firstChild.data == "secondfourth" - and root.firstChild.previousSibling is None - and root.firstChild.nextSibling is None - , "testNormalizeDeleteAndCombine -- result") - doc.unlink() - - def testNormalizeRecursion(self): - doc = parseString("" - "" - "" - "t" - # - #x - "" - "" - "" - "t2" - #x2 - "" - "t3" - #x3 - "" - # - "") - root = doc.documentElement - root.childNodes[0].appendChild(doc.createTextNode("")) - root.childNodes[0].appendChild(doc.createTextNode("x")) - root.childNodes[1].childNodes[0].appendChild(doc.createTextNode("x2")) - root.childNodes[1].appendChild(doc.createTextNode("x3")) - root.appendChild(doc.createTextNode("")) - self.confirm(len(root.childNodes) == 3 - and root.childNodes.length == 3 - and len(root.childNodes[0].childNodes) == 4 - and root.childNodes[0].childNodes.length == 4 - and len(root.childNodes[1].childNodes) == 3 - and root.childNodes[1].childNodes.length == 3 - and len(root.childNodes[1].childNodes[0].childNodes) == 2 - and root.childNodes[1].childNodes[0].childNodes.length == 2 - , "testNormalize2 -- preparation") - doc.normalize() - self.confirm(len(root.childNodes) == 2 - and root.childNodes.length == 2 - and len(root.childNodes[0].childNodes) == 2 - and root.childNodes[0].childNodes.length == 2 - and len(root.childNodes[1].childNodes) == 2 - and root.childNodes[1].childNodes.length == 2 - and len(root.childNodes[1].childNodes[0].childNodes) == 1 - and root.childNodes[1].childNodes[0].childNodes.length == 1 - , "testNormalize2 -- childNodes lengths") - self.confirm(root.childNodes[0].childNodes[1].data == "tx" - and root.childNodes[1].childNodes[0].childNodes[0].data == "t2x2" - and root.childNodes[1].childNodes[1].data == "t3x3" - , "testNormalize2 -- joined text fields") - self.confirm(root.childNodes[0].childNodes[1].nextSibling is None - and root.childNodes[0].childNodes[1].previousSibling - is root.childNodes[0].childNodes[0] - and root.childNodes[0].childNodes[0].previousSibling is None - and root.childNodes[0].childNodes[0].nextSibling - is root.childNodes[0].childNodes[1] - and root.childNodes[1].childNodes[1].nextSibling is None - and root.childNodes[1].childNodes[1].previousSibling - is root.childNodes[1].childNodes[0] - and root.childNodes[1].childNodes[0].previousSibling is None - and root.childNodes[1].childNodes[0].nextSibling - is root.childNodes[1].childNodes[1] - , "testNormalize2 -- sibling pointers") - doc.unlink() - - - def testBug0777884(self): - doc = parseString("text") - text = doc.documentElement.childNodes[0] - self.assertEqual(text.nodeType, Node.TEXT_NODE) - # Should run quietly, doing nothing. - text.normalize() - doc.unlink() - - def testBug1433694(self): - doc = parseString("t") - node = doc.documentElement - node.childNodes[1].nodeValue = "" - node.normalize() - self.confirm(node.childNodes[-1].nextSibling is None, - "Final child's .nextSibling should be None") - def testSiblings(self): doc = parseString("text?") root = doc.documentElement @@ -1373,15 +967,6 @@ def testWholeText(self): self.checkWholeText(text, "cabd") self.checkWholeText(text2, "cabd") - def testPatch1094164(self): - doc = parseString("") - elem = doc.documentElement - e = elem.firstChild - self.confirm(e.parentNode is elem, "Before replaceChild()") - # Check that replacing a child with itself leaves the tree unchanged - elem.replaceChild(e, e) - self.confirm(e.parentNode is elem, "After replaceChild()") - def testReplaceWholeText(self): def setup(): doc = parseString("ad") @@ -1612,15 +1197,6 @@ def testExceptionOnSpacesInXMLNSValue(self): with self.assertRaisesRegex(ValueError, 'Unsupported syntax'): parseString('') - def testDocRemoveChild(self): - doc = parse(tstfile) - title_tag = doc.documentElement.getElementsByTagName("TITLE")[0] - self.assertRaises( xml.dom.NotFoundErr, doc.removeChild, title_tag) - num_children_before = len(doc.childNodes) - doc.removeChild(doc.childNodes[0]) - num_children_after = len(doc.childNodes) - self.assertTrue(num_children_after == num_children_before - 1) - def testProcessingInstructionNameError(self): # wrong variable in .nodeValue property will # lead to "NameError: name 'data' is not defined" @@ -1663,5 +1239,441 @@ def test_cdata_parsing(self): dom2 = parseString(dom1.toprettyxml()) self.checkWholeText(dom2.getElementsByTagName('node')[0].firstChild, '') + def test_toxml(self): + # simple test + xml_str = '
text
' + root = parseString(xml_str) + self.assertEqual(root.toxml(), '
text
') + self.assertEqual( + root.toxml(standalone=True), + '
text
') + self.assertEqual( + root.toxml(standalone=False), + '
text
') + self.assertEqual( + root.toxml(encoding="utf-8"), + b'
text
') + + def test_hasChildNodes(self): + """Test if a node has children.""" + dom = parseString("") + doc = dom.documentElement + self.assertTrue(doc.hasChildNodes()) + dom2 = parseString("") + doc2 = dom2.documentElement + self.assertFalse(doc2.hasChildNodes()) + + def test_childNodes(self): + """Test the list of children Nodes of a DOM Element.""" + dom = parseString('

') + doc = dom.documentElement + self.assertEqual(len(doc.childNodes), 2) + self.assertIsInstance(doc.childNodes[0], Element) + self.assertIsInstance(doc.childNodes, NodeList) + + def test_firstChild(self): + """Test access to the first child of a DOM Element.""" + dom = parseString('

') + doc = dom.documentElement + self.assertEqual(doc.firstChild.toxml(), '') + self.assertIsInstance(doc.firstChild, Element) + dom = parseString('
') + doc = dom.documentElement + self.assertIsNone(doc.firstChild) + + def test_lastChild(self): + """Test access to the last child of a DOM Element.""" + dom = parseString('

') + doc = dom.documentElement + self.assertEqual(doc.lastChild.toxml(), '
') + self.assertIsInstance(doc.lastChild, Element) + dom = parseString('
') + doc = dom.documentElement + self.assertIsNone(doc.lastChild) + + def test_insertBefore_with_document_fragment_node(self): + """Test insertBefore for DOCUMENT_FRAGMENT_NODE.""" + # Preparing the test + dom = parseString('') + parentNode = dom.documentElement + existingNode = parentNode.firstChild + newNode = dom.createElement('new') + fragment = DocumentFragment() + fragment.appendChild(newNode) + # insertBefore with document fragment + parentNode.insertBefore(fragment, existingNode) + self.assertEqual(parentNode.toxml(), + '') + self.assertIs(parentNode.firstChild, newNode) + self.assertEqual(parentNode.childNodes.length, 2) + # after the insert the fragment is empty + self.assertEqual(fragment.childNodes.length, 0) + + def test_insertBefore_with_invalid_node_type(self): + """Test insertBefore with invalid node type.""" + # Preparing the test + dom = parseString('') + parentNode = dom.documentElement + existingNode = parentNode.firstChild + doc = getDOMImplementation().createDocument(None, "doc", None) + # parentNode.insertBefore(doc, existingNode) will raise + self.assertRaises(xml.dom.HierarchyRequestErr, + parentNode.insertBefore, doc, existingNode) + + def test_insertBefore_with_text_node(self): + """Test insertBefore for text node.""" + # Preparing the test + dom = parseString('') + parentNode = dom.documentElement + existingNode = parentNode.firstChild + newNode = dom.createTextNode('new text') + # insertBefore with text node + parentNode.insertBefore(newNode, existingNode) + self.assertEqual(parentNode.toxml(), + 'new text') + + def test_insertBefore_with_no_reference_node(self): + """Test insertBefore with no reference node.""" + # Preparing the test + dom = parseString('') + parentNode = dom.documentElement + newNode = dom.createTextNode('text trailing') + # insertBefore with no reference node aka None + parentNode.insertBefore(newNode, None) + self.assertEqual(parentNode.toxml(), + 'text trailing') + + def test_insertBefore_with_no_children(self): + """Test insertBefore with no children node.""" + # Preparing the test + dom = parseString('') + parentNode = dom.documentElement + newNode = dom.createElement('new') + existingNode = dom.createElement('existing') + # insertBefore with existing node not existing + self.assertRaises(xml.dom.NotFoundErr, + parentNode.insertBefore, newNode, existingNode) + + def test_insertBefore_return_value(self): + """Test insertBefore returned value.""" + dom = parseString('') + parentNode = dom.documentElement + newNode = dom.createElement('new') + existingNode = parentNode.firstChild + return_value = parentNode.insertBefore(newNode, existingNode) + self.assertEqual(return_value, newNode) + + def test_appendChild(self): + """Test appendChild for a simple node.""" + dom = parseString('') + parentNode = dom.documentElement + newNode = dom.createElement('new') + # append a new node child + parentNode.appendChild(newNode) + self.assertEqual(parentNode.toxml(), + '') + + def test_appendChild_with_document_fragment_node(self): + """Test appendChild for DOCUMENT_FRAGMENT_NODE.""" + # Preparing the test + dom = parseString('') + parentNode = dom.documentElement + newNode = dom.createElement('new') + fragment = DocumentFragment() + fragment.appendChild(newNode) + # appendChild with document fragment + parentNode.appendChild(fragment) + self.assertEqual(parentNode.toxml(), + '') + # after the appendChild, the fragment should be empty + self.assertEqual(fragment.childNodes.length, 0) + + def test_appendChild_with_invalid_node_type(self): + """Test appendChild with invalid node type.""" + # Preparing the test + dom = parseString('') + parentNode = dom.documentElement + doc = getDOMImplementation().createDocument(None, "doc", None) + # parentNode.appendChild(doc) will raise + self.assertRaises(xml.dom.HierarchyRequestErr, + parentNode.appendChild, doc) + + def test_appendChild_return_value(self): + """Test appendChild returned value.""" + dom = parseString('') + parentNode = dom.documentElement + newNode = dom.createElement('new') + # append a new node child + return_value = parentNode.appendChild(newNode) + self.assertEqual(return_value, newNode) + + def test_replaceChild(self): + """Test replaceChild for a simple node.""" + dom = parseString('') + parentNode = dom.documentElement + existingNode = parentNode.firstChild + newNode = dom.createElement('new') + # replace existingNode by newNode + parentNode.replaceChild(newNode, existingNode) + self.assertEqual(parentNode.toxml(), '') + + def test_replaceChild_with_document_fragment_node(self): + """Test replaceChild for DOCUMENT_FRAGMENT_NODE.""" + # Preparing the test + dom = parseString('') + parentNode = dom.documentElement + existingNode = parentNode.firstChild + newNode = dom.createElement('new') + fragment = DocumentFragment() + fragment.appendChild(newNode) + # replaceChild with document fragment + parentNode.replaceChild(fragment, existingNode) + self.assertEqual(parentNode.toxml(), '') + # after the replaceChild, the fragment should be empty + self.assertEqual(fragment.childNodes.length, 0) + + def test_replaceChild_with_invalid_node_type(self): + """Test replaceChild with invalid node type.""" + # Preparing the test + dom = parseString('') + parentNode = dom.documentElement + existingNode = parentNode.firstChild + doc = getDOMImplementation().createDocument(None, "doc", None) + # parentNode.replaceChild(doc, existingNode) will raise + self.assertRaises(xml.dom.HierarchyRequestErr, + parentNode.replaceChild, doc, existingNode) + + def test_replaceChild_with_same_node(self): + """Test replaceChild with same node.""" + dom = parseString('') + parentNode = dom.documentElement + existingNode = parentNode.firstChild + # replace existingNode by existingNode + parentNode.replaceChild(existingNode, existingNode) + self.assertEqual(parentNode.toxml(), '') + + def test_replaceChild_with_new_child_parent_not_None(self): + """Test replaceChild with new child parent is not Nonce.""" + dom = parseString('') + parentNode = dom.documentElement + existingNode = parentNode.firstChild + newdom = parseString('') + newparentNode = newdom.documentElement + newNode = newparentNode.firstChild + # replace existingNode by newNode + parentNode.replaceChild(newNode, existingNode) + self.assertEqual(parentNode.toxml(), '') + + def test_replaceChild_with_no_existing_node(self): + """Test replaceChild with missing existing node.""" + dom = parseString('') + parentNode = dom.documentElement + existingNode = dom.createElement('existing') + newdom = parseString('') + newparentNode = newdom.documentElement + newNode = newparentNode.firstChild + # replace existingNode by newNode + self.assertRaises(xml.dom.NotFoundErr, + parentNode.replaceChild, newNode, existingNode) + + def test_replaceChild_return_value(self): + """Test replaceChild returned value.""" + dom = parseString('') + parentNode = dom.documentElement + existingNode = parentNode.firstChild + newNode = dom.createElement('new') + # replace a new node child + return_value = parentNode.replaceChild(newNode, existingNode) + self.assertEqual(return_value, existingNode) + + def test_removeChild(self): + """Test removeChild for a simple node.""" + dom = parseString('') + parentNode = dom.documentElement + existingNode = parentNode.firstChild + # remove an existing node child + parentNode.removeChild(existingNode) + self.assertEqual(parentNode.toxml(), '') + + def test_removeChild_with_no_existing_node(self): + """Test removeChild with missing existing node.""" + dom = parseString('') + parentNode = dom.documentElement + existingNode = dom.createElement('existing') + # remove non existing Node + self.assertRaises(xml.dom.NotFoundErr, + parentNode.removeChild, existingNode) + + def test_normalize_text_nodes(self): + """Test normalize two text nodes.""" + doc = parseString("") + root = doc.documentElement + root.appendChild(doc.createTextNode("first")) + root.appendChild(doc.createTextNode("second")) + # Two text nodes have been created + # len(root.childNodes) == 2 + # root.firstChild.data == 'first' + doc.normalize() + self.assertEqual(len(root.childNodes), 1) + self.assertEqual(root.firstChild.data, 'firstsecond') + self.assertEqual(root.toxml(), 'firstsecond') + + + def test_normalize_empty_text_node(self): + """Test normalize removing the childNode of an empty text node.""" + doc = parseString("") + root = doc.documentElement + root.appendChild(doc.createTextNode("")) + # len(root.childNodes) == 1 + # root.firstChild.data == '' + doc.normalize() + self.assertEqual(len(root.childNodes), 0) + self.assertIs(root.firstChild, None) + self.assertEqual(root.toxml(), '') + + + def test_normalize_text_node_and_element_node(self): + """Test normalize merges two text nodes but not element node.""" + doc = parseString("") + root = doc.documentElement + root.appendChild(doc.createTextNode("first")) + root.appendChild(doc.createTextNode("second")) + root.appendChild(doc.createElement("element")) + # len(root.childNodes) == 3 + # root.firstChild.data == 'first' + doc.normalize() + self.assertEqual(len(root.childNodes), 2) + self.assertEqual(root.firstChild.data, 'firstsecond') + self.assertEqual(root.toxml(), 'firstsecond') + + def test_normalize_two_text_nodes_with_first_empty(self): + """Test that normalize removes the first empty text node only.""" + doc = parseString("") + root = doc.documentElement + root.appendChild(doc.createTextNode("")) + root.appendChild(doc.createTextNode("second")) + # Two text nodes have been created + # len(root.childNodes) == 2 + # root.firstChild.data == '' + # root.lastChild.data == 'second' + doc.normalize() + self.assertEqual(len(root.childNodes), 1) + self.assertEqual(root.firstChild.data, 'second') + self.assertEqual(root.toxml(), 'second') + + def test_normalize_two_text_nodes_with_last_empty(self): + """Test that normalize removes the last empty text node only.""" + doc = parseString("") + root = doc.documentElement + root.appendChild(doc.createTextNode("first")) + root.appendChild(doc.createTextNode("")) + # Two text nodes have been created + # len(root.childNodes) == 2 + # root.firstChild.data == 'first' + # root.lastChild.data == '' + doc.normalize() + self.assertEqual(len(root.childNodes), 1) + self.assertEqual(root.firstChild.data, 'first') + self.assertEqual(root.toxml(), 'first') + + def test_normalize_two_element_nodes_with_empty_text_node(self): + """Test that normalize removes the empty text node in between.""" + doc = parseString("") + root = doc.documentElement + root.appendChild(doc.createElement('first')) + root.appendChild(doc.createTextNode('')) + root.appendChild(doc.createElement('second')) + # Two text nodes have been created + # len(root.childNodes) == 3 + # root.toxml() == '' + doc.normalize() + self.assertEqual(len(root.childNodes), 2) + self.assertEqual(root.toxml(), '') + + def text_normalize_nested_elements_with_text_nodes(self): + """Test that normalize is still working in a more complex tree.""" + xml = 'texttext' + doc = parseString(xml) + root = doc.documentElement + root.childNodes[0].appendChild(doc.createTextNode('added')) + root.childNodes[1].appendChild(doc.createTextNode('')) + doc.normalize() + self.assertEqual(len(root.childNodes[0]), 1) + self.assertEqual(len(root.childNodes[1]), 1) + self.assertEqual( + root.toxml(), + 'textaddedtext') + + def test_normalize_on_text_node(self): + """Test that normalize on a text Node doesn't fail. + + See https://bugs.python.org/issue777884 + """ + doc = parseString("text") + text = doc.documentElement.childNodes[0] + self.assertEqual(text.nodeType, Node.TEXT_NODE) + # Should run quietly, doing nothing. + text.normalize() + + def test_normalize_unlink_child_node(self): + """Test that normalize unlinks empty child nodes. + + See https://bugs.python.org/issue1433694 + """ + doc = parseString("text") + root = doc.documentElement + root.childNodes[1].nodeValue = '' + doc.normalize() + # Final child's .nextSibling should be None + self.assertIsNone(root.childNodes[-1].nextSibling) + + def test_cloneNode_with_simple_element(self): + """Test cloneNode for a simple Element Node.""" + doc = parseString("") + root = doc.documentElement + new_clone = root.cloneNode(deep=True) + self.assertEqual(new_clone.toxml(), root.toxml()) + self.assertEqual(new_clone.nodeType, root.nodeType) + new_clone = root.cloneNode(deep=False) + self.assertEqual(new_clone.toxml(), '') + + def test_cloneNode_with_text_node(self): + """Test cloneNode with text node.""" + doc = parseString("text") + root = doc.documentElement + new_clone = root.cloneNode(deep=True) + self.assertEqual(new_clone.toxml(), root.toxml()) + self.assertEqual(new_clone.nodeType, root.nodeType) + new_clone = root.cloneNode(deep=False) + self.assertEqual(new_clone.toxml(), '') + + def test_cloneNode_with_text_and_element_nodes(self): + """Test cloneNode with text and element nodes.""" + doc = parseString("text") + root = doc.documentElement + new_clone = root.cloneNode(deep=True) + self.assertEqual(new_clone.toxml(), root.toxml()) + self.assertEqual(new_clone.nodeType, root.nodeType) + new_clone = root.cloneNode(deep=False) + self.assertEqual(new_clone.toxml(), '') + + def test_cloneNode_with_element_node_with_attribute(self): + """Test cloneNode with element nodes with attribute.""" + doc = parseString("") + root = doc.documentElement + new_clone = root.cloneNode(deep=True) + self.assertEqual(new_clone.toxml(), root.toxml()) + self.assertEqual(new_clone.nodeType, root.nodeType) + new_clone = root.cloneNode(deep=False) + self.assertEqual(new_clone.toxml(), '') + + def test_cloneNode_parentNode_None(self): + """Test cloneNode that the parent node is None.""" + doc = parseString("") + root = doc.documentElement + new_clone = root.firstChild.cloneNode(deep=True) + self.assertEqual(new_clone.toxml(), '') + self.assertIsNone(new_clone.parentNode) + if __name__ == "__main__": unittest.main() diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index d09ef5e7d0371a..916c0503b3d39b 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -32,6 +32,7 @@ class Node(xml.dom.Node): + """Define properties accessible on a DOM node.""" namespaceURI = None # this is non-null only for elements and attributes parentNode = None ownerDocument = None @@ -44,6 +45,7 @@ def __bool__(self): return True def toxml(self, encoding=None, standalone=None): + """Generate a string representation of a DOM.""" return self.toprettyxml("", "", encoding, standalone) def toprettyxml(self, indent="\t", newl="\n", encoding=None, @@ -66,6 +68,7 @@ def toprettyxml(self, indent="\t", newl="\n", encoding=None, return writer.detach().getvalue() def hasChildNodes(self): + """Return True if the node has children, False else.""" return bool(self.childNodes) def _get_childNodes(self): @@ -80,10 +83,17 @@ def _get_lastChild(self): return self.childNodes[-1] def insertBefore(self, newChild, refChild): + """Insert a new DOM Node before an existing Node. + + https://dom.spec.whatwg.org/#dom-node-insertbefore + The insertBefore(node, child) method, when invoked, + must return the result of pre-inserting node into + this before child. + See also https://dom.spec.whatwg.org/#concept-node-pre-insert + """ if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE: for c in tuple(newChild.childNodes): self.insertBefore(c, refChild) - ### The DOM does not clearly specify what to return in this case return newChild if newChild.nodeType not in self._child_node_types: raise xml.dom.HierarchyRequestErr( @@ -112,10 +122,10 @@ def insertBefore(self, newChild, refChild): return newChild def appendChild(self, node): + """Append a child node to an existing node.""" if node.nodeType == self.DOCUMENT_FRAGMENT_NODE: for c in tuple(node.childNodes): self.appendChild(c) - ### The DOM does not clearly specify what to return in this case return node if node.nodeType not in self._child_node_types: raise xml.dom.HierarchyRequestErr( @@ -129,6 +139,7 @@ def appendChild(self, node): return node def replaceChild(self, newChild, oldChild): + """Replace an existing node with a new node.""" if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE: refChild = oldChild.nextSibling self.removeChild(oldChild) @@ -161,6 +172,7 @@ def replaceChild(self, newChild, oldChild): return oldChild def removeChild(self, oldChild): + """Remove an existing child.""" try: self.childNodes.remove(oldChild) except ValueError: @@ -172,11 +184,16 @@ def removeChild(self, oldChild): oldChild.nextSibling = oldChild.previousSibling = None if oldChild.nodeType in _nodeTypes_with_children: _clear_id_cache(self) - oldChild.parentNode = None return oldChild def normalize(self): + """Transform a node into its normalized form. + + Removes empty exclusive Text nodes and concatenates the data of + remaining contiguous exclusive Text nodes into the first of + their nodes. + """ L = [] for child in self.childNodes: if child.nodeType == Node.TEXT_NODE: @@ -204,6 +221,7 @@ def normalize(self): self.childNodes[:] = L def cloneNode(self, deep): + """The Node.cloneNode() method returns a duplicate of the node.""" return _clone_node(self, deep, self.ownerDocument or self) def isSupported(self, feature, version): @@ -864,7 +882,7 @@ def getElementsByTagNameNS(self, namespaceURI, localName): self, namespaceURI, localName, NodeList()) def __repr__(self): - return "" % (self.tagName, id(self)) + return f"" def writexml(self, writer, indent="", addindent="", newl=""): """Write an XML element to a file-like object @@ -1323,6 +1341,7 @@ def _get_internalSubset(self): return self.internalSubset def cloneNode(self, deep): + """The Node.cloneNode() method returns a duplicate of the node.""" if self.ownerDocument is None: # it's ok clone = DocumentType(None) @@ -1886,7 +1905,9 @@ def renameNode(self, n, namespaceURI, name): def _clone_node(node, deep, newOwnerDocument): """ - Clone a node and give it the new owner document. + Returns a copy of node. + + If deep is true, the copy also includes the node’s descendants. Called by Node.cloneNode and Document.importNode """ if node.ownerDocument.isSameNode(newOwnerDocument):