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