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