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