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