1# test for xml.dom.minidom
2
3import copy
4import pickle
5from test import support
6import unittest
7
8import xml.dom.minidom
9
10from xml.dom.minidom import parse, Node, Document, parseString
11from xml.dom.minidom import getDOMImplementation
12
13
14tstfile = support.findfile("test.xml", subdir="xmltestdata")
15sample = ("<?xml version='1.0' encoding='us-ascii'?>\n"
16          "<!DOCTYPE doc PUBLIC 'http://xml.python.org/public'"
17          " 'http://xml.python.org/system' [\n"
18          "  <!ELEMENT e EMPTY>\n"
19          "  <!ENTITY ent SYSTEM 'http://xml.python.org/entity'>\n"
20          "]><doc attr='value'> text\n"
21          "<?pi sample?> <!-- comment --> <e/> </doc>")
22
23# The tests of DocumentType importing use these helpers to construct
24# the documents to work with, since not all DOM builders actually
25# create the DocumentType nodes.
26def create_doc_without_doctype(doctype=None):
27    return getDOMImplementation().createDocument(None, "doc", doctype)
28
29def create_nonempty_doctype():
30    doctype = getDOMImplementation().createDocumentType("doc", None, None)
31    doctype.entities._seq = []
32    doctype.notations._seq = []
33    notation = xml.dom.minidom.Notation("my-notation", None,
34                                        "http://xml.python.org/notations/my")
35    doctype.notations._seq.append(notation)
36    entity = xml.dom.minidom.Entity("my-entity", None,
37                                    "http://xml.python.org/entities/my",
38                                    "my-notation")
39    entity.version = "1.0"
40    entity.encoding = "utf-8"
41    entity.actualEncoding = "us-ascii"
42    doctype.entities._seq.append(entity)
43    return doctype
44
45def create_doc_with_doctype():
46    doctype = create_nonempty_doctype()
47    doc = create_doc_without_doctype(doctype)
48    doctype.entities.item(0).ownerDocument = doc
49    doctype.notations.item(0).ownerDocument = doc
50    return doc
51
52class MinidomTest(unittest.TestCase):
53    def confirm(self, test, testname = "Test"):
54        self.assertTrue(test, testname)
55
56    def checkWholeText(self, node, s):
57        t = node.wholeText
58        self.confirm(t == s, "looking for %r, found %r" % (s, t))
59
60    def testDocumentAsyncAttr(self):
61        doc = Document()
62        self.assertFalse(doc.async_)
63        self.assertFalse(Document.async_)
64
65    def testParseFromBinaryFile(self):
66        with open(tstfile, 'rb') as file:
67            dom = parse(file)
68            dom.unlink()
69            self.confirm(isinstance(dom, Document))
70
71    def testParseFromTextFile(self):
72        with open(tstfile, 'r', encoding='iso-8859-1') as file:
73            dom = parse(file)
74            dom.unlink()
75            self.confirm(isinstance(dom, Document))
76
77    def testGetElementsByTagName(self):
78        dom = parse(tstfile)
79        self.confirm(dom.getElementsByTagName("LI") == \
80                dom.documentElement.getElementsByTagName("LI"))
81        dom.unlink()
82
83    def testInsertBefore(self):
84        dom = parseString("<doc><foo/></doc>")
85        root = dom.documentElement
86        elem = root.childNodes[0]
87        nelem = dom.createElement("element")
88        root.insertBefore(nelem, elem)
89        self.confirm(len(root.childNodes) == 2
90                and root.childNodes.length == 2
91                and root.childNodes[0] is nelem
92                and root.childNodes.item(0) is nelem
93                and root.childNodes[1] is elem
94                and root.childNodes.item(1) is elem
95                and root.firstChild is nelem
96                and root.lastChild is elem
97                and root.toxml() == "<doc><element/><foo/></doc>"
98                , "testInsertBefore -- node properly placed in tree")
99        nelem = dom.createElement("element")
100        root.insertBefore(nelem, None)
101        self.confirm(len(root.childNodes) == 3
102                and root.childNodes.length == 3
103                and root.childNodes[1] is elem
104                and root.childNodes.item(1) is elem
105                and root.childNodes[2] is nelem
106                and root.childNodes.item(2) is nelem
107                and root.lastChild is nelem
108                and nelem.previousSibling is elem
109                and root.toxml() == "<doc><element/><foo/><element/></doc>"
110                , "testInsertBefore -- node properly placed in tree")
111        nelem2 = dom.createElement("bar")
112        root.insertBefore(nelem2, nelem)
113        self.confirm(len(root.childNodes) == 4
114                and root.childNodes.length == 4
115                and root.childNodes[2] is nelem2
116                and root.childNodes.item(2) is nelem2
117                and root.childNodes[3] is nelem
118                and root.childNodes.item(3) is nelem
119                and nelem2.nextSibling is nelem
120                and nelem.previousSibling is nelem2
121                and root.toxml() ==
122                "<doc><element/><foo/><bar/><element/></doc>"
123                , "testInsertBefore -- node properly placed in tree")
124        dom.unlink()
125
126    def _create_fragment_test_nodes(self):
127        dom = parseString("<doc/>")
128        orig = dom.createTextNode("original")
129        c1 = dom.createTextNode("foo")
130        c2 = dom.createTextNode("bar")
131        c3 = dom.createTextNode("bat")
132        dom.documentElement.appendChild(orig)
133        frag = dom.createDocumentFragment()
134        frag.appendChild(c1)
135        frag.appendChild(c2)
136        frag.appendChild(c3)
137        return dom, orig, c1, c2, c3, frag
138
139    def testInsertBeforeFragment(self):
140        dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes()
141        dom.documentElement.insertBefore(frag, None)
142        self.confirm(tuple(dom.documentElement.childNodes) ==
143                     (orig, c1, c2, c3),
144                     "insertBefore(<fragment>, None)")
145        frag.unlink()
146        dom.unlink()
147
148        dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes()
149        dom.documentElement.insertBefore(frag, orig)
150        self.confirm(tuple(dom.documentElement.childNodes) ==
151                     (c1, c2, c3, orig),
152                     "insertBefore(<fragment>, orig)")
153        frag.unlink()
154        dom.unlink()
155
156    def testAppendChild(self):
157        dom = parse(tstfile)
158        dom.documentElement.appendChild(dom.createComment("Hello"))
159        self.confirm(dom.documentElement.childNodes[-1].nodeName == "#comment")
160        self.confirm(dom.documentElement.childNodes[-1].data == "Hello")
161        dom.unlink()
162
163    def testAppendChildFragment(self):
164        dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes()
165        dom.documentElement.appendChild(frag)
166        self.confirm(tuple(dom.documentElement.childNodes) ==
167                     (orig, c1, c2, c3),
168                     "appendChild(<fragment>)")
169        frag.unlink()
170        dom.unlink()
171
172    def testReplaceChildFragment(self):
173        dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes()
174        dom.documentElement.replaceChild(frag, orig)
175        orig.unlink()
176        self.confirm(tuple(dom.documentElement.childNodes) == (c1, c2, c3),
177                "replaceChild(<fragment>)")
178        frag.unlink()
179        dom.unlink()
180
181    def testLegalChildren(self):
182        dom = Document()
183        elem = dom.createElement('element')
184        text = dom.createTextNode('text')
185        self.assertRaises(xml.dom.HierarchyRequestErr, dom.appendChild, text)
186
187        dom.appendChild(elem)
188        self.assertRaises(xml.dom.HierarchyRequestErr, dom.insertBefore, text,
189                          elem)
190        self.assertRaises(xml.dom.HierarchyRequestErr, dom.replaceChild, text,
191                          elem)
192
193        nodemap = elem.attributes
194        self.assertRaises(xml.dom.HierarchyRequestErr, nodemap.setNamedItem,
195                          text)
196        self.assertRaises(xml.dom.HierarchyRequestErr, nodemap.setNamedItemNS,
197                          text)
198
199        elem.appendChild(text)
200        dom.unlink()
201
202    def testNamedNodeMapSetItem(self):
203        dom = Document()
204        elem = dom.createElement('element')
205        attrs = elem.attributes
206        attrs["foo"] = "bar"
207        a = attrs.item(0)
208        self.confirm(a.ownerDocument is dom,
209                "NamedNodeMap.__setitem__() sets ownerDocument")
210        self.confirm(a.ownerElement is elem,
211                "NamedNodeMap.__setitem__() sets ownerElement")
212        self.confirm(a.value == "bar",
213                "NamedNodeMap.__setitem__() sets value")
214        self.confirm(a.nodeValue == "bar",
215                "NamedNodeMap.__setitem__() sets nodeValue")
216        elem.unlink()
217        dom.unlink()
218
219    def testNonZero(self):
220        dom = parse(tstfile)
221        self.confirm(dom)# should not be zero
222        dom.appendChild(dom.createComment("foo"))
223        self.confirm(not dom.childNodes[-1].childNodes)
224        dom.unlink()
225
226    def testUnlink(self):
227        dom = parse(tstfile)
228        self.assertTrue(dom.childNodes)
229        dom.unlink()
230        self.assertFalse(dom.childNodes)
231
232    def testContext(self):
233        with parse(tstfile) as dom:
234            self.assertTrue(dom.childNodes)
235        self.assertFalse(dom.childNodes)
236
237    def testElement(self):
238        dom = Document()
239        dom.appendChild(dom.createElement("abc"))
240        self.confirm(dom.documentElement)
241        dom.unlink()
242
243    def testAAA(self):
244        dom = parseString("<abc/>")
245        el = dom.documentElement
246        el.setAttribute("spam", "jam2")
247        self.confirm(el.toxml() == '<abc spam="jam2"/>', "testAAA")
248        a = el.getAttributeNode("spam")
249        self.confirm(a.ownerDocument is dom,
250                "setAttribute() sets ownerDocument")
251        self.confirm(a.ownerElement is dom.documentElement,
252                "setAttribute() sets ownerElement")
253        dom.unlink()
254
255    def testAAB(self):
256        dom = parseString("<abc/>")
257        el = dom.documentElement
258        el.setAttribute("spam", "jam")
259        el.setAttribute("spam", "jam2")
260        self.confirm(el.toxml() == '<abc spam="jam2"/>', "testAAB")
261        dom.unlink()
262
263    def testAddAttr(self):
264        dom = Document()
265        child = dom.appendChild(dom.createElement("abc"))
266
267        child.setAttribute("def", "ghi")
268        self.confirm(child.getAttribute("def") == "ghi")
269        self.confirm(child.attributes["def"].value == "ghi")
270
271        child.setAttribute("jkl", "mno")
272        self.confirm(child.getAttribute("jkl") == "mno")
273        self.confirm(child.attributes["jkl"].value == "mno")
274
275        self.confirm(len(child.attributes) == 2)
276
277        child.setAttribute("def", "newval")
278        self.confirm(child.getAttribute("def") == "newval")
279        self.confirm(child.attributes["def"].value == "newval")
280
281        self.confirm(len(child.attributes) == 2)
282        dom.unlink()
283
284    def testDeleteAttr(self):
285        dom = Document()
286        child = dom.appendChild(dom.createElement("abc"))
287
288        self.confirm(len(child.attributes) == 0)
289        child.setAttribute("def", "ghi")
290        self.confirm(len(child.attributes) == 1)
291        del child.attributes["def"]
292        self.confirm(len(child.attributes) == 0)
293        dom.unlink()
294
295    def testRemoveAttr(self):
296        dom = Document()
297        child = dom.appendChild(dom.createElement("abc"))
298
299        child.setAttribute("def", "ghi")
300        self.confirm(len(child.attributes) == 1)
301        self.assertRaises(xml.dom.NotFoundErr, child.removeAttribute, "foo")
302        child.removeAttribute("def")
303        self.confirm(len(child.attributes) == 0)
304        dom.unlink()
305
306    def testRemoveAttrNS(self):
307        dom = Document()
308        child = dom.appendChild(
309                dom.createElementNS("http://www.python.org", "python:abc"))
310        child.setAttributeNS("http://www.w3.org", "xmlns:python",
311                                                "http://www.python.org")
312        child.setAttributeNS("http://www.python.org", "python:abcattr", "foo")
313        self.assertRaises(xml.dom.NotFoundErr, child.removeAttributeNS,
314            "foo", "http://www.python.org")
315        self.confirm(len(child.attributes) == 2)
316        child.removeAttributeNS("http://www.python.org", "abcattr")
317        self.confirm(len(child.attributes) == 1)
318        dom.unlink()
319
320    def testRemoveAttributeNode(self):
321        dom = Document()
322        child = dom.appendChild(dom.createElement("foo"))
323        child.setAttribute("spam", "jam")
324        self.confirm(len(child.attributes) == 1)
325        node = child.getAttributeNode("spam")
326        self.assertRaises(xml.dom.NotFoundErr, child.removeAttributeNode,
327            None)
328        child.removeAttributeNode(node)
329        self.confirm(len(child.attributes) == 0
330                and child.getAttributeNode("spam") is None)
331        dom2 = Document()
332        child2 = dom2.appendChild(dom2.createElement("foo"))
333        node2 = child2.getAttributeNode("spam")
334        self.assertRaises(xml.dom.NotFoundErr, child2.removeAttributeNode,
335            node2)
336        dom.unlink()
337
338    def testHasAttribute(self):
339        dom = Document()
340        child = dom.appendChild(dom.createElement("foo"))
341        child.setAttribute("spam", "jam")
342        self.confirm(child.hasAttribute("spam"))
343
344    def testChangeAttr(self):
345        dom = parseString("<abc/>")
346        el = dom.documentElement
347        el.setAttribute("spam", "jam")
348        self.confirm(len(el.attributes) == 1)
349        el.setAttribute("spam", "bam")
350        # Set this attribute to be an ID and make sure that doesn't change
351        # when changing the value:
352        el.setIdAttribute("spam")
353        self.confirm(len(el.attributes) == 1
354                and el.attributes["spam"].value == "bam"
355                and el.attributes["spam"].nodeValue == "bam"
356                and el.getAttribute("spam") == "bam"
357                and el.getAttributeNode("spam").isId)
358        el.attributes["spam"] = "ham"
359        self.confirm(len(el.attributes) == 1
360                and el.attributes["spam"].value == "ham"
361                and el.attributes["spam"].nodeValue == "ham"
362                and el.getAttribute("spam") == "ham"
363                and el.attributes["spam"].isId)
364        el.setAttribute("spam2", "bam")
365        self.confirm(len(el.attributes) == 2
366                and el.attributes["spam"].value == "ham"
367                and el.attributes["spam"].nodeValue == "ham"
368                and el.getAttribute("spam") == "ham"
369                and el.attributes["spam2"].value == "bam"
370                and el.attributes["spam2"].nodeValue == "bam"
371                and el.getAttribute("spam2") == "bam")
372        el.attributes["spam2"] = "bam2"
373        self.confirm(len(el.attributes) == 2
374                and el.attributes["spam"].value == "ham"
375                and el.attributes["spam"].nodeValue == "ham"
376                and el.getAttribute("spam") == "ham"
377                and el.attributes["spam2"].value == "bam2"
378                and el.attributes["spam2"].nodeValue == "bam2"
379                and el.getAttribute("spam2") == "bam2")
380        dom.unlink()
381
382    def testGetAttrList(self):
383        pass
384
385    def testGetAttrValues(self):
386        pass
387
388    def testGetAttrLength(self):
389        pass
390
391    def testGetAttribute(self):
392        dom = Document()
393        child = dom.appendChild(
394            dom.createElementNS("http://www.python.org", "python:abc"))
395        self.assertEqual(child.getAttribute('missing'), '')
396
397    def testGetAttributeNS(self):
398        dom = Document()
399        child = dom.appendChild(
400                dom.createElementNS("http://www.python.org", "python:abc"))
401        child.setAttributeNS("http://www.w3.org", "xmlns:python",
402                                                "http://www.python.org")
403        self.assertEqual(child.getAttributeNS("http://www.w3.org", "python"),
404            'http://www.python.org')
405        self.assertEqual(child.getAttributeNS("http://www.w3.org", "other"),
406            '')
407        child2 = child.appendChild(dom.createElement('abc'))
408        self.assertEqual(child2.getAttributeNS("http://www.python.org", "missing"),
409                         '')
410
411    def testGetAttributeNode(self): pass
412
413    def testGetElementsByTagNameNS(self):
414        d="""<foo xmlns:minidom='http://pyxml.sf.net/minidom'>
415        <minidom:myelem/>
416        </foo>"""
417        dom = parseString(d)
418        elems = dom.getElementsByTagNameNS("http://pyxml.sf.net/minidom",
419                                           "myelem")
420        self.confirm(len(elems) == 1
421                and elems[0].namespaceURI == "http://pyxml.sf.net/minidom"
422                and elems[0].localName == "myelem"
423                and elems[0].prefix == "minidom"
424                and elems[0].tagName == "minidom:myelem"
425                and elems[0].nodeName == "minidom:myelem")
426        dom.unlink()
427
428    def get_empty_nodelist_from_elements_by_tagName_ns_helper(self, doc, nsuri,
429                                                              lname):
430        nodelist = doc.getElementsByTagNameNS(nsuri, lname)
431        self.confirm(len(nodelist) == 0)
432
433    def testGetEmptyNodeListFromElementsByTagNameNS(self):
434        doc = parseString('<doc/>')
435        self.get_empty_nodelist_from_elements_by_tagName_ns_helper(
436            doc, 'http://xml.python.org/namespaces/a', 'localname')
437        self.get_empty_nodelist_from_elements_by_tagName_ns_helper(
438            doc, '*', 'splat')
439        self.get_empty_nodelist_from_elements_by_tagName_ns_helper(
440            doc, 'http://xml.python.org/namespaces/a', '*')
441
442        doc = parseString('<doc xmlns="http://xml.python.org/splat"><e/></doc>')
443        self.get_empty_nodelist_from_elements_by_tagName_ns_helper(
444            doc, "http://xml.python.org/splat", "not-there")
445        self.get_empty_nodelist_from_elements_by_tagName_ns_helper(
446            doc, "*", "not-there")
447        self.get_empty_nodelist_from_elements_by_tagName_ns_helper(
448            doc, "http://somewhere.else.net/not-there", "e")
449
450    def testElementReprAndStr(self):
451        dom = Document()
452        el = dom.appendChild(dom.createElement("abc"))
453        string1 = repr(el)
454        string2 = str(el)
455        self.confirm(string1 == string2)
456        dom.unlink()
457
458    def testElementReprAndStrUnicode(self):
459        dom = Document()
460        el = dom.appendChild(dom.createElement("abc"))
461        string1 = repr(el)
462        string2 = str(el)
463        self.confirm(string1 == string2)
464        dom.unlink()
465
466    def testElementReprAndStrUnicodeNS(self):
467        dom = Document()
468        el = dom.appendChild(
469            dom.createElementNS("http://www.slashdot.org", "slash:abc"))
470        string1 = repr(el)
471        string2 = str(el)
472        self.confirm(string1 == string2)
473        self.confirm("slash:abc" in string1)
474        dom.unlink()
475
476    def testAttributeRepr(self):
477        dom = Document()
478        el = dom.appendChild(dom.createElement("abc"))
479        node = el.setAttribute("abc", "def")
480        self.confirm(str(node) == repr(node))
481        dom.unlink()
482
483    def testTextNodeRepr(self): pass
484
485    def testWriteXML(self):
486        str = '<?xml version="1.0" ?><a b="c"/>'
487        dom = parseString(str)
488        domstr = dom.toxml()
489        dom.unlink()
490        self.confirm(str == domstr)
491
492    def testAltNewline(self):
493        str = '<?xml version="1.0" ?>\n<a b="c"/>\n'
494        dom = parseString(str)
495        domstr = dom.toprettyxml(newl="\r\n")
496        dom.unlink()
497        self.confirm(domstr == str.replace("\n", "\r\n"))
498
499    def test_toprettyxml_with_text_nodes(self):
500        # see issue #4147, text nodes are not indented
501        decl = '<?xml version="1.0" ?>\n'
502        self.assertEqual(parseString('<B>A</B>').toprettyxml(),
503                         decl + '<B>A</B>\n')
504        self.assertEqual(parseString('<C>A<B>A</B></C>').toprettyxml(),
505                         decl + '<C>\n\tA\n\t<B>A</B>\n</C>\n')
506        self.assertEqual(parseString('<C><B>A</B>A</C>').toprettyxml(),
507                         decl + '<C>\n\t<B>A</B>\n\tA\n</C>\n')
508        self.assertEqual(parseString('<C><B>A</B><B>A</B></C>').toprettyxml(),
509                         decl + '<C>\n\t<B>A</B>\n\t<B>A</B>\n</C>\n')
510        self.assertEqual(parseString('<C><B>A</B>A<B>A</B></C>').toprettyxml(),
511                         decl + '<C>\n\t<B>A</B>\n\tA\n\t<B>A</B>\n</C>\n')
512
513    def test_toprettyxml_with_adjacent_text_nodes(self):
514        # see issue #4147, adjacent text nodes are indented normally
515        dom = Document()
516        elem = dom.createElement('elem')
517        elem.appendChild(dom.createTextNode('TEXT'))
518        elem.appendChild(dom.createTextNode('TEXT'))
519        dom.appendChild(elem)
520        decl = '<?xml version="1.0" ?>\n'
521        self.assertEqual(dom.toprettyxml(),
522                         decl + '<elem>\n\tTEXT\n\tTEXT\n</elem>\n')
523
524    def test_toprettyxml_preserves_content_of_text_node(self):
525        # see issue #4147
526        for str in ('<B>A</B>', '<A><B>C</B></A>'):
527            dom = parseString(str)
528            dom2 = parseString(dom.toprettyxml())
529            self.assertEqual(
530                dom.getElementsByTagName('B')[0].childNodes[0].toxml(),
531                dom2.getElementsByTagName('B')[0].childNodes[0].toxml())
532
533    def testProcessingInstruction(self):
534        dom = parseString('<e><?mypi \t\n data \t\n ?></e>')
535        pi = dom.documentElement.firstChild
536        self.confirm(pi.target == "mypi"
537                and pi.data == "data \t\n "
538                and pi.nodeName == "mypi"
539                and pi.nodeType == Node.PROCESSING_INSTRUCTION_NODE
540                and pi.attributes is None
541                and not pi.hasChildNodes()
542                and len(pi.childNodes) == 0
543                and pi.firstChild is None
544                and pi.lastChild is None
545                and pi.localName is None
546                and pi.namespaceURI == xml.dom.EMPTY_NAMESPACE)
547
548    def testProcessingInstructionRepr(self): pass
549
550    def testTextRepr(self): pass
551
552    def testWriteText(self): pass
553
554    def testDocumentElement(self): pass
555
556    def testTooManyDocumentElements(self):
557        doc = parseString("<doc/>")
558        elem = doc.createElement("extra")
559        # Should raise an exception when adding an extra document element.
560        self.assertRaises(xml.dom.HierarchyRequestErr, doc.appendChild, elem)
561        elem.unlink()
562        doc.unlink()
563
564    def testCreateElementNS(self): pass
565
566    def testCreateAttributeNS(self): pass
567
568    def testParse(self): pass
569
570    def testParseString(self): pass
571
572    def testComment(self): pass
573
574    def testAttrListItem(self): pass
575
576    def testAttrListItems(self): pass
577
578    def testAttrListItemNS(self): pass
579
580    def testAttrListKeys(self): pass
581
582    def testAttrListKeysNS(self): pass
583
584    def testRemoveNamedItem(self):
585        doc = parseString("<doc a=''/>")
586        e = doc.documentElement
587        attrs = e.attributes
588        a1 = e.getAttributeNode("a")
589        a2 = attrs.removeNamedItem("a")
590        self.confirm(a1.isSameNode(a2))
591        self.assertRaises(xml.dom.NotFoundErr, attrs.removeNamedItem, "a")
592
593    def testRemoveNamedItemNS(self):
594        doc = parseString("<doc xmlns:a='http://xml.python.org/' a:b=''/>")
595        e = doc.documentElement
596        attrs = e.attributes
597        a1 = e.getAttributeNodeNS("http://xml.python.org/", "b")
598        a2 = attrs.removeNamedItemNS("http://xml.python.org/", "b")
599        self.confirm(a1.isSameNode(a2))
600        self.assertRaises(xml.dom.NotFoundErr, attrs.removeNamedItemNS,
601                          "http://xml.python.org/", "b")
602
603    def testAttrListValues(self): pass
604
605    def testAttrListLength(self): pass
606
607    def testAttrList__getitem__(self): pass
608
609    def testAttrList__setitem__(self): pass
610
611    def testSetAttrValueandNodeValue(self): pass
612
613    def testParseElement(self): pass
614
615    def testParseAttributes(self): pass
616
617    def testParseElementNamespaces(self): pass
618
619    def testParseAttributeNamespaces(self): pass
620
621    def testParseProcessingInstructions(self): pass
622
623    def testChildNodes(self): pass
624
625    def testFirstChild(self): pass
626
627    def testHasChildNodes(self):
628        dom = parseString("<doc><foo/></doc>")
629        doc = dom.documentElement
630        self.assertTrue(doc.hasChildNodes())
631        dom2 = parseString("<doc/>")
632        doc2 = dom2.documentElement
633        self.assertFalse(doc2.hasChildNodes())
634
635    def _testCloneElementCopiesAttributes(self, e1, e2, test):
636        attrs1 = e1.attributes
637        attrs2 = e2.attributes
638        keys1 = list(attrs1.keys())
639        keys2 = list(attrs2.keys())
640        keys1.sort()
641        keys2.sort()
642        self.confirm(keys1 == keys2, "clone of element has same attribute keys")
643        for i in range(len(keys1)):
644            a1 = attrs1.item(i)
645            a2 = attrs2.item(i)
646            self.confirm(a1 is not a2
647                    and a1.value == a2.value
648                    and a1.nodeValue == a2.nodeValue
649                    and a1.namespaceURI == a2.namespaceURI
650                    and a1.localName == a2.localName
651                    , "clone of attribute node has proper attribute values")
652            self.confirm(a2.ownerElement is e2,
653                    "clone of attribute node correctly owned")
654
655    def _setupCloneElement(self, deep):
656        dom = parseString("<doc attr='value'><foo/></doc>")
657        root = dom.documentElement
658        clone = root.cloneNode(deep)
659        self._testCloneElementCopiesAttributes(
660            root, clone, "testCloneElement" + (deep and "Deep" or "Shallow"))
661        # mutilate the original so shared data is detected
662        root.tagName = root.nodeName = "MODIFIED"
663        root.setAttribute("attr", "NEW VALUE")
664        root.setAttribute("added", "VALUE")
665        return dom, clone
666
667    def testCloneElementShallow(self):
668        dom, clone = self._setupCloneElement(0)
669        self.confirm(len(clone.childNodes) == 0
670                and clone.childNodes.length == 0
671                and clone.parentNode is None
672                and clone.toxml() == '<doc attr="value"/>'
673                , "testCloneElementShallow")
674        dom.unlink()
675
676    def testCloneElementDeep(self):
677        dom, clone = self._setupCloneElement(1)
678        self.confirm(len(clone.childNodes) == 1
679                and clone.childNodes.length == 1
680                and clone.parentNode is None
681                and clone.toxml() == '<doc attr="value"><foo/></doc>'
682                , "testCloneElementDeep")
683        dom.unlink()
684
685    def testCloneDocumentShallow(self):
686        doc = parseString("<?xml version='1.0'?>\n"
687                    "<!-- comment -->"
688                    "<!DOCTYPE doc [\n"
689                    "<!NOTATION notation SYSTEM 'http://xml.python.org/'>\n"
690                    "]>\n"
691                    "<doc attr='value'/>")
692        doc2 = doc.cloneNode(0)
693        self.confirm(doc2 is None,
694                "testCloneDocumentShallow:"
695                " shallow cloning of documents makes no sense!")
696
697    def testCloneDocumentDeep(self):
698        doc = parseString("<?xml version='1.0'?>\n"
699                    "<!-- comment -->"
700                    "<!DOCTYPE doc [\n"
701                    "<!NOTATION notation SYSTEM 'http://xml.python.org/'>\n"
702                    "]>\n"
703                    "<doc attr='value'/>")
704        doc2 = doc.cloneNode(1)
705        self.confirm(not (doc.isSameNode(doc2) or doc2.isSameNode(doc)),
706                "testCloneDocumentDeep: document objects not distinct")
707        self.confirm(len(doc.childNodes) == len(doc2.childNodes),
708                "testCloneDocumentDeep: wrong number of Document children")
709        self.confirm(doc2.documentElement.nodeType == Node.ELEMENT_NODE,
710                "testCloneDocumentDeep: documentElement not an ELEMENT_NODE")
711        self.confirm(doc2.documentElement.ownerDocument.isSameNode(doc2),
712            "testCloneDocumentDeep: documentElement owner is not new document")
713        self.confirm(not doc.documentElement.isSameNode(doc2.documentElement),
714                "testCloneDocumentDeep: documentElement should not be shared")
715        if doc.doctype is not None:
716            # check the doctype iff the original DOM maintained it
717            self.confirm(doc2.doctype.nodeType == Node.DOCUMENT_TYPE_NODE,
718                    "testCloneDocumentDeep: doctype not a DOCUMENT_TYPE_NODE")
719            self.confirm(doc2.doctype.ownerDocument.isSameNode(doc2))
720            self.confirm(not doc.doctype.isSameNode(doc2.doctype))
721
722    def testCloneDocumentTypeDeepOk(self):
723        doctype = create_nonempty_doctype()
724        clone = doctype.cloneNode(1)
725        self.confirm(clone is not None
726                and clone.nodeName == doctype.nodeName
727                and clone.name == doctype.name
728                and clone.publicId == doctype.publicId
729                and clone.systemId == doctype.systemId
730                and len(clone.entities) == len(doctype.entities)
731                and clone.entities.item(len(clone.entities)) is None
732                and len(clone.notations) == len(doctype.notations)
733                and clone.notations.item(len(clone.notations)) is None
734                and len(clone.childNodes) == 0)
735        for i in range(len(doctype.entities)):
736            se = doctype.entities.item(i)
737            ce = clone.entities.item(i)
738            self.confirm((not se.isSameNode(ce))
739                    and (not ce.isSameNode(se))
740                    and ce.nodeName == se.nodeName
741                    and ce.notationName == se.notationName
742                    and ce.publicId == se.publicId
743                    and ce.systemId == se.systemId
744                    and ce.encoding == se.encoding
745                    and ce.actualEncoding == se.actualEncoding
746                    and ce.version == se.version)
747        for i in range(len(doctype.notations)):
748            sn = doctype.notations.item(i)
749            cn = clone.notations.item(i)
750            self.confirm((not sn.isSameNode(cn))
751                    and (not cn.isSameNode(sn))
752                    and cn.nodeName == sn.nodeName
753                    and cn.publicId == sn.publicId
754                    and cn.systemId == sn.systemId)
755
756    def testCloneDocumentTypeDeepNotOk(self):
757        doc = create_doc_with_doctype()
758        clone = doc.doctype.cloneNode(1)
759        self.confirm(clone is None, "testCloneDocumentTypeDeepNotOk")
760
761    def testCloneDocumentTypeShallowOk(self):
762        doctype = create_nonempty_doctype()
763        clone = doctype.cloneNode(0)
764        self.confirm(clone is not None
765                and clone.nodeName == doctype.nodeName
766                and clone.name == doctype.name
767                and clone.publicId == doctype.publicId
768                and clone.systemId == doctype.systemId
769                and len(clone.entities) == 0
770                and clone.entities.item(0) is None
771                and len(clone.notations) == 0
772                and clone.notations.item(0) is None
773                and len(clone.childNodes) == 0)
774
775    def testCloneDocumentTypeShallowNotOk(self):
776        doc = create_doc_with_doctype()
777        clone = doc.doctype.cloneNode(0)
778        self.confirm(clone is None, "testCloneDocumentTypeShallowNotOk")
779
780    def check_import_document(self, deep, testName):
781        doc1 = parseString("<doc/>")
782        doc2 = parseString("<doc/>")
783        self.assertRaises(xml.dom.NotSupportedErr, doc1.importNode, doc2, deep)
784
785    def testImportDocumentShallow(self):
786        self.check_import_document(0, "testImportDocumentShallow")
787
788    def testImportDocumentDeep(self):
789        self.check_import_document(1, "testImportDocumentDeep")
790
791    def testImportDocumentTypeShallow(self):
792        src = create_doc_with_doctype()
793        target = create_doc_without_doctype()
794        self.assertRaises(xml.dom.NotSupportedErr, target.importNode,
795                          src.doctype, 0)
796
797    def testImportDocumentTypeDeep(self):
798        src = create_doc_with_doctype()
799        target = create_doc_without_doctype()
800        self.assertRaises(xml.dom.NotSupportedErr, target.importNode,
801                          src.doctype, 1)
802
803    # Testing attribute clones uses a helper, and should always be deep,
804    # even if the argument to cloneNode is false.
805    def check_clone_attribute(self, deep, testName):
806        doc = parseString("<doc attr='value'/>")
807        attr = doc.documentElement.getAttributeNode("attr")
808        self.assertNotEqual(attr, None)
809        clone = attr.cloneNode(deep)
810        self.confirm(not clone.isSameNode(attr))
811        self.confirm(not attr.isSameNode(clone))
812        self.confirm(clone.ownerElement is None,
813                testName + ": ownerElement should be None")
814        self.confirm(clone.ownerDocument.isSameNode(attr.ownerDocument),
815                testName + ": ownerDocument does not match")
816        self.confirm(clone.specified,
817                testName + ": cloned attribute must have specified == True")
818
819    def testCloneAttributeShallow(self):
820        self.check_clone_attribute(0, "testCloneAttributeShallow")
821
822    def testCloneAttributeDeep(self):
823        self.check_clone_attribute(1, "testCloneAttributeDeep")
824
825    def check_clone_pi(self, deep, testName):
826        doc = parseString("<?target data?><doc/>")
827        pi = doc.firstChild
828        self.assertEqual(pi.nodeType, Node.PROCESSING_INSTRUCTION_NODE)
829        clone = pi.cloneNode(deep)
830        self.confirm(clone.target == pi.target
831                and clone.data == pi.data)
832
833    def testClonePIShallow(self):
834        self.check_clone_pi(0, "testClonePIShallow")
835
836    def testClonePIDeep(self):
837        self.check_clone_pi(1, "testClonePIDeep")
838
839    def check_clone_node_entity(self, clone_document):
840        # bpo-35052: Test user data handler in cloneNode() on a document with
841        # an entity
842        document = xml.dom.minidom.parseString("""
843            <?xml version="1.0" ?>
844            <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
845                "http://www.w3.org/TR/html4/strict.dtd"
846                [ <!ENTITY smile "☺"> ]
847            >
848            <doc>Don't let entities make you frown &smile;</doc>
849        """.strip())
850
851        class Handler:
852            def handle(self, operation, key, data, src, dst):
853                self.operation = operation
854                self.key = key
855                self.data = data
856                self.src = src
857                self.dst = dst
858
859        handler = Handler()
860        doctype = document.doctype
861        entity = doctype.entities['smile']
862        entity.setUserData("key", "data", handler)
863
864        if clone_document:
865            # clone Document
866            clone = document.cloneNode(deep=True)
867
868            self.assertEqual(clone.documentElement.firstChild.wholeText,
869                             "Don't let entities make you frown ☺")
870            operation = xml.dom.UserDataHandler.NODE_IMPORTED
871            dst = clone.doctype.entities['smile']
872        else:
873            # clone DocumentType
874            with support.swap_attr(doctype, 'ownerDocument', None):
875                clone = doctype.cloneNode(deep=True)
876
877            operation = xml.dom.UserDataHandler.NODE_CLONED
878            dst = clone.entities['smile']
879
880        self.assertEqual(handler.operation, operation)
881        self.assertEqual(handler.key, "key")
882        self.assertEqual(handler.data, "data")
883        self.assertIs(handler.src, entity)
884        self.assertIs(handler.dst, dst)
885
886    def testCloneNodeEntity(self):
887        self.check_clone_node_entity(False)
888        self.check_clone_node_entity(True)
889
890    def testNormalize(self):
891        doc = parseString("<doc/>")
892        root = doc.documentElement
893        root.appendChild(doc.createTextNode("first"))
894        root.appendChild(doc.createTextNode("second"))
895        self.confirm(len(root.childNodes) == 2
896                and root.childNodes.length == 2,
897                "testNormalize -- preparation")
898        doc.normalize()
899        self.confirm(len(root.childNodes) == 1
900                and root.childNodes.length == 1
901                and root.firstChild is root.lastChild
902                and root.firstChild.data == "firstsecond"
903                , "testNormalize -- result")
904        doc.unlink()
905
906        doc = parseString("<doc/>")
907        root = doc.documentElement
908        root.appendChild(doc.createTextNode(""))
909        doc.normalize()
910        self.confirm(len(root.childNodes) == 0
911                and root.childNodes.length == 0,
912                "testNormalize -- single empty node removed")
913        doc.unlink()
914
915    def testNormalizeCombineAndNextSibling(self):
916        doc = parseString("<doc/>")
917        root = doc.documentElement
918        root.appendChild(doc.createTextNode("first"))
919        root.appendChild(doc.createTextNode("second"))
920        root.appendChild(doc.createElement("i"))
921        self.confirm(len(root.childNodes) == 3
922                and root.childNodes.length == 3,
923                "testNormalizeCombineAndNextSibling -- preparation")
924        doc.normalize()
925        self.confirm(len(root.childNodes) == 2
926                and root.childNodes.length == 2
927                and root.firstChild.data == "firstsecond"
928                and root.firstChild is not root.lastChild
929                and root.firstChild.nextSibling is root.lastChild
930                and root.firstChild.previousSibling is None
931                and root.lastChild.previousSibling is root.firstChild
932                and root.lastChild.nextSibling is None
933                , "testNormalizeCombinedAndNextSibling -- result")
934        doc.unlink()
935
936    def testNormalizeDeleteWithPrevSibling(self):
937        doc = parseString("<doc/>")
938        root = doc.documentElement
939        root.appendChild(doc.createTextNode("first"))
940        root.appendChild(doc.createTextNode(""))
941        self.confirm(len(root.childNodes) == 2
942                and root.childNodes.length == 2,
943                "testNormalizeDeleteWithPrevSibling -- preparation")
944        doc.normalize()
945        self.confirm(len(root.childNodes) == 1
946                and root.childNodes.length == 1
947                and root.firstChild.data == "first"
948                and root.firstChild is root.lastChild
949                and root.firstChild.nextSibling is None
950                and root.firstChild.previousSibling is None
951                , "testNormalizeDeleteWithPrevSibling -- result")
952        doc.unlink()
953
954    def testNormalizeDeleteWithNextSibling(self):
955        doc = parseString("<doc/>")
956        root = doc.documentElement
957        root.appendChild(doc.createTextNode(""))
958        root.appendChild(doc.createTextNode("second"))
959        self.confirm(len(root.childNodes) == 2
960                and root.childNodes.length == 2,
961                "testNormalizeDeleteWithNextSibling -- preparation")
962        doc.normalize()
963        self.confirm(len(root.childNodes) == 1
964                and root.childNodes.length == 1
965                and root.firstChild.data == "second"
966                and root.firstChild is root.lastChild
967                and root.firstChild.nextSibling is None
968                and root.firstChild.previousSibling is None
969                , "testNormalizeDeleteWithNextSibling -- result")
970        doc.unlink()
971
972    def testNormalizeDeleteWithTwoNonTextSiblings(self):
973        doc = parseString("<doc/>")
974        root = doc.documentElement
975        root.appendChild(doc.createElement("i"))
976        root.appendChild(doc.createTextNode(""))
977        root.appendChild(doc.createElement("i"))
978        self.confirm(len(root.childNodes) == 3
979                and root.childNodes.length == 3,
980                "testNormalizeDeleteWithTwoSiblings -- preparation")
981        doc.normalize()
982        self.confirm(len(root.childNodes) == 2
983                and root.childNodes.length == 2
984                and root.firstChild is not root.lastChild
985                and root.firstChild.nextSibling is root.lastChild
986                and root.firstChild.previousSibling is None
987                and root.lastChild.previousSibling is root.firstChild
988                and root.lastChild.nextSibling is None
989                , "testNormalizeDeleteWithTwoSiblings -- result")
990        doc.unlink()
991
992    def testNormalizeDeleteAndCombine(self):
993        doc = parseString("<doc/>")
994        root = doc.documentElement
995        root.appendChild(doc.createTextNode(""))
996        root.appendChild(doc.createTextNode("second"))
997        root.appendChild(doc.createTextNode(""))
998        root.appendChild(doc.createTextNode("fourth"))
999        root.appendChild(doc.createTextNode(""))
1000        self.confirm(len(root.childNodes) == 5
1001                and root.childNodes.length == 5,
1002                "testNormalizeDeleteAndCombine -- preparation")
1003        doc.normalize()
1004        self.confirm(len(root.childNodes) == 1
1005                and root.childNodes.length == 1
1006                and root.firstChild is root.lastChild
1007                and root.firstChild.data == "secondfourth"
1008                and root.firstChild.previousSibling is None
1009                and root.firstChild.nextSibling is None
1010                , "testNormalizeDeleteAndCombine -- result")
1011        doc.unlink()
1012
1013    def testNormalizeRecursion(self):
1014        doc = parseString("<doc>"
1015                            "<o>"
1016                              "<i/>"
1017                              "t"
1018                              #
1019                              #x
1020                            "</o>"
1021                            "<o>"
1022                              "<o>"
1023                                "t2"
1024                                #x2
1025                              "</o>"
1026                              "t3"
1027                              #x3
1028                            "</o>"
1029                            #
1030                          "</doc>")
1031        root = doc.documentElement
1032        root.childNodes[0].appendChild(doc.createTextNode(""))
1033        root.childNodes[0].appendChild(doc.createTextNode("x"))
1034        root.childNodes[1].childNodes[0].appendChild(doc.createTextNode("x2"))
1035        root.childNodes[1].appendChild(doc.createTextNode("x3"))
1036        root.appendChild(doc.createTextNode(""))
1037        self.confirm(len(root.childNodes) == 3
1038                and root.childNodes.length == 3
1039                and len(root.childNodes[0].childNodes) == 4
1040                and root.childNodes[0].childNodes.length == 4
1041                and len(root.childNodes[1].childNodes) == 3
1042                and root.childNodes[1].childNodes.length == 3
1043                and len(root.childNodes[1].childNodes[0].childNodes) == 2
1044                and root.childNodes[1].childNodes[0].childNodes.length == 2
1045                , "testNormalize2 -- preparation")
1046        doc.normalize()
1047        self.confirm(len(root.childNodes) == 2
1048                and root.childNodes.length == 2
1049                and len(root.childNodes[0].childNodes) == 2
1050                and root.childNodes[0].childNodes.length == 2
1051                and len(root.childNodes[1].childNodes) == 2
1052                and root.childNodes[1].childNodes.length == 2
1053                and len(root.childNodes[1].childNodes[0].childNodes) == 1
1054                and root.childNodes[1].childNodes[0].childNodes.length == 1
1055                , "testNormalize2 -- childNodes lengths")
1056        self.confirm(root.childNodes[0].childNodes[1].data == "tx"
1057                and root.childNodes[1].childNodes[0].childNodes[0].data == "t2x2"
1058                and root.childNodes[1].childNodes[1].data == "t3x3"
1059                , "testNormalize2 -- joined text fields")
1060        self.confirm(root.childNodes[0].childNodes[1].nextSibling is None
1061                and root.childNodes[0].childNodes[1].previousSibling
1062                        is root.childNodes[0].childNodes[0]
1063                and root.childNodes[0].childNodes[0].previousSibling is None
1064                and root.childNodes[0].childNodes[0].nextSibling
1065                        is root.childNodes[0].childNodes[1]
1066                and root.childNodes[1].childNodes[1].nextSibling is None
1067                and root.childNodes[1].childNodes[1].previousSibling
1068                        is root.childNodes[1].childNodes[0]
1069                and root.childNodes[1].childNodes[0].previousSibling is None
1070                and root.childNodes[1].childNodes[0].nextSibling
1071                        is root.childNodes[1].childNodes[1]
1072                , "testNormalize2 -- sibling pointers")
1073        doc.unlink()
1074
1075
1076    def testBug0777884(self):
1077        doc = parseString("<o>text</o>")
1078        text = doc.documentElement.childNodes[0]
1079        self.assertEqual(text.nodeType, Node.TEXT_NODE)
1080        # Should run quietly, doing nothing.
1081        text.normalize()
1082        doc.unlink()
1083
1084    def testBug1433694(self):
1085        doc = parseString("<o><i/>t</o>")
1086        node = doc.documentElement
1087        node.childNodes[1].nodeValue = ""
1088        node.normalize()
1089        self.confirm(node.childNodes[-1].nextSibling is None,
1090                     "Final child's .nextSibling should be None")
1091
1092    def testSiblings(self):
1093        doc = parseString("<doc><?pi?>text?<elm/></doc>")
1094        root = doc.documentElement
1095        (pi, text, elm) = root.childNodes
1096
1097        self.confirm(pi.nextSibling is text and
1098                pi.previousSibling is None and
1099                text.nextSibling is elm and
1100                text.previousSibling is pi and
1101                elm.nextSibling is None and
1102                elm.previousSibling is text, "testSiblings")
1103
1104        doc.unlink()
1105
1106    def testParents(self):
1107        doc = parseString(
1108            "<doc><elm1><elm2/><elm2><elm3/></elm2></elm1></doc>")
1109        root = doc.documentElement
1110        elm1 = root.childNodes[0]
1111        (elm2a, elm2b) = elm1.childNodes
1112        elm3 = elm2b.childNodes[0]
1113
1114        self.confirm(root.parentNode is doc and
1115                elm1.parentNode is root and
1116                elm2a.parentNode is elm1 and
1117                elm2b.parentNode is elm1 and
1118                elm3.parentNode is elm2b, "testParents")
1119        doc.unlink()
1120
1121    def testNodeListItem(self):
1122        doc = parseString("<doc><e/><e/></doc>")
1123        children = doc.childNodes
1124        docelem = children[0]
1125        self.confirm(children[0] is children.item(0)
1126                and children.item(1) is None
1127                and docelem.childNodes.item(0) is docelem.childNodes[0]
1128                and docelem.childNodes.item(1) is docelem.childNodes[1]
1129                and docelem.childNodes.item(0).childNodes.item(0) is None,
1130                "test NodeList.item()")
1131        doc.unlink()
1132
1133    def testEncodings(self):
1134        doc = parseString('<foo>&#x20ac;</foo>')
1135        self.assertEqual(doc.toxml(),
1136                         '<?xml version="1.0" ?><foo>\u20ac</foo>')
1137        self.assertEqual(doc.toxml('utf-8'),
1138            b'<?xml version="1.0" encoding="utf-8"?><foo>\xe2\x82\xac</foo>')
1139        self.assertEqual(doc.toxml('iso-8859-15'),
1140            b'<?xml version="1.0" encoding="iso-8859-15"?><foo>\xa4</foo>')
1141        self.assertEqual(doc.toxml('us-ascii'),
1142            b'<?xml version="1.0" encoding="us-ascii"?><foo>&#8364;</foo>')
1143        self.assertEqual(doc.toxml('utf-16'),
1144            '<?xml version="1.0" encoding="utf-16"?>'
1145            '<foo>\u20ac</foo>'.encode('utf-16'))
1146
1147        # Verify that character decoding errors raise exceptions instead
1148        # of crashing
1149        self.assertRaises(UnicodeDecodeError, parseString,
1150                b'<fran\xe7ais>Comment \xe7a va ? Tr\xe8s bien ?</fran\xe7ais>')
1151
1152        doc.unlink()
1153
1154    class UserDataHandler:
1155        called = 0
1156        def handle(self, operation, key, data, src, dst):
1157            dst.setUserData(key, data + 1, self)
1158            src.setUserData(key, None, None)
1159            self.called = 1
1160
1161    def testUserData(self):
1162        dom = Document()
1163        n = dom.createElement('e')
1164        self.confirm(n.getUserData("foo") is None)
1165        n.setUserData("foo", None, None)
1166        self.confirm(n.getUserData("foo") is None)
1167        n.setUserData("foo", 12, 12)
1168        n.setUserData("bar", 13, 13)
1169        self.confirm(n.getUserData("foo") == 12)
1170        self.confirm(n.getUserData("bar") == 13)
1171        n.setUserData("foo", None, None)
1172        self.confirm(n.getUserData("foo") is None)
1173        self.confirm(n.getUserData("bar") == 13)
1174
1175        handler = self.UserDataHandler()
1176        n.setUserData("bar", 12, handler)
1177        c = n.cloneNode(1)
1178        self.confirm(handler.called
1179                and n.getUserData("bar") is None
1180                and c.getUserData("bar") == 13)
1181        n.unlink()
1182        c.unlink()
1183        dom.unlink()
1184
1185    def checkRenameNodeSharedConstraints(self, doc, node):
1186        # Make sure illegal NS usage is detected:
1187        self.assertRaises(xml.dom.NamespaceErr, doc.renameNode, node,
1188                          "http://xml.python.org/ns", "xmlns:foo")
1189        doc2 = parseString("<doc/>")
1190        self.assertRaises(xml.dom.WrongDocumentErr, doc2.renameNode, node,
1191                          xml.dom.EMPTY_NAMESPACE, "foo")
1192
1193    def testRenameAttribute(self):
1194        doc = parseString("<doc a='v'/>")
1195        elem = doc.documentElement
1196        attrmap = elem.attributes
1197        attr = elem.attributes['a']
1198
1199        # Simple renaming
1200        attr = doc.renameNode(attr, xml.dom.EMPTY_NAMESPACE, "b")
1201        self.confirm(attr.name == "b"
1202                and attr.nodeName == "b"
1203                and attr.localName is None
1204                and attr.namespaceURI == xml.dom.EMPTY_NAMESPACE
1205                and attr.prefix is None
1206                and attr.value == "v"
1207                and elem.getAttributeNode("a") is None
1208                and elem.getAttributeNode("b").isSameNode(attr)
1209                and attrmap["b"].isSameNode(attr)
1210                and attr.ownerDocument.isSameNode(doc)
1211                and attr.ownerElement.isSameNode(elem))
1212
1213        # Rename to have a namespace, no prefix
1214        attr = doc.renameNode(attr, "http://xml.python.org/ns", "c")
1215        self.confirm(attr.name == "c"
1216                and attr.nodeName == "c"
1217                and attr.localName == "c"
1218                and attr.namespaceURI == "http://xml.python.org/ns"
1219                and attr.prefix is None
1220                and attr.value == "v"
1221                and elem.getAttributeNode("a") is None
1222                and elem.getAttributeNode("b") is None
1223                and elem.getAttributeNode("c").isSameNode(attr)
1224                and elem.getAttributeNodeNS(
1225                    "http://xml.python.org/ns", "c").isSameNode(attr)
1226                and attrmap["c"].isSameNode(attr)
1227                and attrmap[("http://xml.python.org/ns", "c")].isSameNode(attr))
1228
1229        # Rename to have a namespace, with prefix
1230        attr = doc.renameNode(attr, "http://xml.python.org/ns2", "p:d")
1231        self.confirm(attr.name == "p:d"
1232                and attr.nodeName == "p:d"
1233                and attr.localName == "d"
1234                and attr.namespaceURI == "http://xml.python.org/ns2"
1235                and attr.prefix == "p"
1236                and attr.value == "v"
1237                and elem.getAttributeNode("a") is None
1238                and elem.getAttributeNode("b") is None
1239                and elem.getAttributeNode("c") is None
1240                and elem.getAttributeNodeNS(
1241                    "http://xml.python.org/ns", "c") is None
1242                and elem.getAttributeNode("p:d").isSameNode(attr)
1243                and elem.getAttributeNodeNS(
1244                    "http://xml.python.org/ns2", "d").isSameNode(attr)
1245                and attrmap["p:d"].isSameNode(attr)
1246                and attrmap[("http://xml.python.org/ns2", "d")].isSameNode(attr))
1247
1248        # Rename back to a simple non-NS node
1249        attr = doc.renameNode(attr, xml.dom.EMPTY_NAMESPACE, "e")
1250        self.confirm(attr.name == "e"
1251                and attr.nodeName == "e"
1252                and attr.localName is None
1253                and attr.namespaceURI == xml.dom.EMPTY_NAMESPACE
1254                and attr.prefix is None
1255                and attr.value == "v"
1256                and elem.getAttributeNode("a") is None
1257                and elem.getAttributeNode("b") is None
1258                and elem.getAttributeNode("c") is None
1259                and elem.getAttributeNode("p:d") is None
1260                and elem.getAttributeNodeNS(
1261                    "http://xml.python.org/ns", "c") is None
1262                and elem.getAttributeNode("e").isSameNode(attr)
1263                and attrmap["e"].isSameNode(attr))
1264
1265        self.assertRaises(xml.dom.NamespaceErr, doc.renameNode, attr,
1266                          "http://xml.python.org/ns", "xmlns")
1267        self.checkRenameNodeSharedConstraints(doc, attr)
1268        doc.unlink()
1269
1270    def testRenameElement(self):
1271        doc = parseString("<doc/>")
1272        elem = doc.documentElement
1273
1274        # Simple renaming
1275        elem = doc.renameNode(elem, xml.dom.EMPTY_NAMESPACE, "a")
1276        self.confirm(elem.tagName == "a"
1277                and elem.nodeName == "a"
1278                and elem.localName is None
1279                and elem.namespaceURI == xml.dom.EMPTY_NAMESPACE
1280                and elem.prefix is None
1281                and elem.ownerDocument.isSameNode(doc))
1282
1283        # Rename to have a namespace, no prefix
1284        elem = doc.renameNode(elem, "http://xml.python.org/ns", "b")
1285        self.confirm(elem.tagName == "b"
1286                and elem.nodeName == "b"
1287                and elem.localName == "b"
1288                and elem.namespaceURI == "http://xml.python.org/ns"
1289                and elem.prefix is None
1290                and elem.ownerDocument.isSameNode(doc))
1291
1292        # Rename to have a namespace, with prefix
1293        elem = doc.renameNode(elem, "http://xml.python.org/ns2", "p:c")
1294        self.confirm(elem.tagName == "p:c"
1295                and elem.nodeName == "p:c"
1296                and elem.localName == "c"
1297                and elem.namespaceURI == "http://xml.python.org/ns2"
1298                and elem.prefix == "p"
1299                and elem.ownerDocument.isSameNode(doc))
1300
1301        # Rename back to a simple non-NS node
1302        elem = doc.renameNode(elem, xml.dom.EMPTY_NAMESPACE, "d")
1303        self.confirm(elem.tagName == "d"
1304                and elem.nodeName == "d"
1305                and elem.localName is None
1306                and elem.namespaceURI == xml.dom.EMPTY_NAMESPACE
1307                and elem.prefix is None
1308                and elem.ownerDocument.isSameNode(doc))
1309
1310        self.checkRenameNodeSharedConstraints(doc, elem)
1311        doc.unlink()
1312
1313    def testRenameOther(self):
1314        # We have to create a comment node explicitly since not all DOM
1315        # builders used with minidom add comments to the DOM.
1316        doc = xml.dom.minidom.getDOMImplementation().createDocument(
1317            xml.dom.EMPTY_NAMESPACE, "e", None)
1318        node = doc.createComment("comment")
1319        self.assertRaises(xml.dom.NotSupportedErr, doc.renameNode, node,
1320                          xml.dom.EMPTY_NAMESPACE, "foo")
1321        doc.unlink()
1322
1323    def testWholeText(self):
1324        doc = parseString("<doc>a</doc>")
1325        elem = doc.documentElement
1326        text = elem.childNodes[0]
1327        self.assertEqual(text.nodeType, Node.TEXT_NODE)
1328
1329        self.checkWholeText(text, "a")
1330        elem.appendChild(doc.createTextNode("b"))
1331        self.checkWholeText(text, "ab")
1332        elem.insertBefore(doc.createCDATASection("c"), text)
1333        self.checkWholeText(text, "cab")
1334
1335        # make sure we don't cross other nodes
1336        splitter = doc.createComment("comment")
1337        elem.appendChild(splitter)
1338        text2 = doc.createTextNode("d")
1339        elem.appendChild(text2)
1340        self.checkWholeText(text, "cab")
1341        self.checkWholeText(text2, "d")
1342
1343        x = doc.createElement("x")
1344        elem.replaceChild(x, splitter)
1345        splitter = x
1346        self.checkWholeText(text, "cab")
1347        self.checkWholeText(text2, "d")
1348
1349        x = doc.createProcessingInstruction("y", "z")
1350        elem.replaceChild(x, splitter)
1351        splitter = x
1352        self.checkWholeText(text, "cab")
1353        self.checkWholeText(text2, "d")
1354
1355        elem.removeChild(splitter)
1356        self.checkWholeText(text, "cabd")
1357        self.checkWholeText(text2, "cabd")
1358
1359    def testPatch1094164(self):
1360        doc = parseString("<doc><e/></doc>")
1361        elem = doc.documentElement
1362        e = elem.firstChild
1363        self.confirm(e.parentNode is elem, "Before replaceChild()")
1364        # Check that replacing a child with itself leaves the tree unchanged
1365        elem.replaceChild(e, e)
1366        self.confirm(e.parentNode is elem, "After replaceChild()")
1367
1368    def testReplaceWholeText(self):
1369        def setup():
1370            doc = parseString("<doc>a<e/>d</doc>")
1371            elem = doc.documentElement
1372            text1 = elem.firstChild
1373            text2 = elem.lastChild
1374            splitter = text1.nextSibling
1375            elem.insertBefore(doc.createTextNode("b"), splitter)
1376            elem.insertBefore(doc.createCDATASection("c"), text1)
1377            return doc, elem, text1, splitter, text2
1378
1379        doc, elem, text1, splitter, text2 = setup()
1380        text = text1.replaceWholeText("new content")
1381        self.checkWholeText(text, "new content")
1382        self.checkWholeText(text2, "d")
1383        self.confirm(len(elem.childNodes) == 3)
1384
1385        doc, elem, text1, splitter, text2 = setup()
1386        text = text2.replaceWholeText("new content")
1387        self.checkWholeText(text, "new content")
1388        self.checkWholeText(text1, "cab")
1389        self.confirm(len(elem.childNodes) == 5)
1390
1391        doc, elem, text1, splitter, text2 = setup()
1392        text = text1.replaceWholeText("")
1393        self.checkWholeText(text2, "d")
1394        self.confirm(text is None
1395                and len(elem.childNodes) == 2)
1396
1397    def testSchemaType(self):
1398        doc = parseString(
1399            "<!DOCTYPE doc [\n"
1400            "  <!ENTITY e1 SYSTEM 'http://xml.python.org/e1'>\n"
1401            "  <!ENTITY e2 SYSTEM 'http://xml.python.org/e2'>\n"
1402            "  <!ATTLIST doc id   ID       #IMPLIED \n"
1403            "                ref  IDREF    #IMPLIED \n"
1404            "                refs IDREFS   #IMPLIED \n"
1405            "                enum (a|b)    #IMPLIED \n"
1406            "                ent  ENTITY   #IMPLIED \n"
1407            "                ents ENTITIES #IMPLIED \n"
1408            "                nm   NMTOKEN  #IMPLIED \n"
1409            "                nms  NMTOKENS #IMPLIED \n"
1410            "                text CDATA    #IMPLIED \n"
1411            "    >\n"
1412            "]><doc id='name' notid='name' text='splat!' enum='b'"
1413            "       ref='name' refs='name name' ent='e1' ents='e1 e2'"
1414            "       nm='123' nms='123 abc' />")
1415        elem = doc.documentElement
1416        # We don't want to rely on any specific loader at this point, so
1417        # just make sure we can get to all the names, and that the
1418        # DTD-based namespace is right.  The names can vary by loader
1419        # since each supports a different level of DTD information.
1420        t = elem.schemaType
1421        self.confirm(t.name is None
1422                and t.namespace == xml.dom.EMPTY_NAMESPACE)
1423        names = "id notid text enum ref refs ent ents nm nms".split()
1424        for name in names:
1425            a = elem.getAttributeNode(name)
1426            t = a.schemaType
1427            self.confirm(hasattr(t, "name")
1428                    and t.namespace == xml.dom.EMPTY_NAMESPACE)
1429
1430    def testSetIdAttribute(self):
1431        doc = parseString("<doc a1='v' a2='w'/>")
1432        e = doc.documentElement
1433        a1 = e.getAttributeNode("a1")
1434        a2 = e.getAttributeNode("a2")
1435        self.confirm(doc.getElementById("v") is None
1436                and not a1.isId
1437                and not a2.isId)
1438        e.setIdAttribute("a1")
1439        self.confirm(e.isSameNode(doc.getElementById("v"))
1440                and a1.isId
1441                and not a2.isId)
1442        e.setIdAttribute("a2")
1443        self.confirm(e.isSameNode(doc.getElementById("v"))
1444                and e.isSameNode(doc.getElementById("w"))
1445                and a1.isId
1446                and a2.isId)
1447        # replace the a1 node; the new node should *not* be an ID
1448        a3 = doc.createAttribute("a1")
1449        a3.value = "v"
1450        e.setAttributeNode(a3)
1451        self.confirm(doc.getElementById("v") is None
1452                and e.isSameNode(doc.getElementById("w"))
1453                and not a1.isId
1454                and a2.isId
1455                and not a3.isId)
1456        # renaming an attribute should not affect its ID-ness:
1457        doc.renameNode(a2, xml.dom.EMPTY_NAMESPACE, "an")
1458        self.confirm(e.isSameNode(doc.getElementById("w"))
1459                and a2.isId)
1460
1461    def testSetIdAttributeNS(self):
1462        NS1 = "http://xml.python.org/ns1"
1463        NS2 = "http://xml.python.org/ns2"
1464        doc = parseString("<doc"
1465                          " xmlns:ns1='" + NS1 + "'"
1466                          " xmlns:ns2='" + NS2 + "'"
1467                          " ns1:a1='v' ns2:a2='w'/>")
1468        e = doc.documentElement
1469        a1 = e.getAttributeNodeNS(NS1, "a1")
1470        a2 = e.getAttributeNodeNS(NS2, "a2")
1471        self.confirm(doc.getElementById("v") is None
1472                and not a1.isId
1473                and not a2.isId)
1474        e.setIdAttributeNS(NS1, "a1")
1475        self.confirm(e.isSameNode(doc.getElementById("v"))
1476                and a1.isId
1477                and not a2.isId)
1478        e.setIdAttributeNS(NS2, "a2")
1479        self.confirm(e.isSameNode(doc.getElementById("v"))
1480                and e.isSameNode(doc.getElementById("w"))
1481                and a1.isId
1482                and a2.isId)
1483        # replace the a1 node; the new node should *not* be an ID
1484        a3 = doc.createAttributeNS(NS1, "a1")
1485        a3.value = "v"
1486        e.setAttributeNode(a3)
1487        self.confirm(e.isSameNode(doc.getElementById("w")))
1488        self.confirm(not a1.isId)
1489        self.confirm(a2.isId)
1490        self.confirm(not a3.isId)
1491        self.confirm(doc.getElementById("v") is None)
1492        # renaming an attribute should not affect its ID-ness:
1493        doc.renameNode(a2, xml.dom.EMPTY_NAMESPACE, "an")
1494        self.confirm(e.isSameNode(doc.getElementById("w"))
1495                and a2.isId)
1496
1497    def testSetIdAttributeNode(self):
1498        NS1 = "http://xml.python.org/ns1"
1499        NS2 = "http://xml.python.org/ns2"
1500        doc = parseString("<doc"
1501                          " xmlns:ns1='" + NS1 + "'"
1502                          " xmlns:ns2='" + NS2 + "'"
1503                          " ns1:a1='v' ns2:a2='w'/>")
1504        e = doc.documentElement
1505        a1 = e.getAttributeNodeNS(NS1, "a1")
1506        a2 = e.getAttributeNodeNS(NS2, "a2")
1507        self.confirm(doc.getElementById("v") is None
1508                and not a1.isId
1509                and not a2.isId)
1510        e.setIdAttributeNode(a1)
1511        self.confirm(e.isSameNode(doc.getElementById("v"))
1512                and a1.isId
1513                and not a2.isId)
1514        e.setIdAttributeNode(a2)
1515        self.confirm(e.isSameNode(doc.getElementById("v"))
1516                and e.isSameNode(doc.getElementById("w"))
1517                and a1.isId
1518                and a2.isId)
1519        # replace the a1 node; the new node should *not* be an ID
1520        a3 = doc.createAttributeNS(NS1, "a1")
1521        a3.value = "v"
1522        e.setAttributeNode(a3)
1523        self.confirm(e.isSameNode(doc.getElementById("w")))
1524        self.confirm(not a1.isId)
1525        self.confirm(a2.isId)
1526        self.confirm(not a3.isId)
1527        self.confirm(doc.getElementById("v") is None)
1528        # renaming an attribute should not affect its ID-ness:
1529        doc.renameNode(a2, xml.dom.EMPTY_NAMESPACE, "an")
1530        self.confirm(e.isSameNode(doc.getElementById("w"))
1531                and a2.isId)
1532
1533    def assert_recursive_equal(self, doc, doc2):
1534        stack = [(doc, doc2)]
1535        while stack:
1536            n1, n2 = stack.pop()
1537            self.assertEqual(n1.nodeType, n2.nodeType)
1538            self.assertEqual(len(n1.childNodes), len(n2.childNodes))
1539            self.assertEqual(n1.nodeName, n2.nodeName)
1540            self.assertFalse(n1.isSameNode(n2))
1541            self.assertFalse(n2.isSameNode(n1))
1542            if n1.nodeType == Node.DOCUMENT_TYPE_NODE:
1543                len(n1.entities)
1544                len(n2.entities)
1545                len(n1.notations)
1546                len(n2.notations)
1547                self.assertEqual(len(n1.entities), len(n2.entities))
1548                self.assertEqual(len(n1.notations), len(n2.notations))
1549                for i in range(len(n1.notations)):
1550                    # XXX this loop body doesn't seem to be executed?
1551                    no1 = n1.notations.item(i)
1552                    no2 = n1.notations.item(i)
1553                    self.assertEqual(no1.name, no2.name)
1554                    self.assertEqual(no1.publicId, no2.publicId)
1555                    self.assertEqual(no1.systemId, no2.systemId)
1556                    stack.append((no1, no2))
1557                for i in range(len(n1.entities)):
1558                    e1 = n1.entities.item(i)
1559                    e2 = n2.entities.item(i)
1560                    self.assertEqual(e1.notationName, e2.notationName)
1561                    self.assertEqual(e1.publicId, e2.publicId)
1562                    self.assertEqual(e1.systemId, e2.systemId)
1563                    stack.append((e1, e2))
1564            if n1.nodeType != Node.DOCUMENT_NODE:
1565                self.assertTrue(n1.ownerDocument.isSameNode(doc))
1566                self.assertTrue(n2.ownerDocument.isSameNode(doc2))
1567            for i in range(len(n1.childNodes)):
1568                stack.append((n1.childNodes[i], n2.childNodes[i]))
1569
1570    def testPickledDocument(self):
1571        doc = parseString(sample)
1572        for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):
1573            s = pickle.dumps(doc, proto)
1574            doc2 = pickle.loads(s)
1575            self.assert_recursive_equal(doc, doc2)
1576
1577    def testDeepcopiedDocument(self):
1578        doc = parseString(sample)
1579        doc2 = copy.deepcopy(doc)
1580        self.assert_recursive_equal(doc, doc2)
1581
1582    def testSerializeCommentNodeWithDoubleHyphen(self):
1583        doc = create_doc_without_doctype()
1584        doc.appendChild(doc.createComment("foo--bar"))
1585        self.assertRaises(ValueError, doc.toxml)
1586
1587
1588    def testEmptyXMLNSValue(self):
1589        doc = parseString("<element xmlns=''>\n"
1590                          "<foo/>\n</element>")
1591        doc2 = parseString(doc.toxml())
1592        self.confirm(doc2.namespaceURI == xml.dom.EMPTY_NAMESPACE)
1593
1594    def testExceptionOnSpacesInXMLNSValue(self):
1595        with self.assertRaisesRegex(ValueError, 'Unsupported syntax'):
1596            parseString('<element xmlns:abc="http:abc.com/de f g/hi/j k"><abc:foo /></element>')
1597
1598    def testDocRemoveChild(self):
1599        doc = parse(tstfile)
1600        title_tag = doc.documentElement.getElementsByTagName("TITLE")[0]
1601        self.assertRaises( xml.dom.NotFoundErr, doc.removeChild, title_tag)
1602        num_children_before = len(doc.childNodes)
1603        doc.removeChild(doc.childNodes[0])
1604        num_children_after = len(doc.childNodes)
1605        self.assertTrue(num_children_after == num_children_before - 1)
1606
1607    def testProcessingInstructionNameError(self):
1608        # wrong variable in .nodeValue property will
1609        # lead to "NameError: name 'data' is not defined"
1610        doc = parse(tstfile)
1611        pi = doc.createProcessingInstruction("y", "z")
1612        pi.nodeValue = "crash"
1613
1614if __name__ == "__main__":
1615    unittest.main()
1616