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