• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# test for xml.dom.minidom
2
3import copy
4import pickle
5from StringIO import StringIO
6from test.test_support import verbose, run_unittest, findfile
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 = 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 testNormalize(self):
715        doc = parseString("<doc/>")
716        root = doc.documentElement
717        root.appendChild(doc.createTextNode("first"))
718        root.appendChild(doc.createTextNode("second"))
719        self.confirm(len(root.childNodes) == 2
720                and root.childNodes.length == 2,
721                "testNormalize -- preparation")
722        doc.normalize()
723        self.confirm(len(root.childNodes) == 1
724                and root.childNodes.length == 1
725                and root.firstChild is root.lastChild
726                and root.firstChild.data == "firstsecond"
727                , "testNormalize -- result")
728        doc.unlink()
729
730        doc = parseString("<doc/>")
731        root = doc.documentElement
732        root.appendChild(doc.createTextNode(""))
733        doc.normalize()
734        self.confirm(len(root.childNodes) == 0
735                and root.childNodes.length == 0,
736                "testNormalize -- single empty node removed")
737        doc.unlink()
738
739    def testNormalizeCombineAndNextSibling(self):
740        doc = parseString("<doc/>")
741        root = doc.documentElement
742        root.appendChild(doc.createTextNode("first"))
743        root.appendChild(doc.createTextNode("second"))
744        root.appendChild(doc.createElement("i"))
745        self.confirm(len(root.childNodes) == 3
746                and root.childNodes.length == 3,
747                "testNormalizeCombineAndNextSibling -- preparation")
748        doc.normalize()
749        self.confirm(len(root.childNodes) == 2
750                and root.childNodes.length == 2
751                and root.firstChild.data == "firstsecond"
752                and root.firstChild is not root.lastChild
753                and root.firstChild.nextSibling is root.lastChild
754                and root.firstChild.previousSibling is None
755                and root.lastChild.previousSibling is root.firstChild
756                and root.lastChild.nextSibling is None
757                , "testNormalizeCombinedAndNextSibling -- result")
758        doc.unlink()
759
760    def testNormalizeDeleteWithPrevSibling(self):
761        doc = parseString("<doc/>")
762        root = doc.documentElement
763        root.appendChild(doc.createTextNode("first"))
764        root.appendChild(doc.createTextNode(""))
765        self.confirm(len(root.childNodes) == 2
766                and root.childNodes.length == 2,
767                "testNormalizeDeleteWithPrevSibling -- preparation")
768        doc.normalize()
769        self.confirm(len(root.childNodes) == 1
770                and root.childNodes.length == 1
771                and root.firstChild.data == "first"
772                and root.firstChild is root.lastChild
773                and root.firstChild.nextSibling is None
774                and root.firstChild.previousSibling is None
775                , "testNormalizeDeleteWithPrevSibling -- result")
776        doc.unlink()
777
778    def testNormalizeDeleteWithNextSibling(self):
779        doc = parseString("<doc/>")
780        root = doc.documentElement
781        root.appendChild(doc.createTextNode(""))
782        root.appendChild(doc.createTextNode("second"))
783        self.confirm(len(root.childNodes) == 2
784                and root.childNodes.length == 2,
785                "testNormalizeDeleteWithNextSibling -- preparation")
786        doc.normalize()
787        self.confirm(len(root.childNodes) == 1
788                and root.childNodes.length == 1
789                and root.firstChild.data == "second"
790                and root.firstChild is root.lastChild
791                and root.firstChild.nextSibling is None
792                and root.firstChild.previousSibling is None
793                , "testNormalizeDeleteWithNextSibling -- result")
794        doc.unlink()
795
796    def testNormalizeDeleteWithTwoNonTextSiblings(self):
797        doc = parseString("<doc/>")
798        root = doc.documentElement
799        root.appendChild(doc.createElement("i"))
800        root.appendChild(doc.createTextNode(""))
801        root.appendChild(doc.createElement("i"))
802        self.confirm(len(root.childNodes) == 3
803                and root.childNodes.length == 3,
804                "testNormalizeDeleteWithTwoSiblings -- preparation")
805        doc.normalize()
806        self.confirm(len(root.childNodes) == 2
807                and root.childNodes.length == 2
808                and root.firstChild is not root.lastChild
809                and root.firstChild.nextSibling is root.lastChild
810                and root.firstChild.previousSibling is None
811                and root.lastChild.previousSibling is root.firstChild
812                and root.lastChild.nextSibling is None
813                , "testNormalizeDeleteWithTwoSiblings -- result")
814        doc.unlink()
815
816    def testNormalizeDeleteAndCombine(self):
817        doc = parseString("<doc/>")
818        root = doc.documentElement
819        root.appendChild(doc.createTextNode(""))
820        root.appendChild(doc.createTextNode("second"))
821        root.appendChild(doc.createTextNode(""))
822        root.appendChild(doc.createTextNode("fourth"))
823        root.appendChild(doc.createTextNode(""))
824        self.confirm(len(root.childNodes) == 5
825                and root.childNodes.length == 5,
826                "testNormalizeDeleteAndCombine -- preparation")
827        doc.normalize()
828        self.confirm(len(root.childNodes) == 1
829                and root.childNodes.length == 1
830                and root.firstChild is root.lastChild
831                and root.firstChild.data == "secondfourth"
832                and root.firstChild.previousSibling is None
833                and root.firstChild.nextSibling is None
834                , "testNormalizeDeleteAndCombine -- result")
835        doc.unlink()
836
837    def testNormalizeRecursion(self):
838        doc = parseString("<doc>"
839                            "<o>"
840                              "<i/>"
841                              "t"
842                              #
843                              #x
844                            "</o>"
845                            "<o>"
846                              "<o>"
847                                "t2"
848                                #x2
849                              "</o>"
850                              "t3"
851                              #x3
852                            "</o>"
853                            #
854                          "</doc>")
855        root = doc.documentElement
856        root.childNodes[0].appendChild(doc.createTextNode(""))
857        root.childNodes[0].appendChild(doc.createTextNode("x"))
858        root.childNodes[1].childNodes[0].appendChild(doc.createTextNode("x2"))
859        root.childNodes[1].appendChild(doc.createTextNode("x3"))
860        root.appendChild(doc.createTextNode(""))
861        self.confirm(len(root.childNodes) == 3
862                and root.childNodes.length == 3
863                and len(root.childNodes[0].childNodes) == 4
864                and root.childNodes[0].childNodes.length == 4
865                and len(root.childNodes[1].childNodes) == 3
866                and root.childNodes[1].childNodes.length == 3
867                and len(root.childNodes[1].childNodes[0].childNodes) == 2
868                and root.childNodes[1].childNodes[0].childNodes.length == 2
869                , "testNormalize2 -- preparation")
870        doc.normalize()
871        self.confirm(len(root.childNodes) == 2
872                and root.childNodes.length == 2
873                and len(root.childNodes[0].childNodes) == 2
874                and root.childNodes[0].childNodes.length == 2
875                and len(root.childNodes[1].childNodes) == 2
876                and root.childNodes[1].childNodes.length == 2
877                and len(root.childNodes[1].childNodes[0].childNodes) == 1
878                and root.childNodes[1].childNodes[0].childNodes.length == 1
879                , "testNormalize2 -- childNodes lengths")
880        self.confirm(root.childNodes[0].childNodes[1].data == "tx"
881                and root.childNodes[1].childNodes[0].childNodes[0].data == "t2x2"
882                and root.childNodes[1].childNodes[1].data == "t3x3"
883                , "testNormalize2 -- joined text fields")
884        self.confirm(root.childNodes[0].childNodes[1].nextSibling is None
885                and root.childNodes[0].childNodes[1].previousSibling
886                        is root.childNodes[0].childNodes[0]
887                and root.childNodes[0].childNodes[0].previousSibling is None
888                and root.childNodes[0].childNodes[0].nextSibling
889                        is root.childNodes[0].childNodes[1]
890                and root.childNodes[1].childNodes[1].nextSibling is None
891                and root.childNodes[1].childNodes[1].previousSibling
892                        is root.childNodes[1].childNodes[0]
893                and root.childNodes[1].childNodes[0].previousSibling is None
894                and root.childNodes[1].childNodes[0].nextSibling
895                        is root.childNodes[1].childNodes[1]
896                , "testNormalize2 -- sibling pointers")
897        doc.unlink()
898
899
900    def testBug0777884(self):
901        doc = parseString("<o>text</o>")
902        text = doc.documentElement.childNodes[0]
903        self.assertEqual(text.nodeType, Node.TEXT_NODE)
904        # Should run quietly, doing nothing.
905        text.normalize()
906        doc.unlink()
907
908    def testBug1433694(self):
909        doc = parseString("<o><i/>t</o>")
910        node = doc.documentElement
911        node.childNodes[1].nodeValue = ""
912        node.normalize()
913        self.confirm(node.childNodes[-1].nextSibling is None,
914                     "Final child's .nextSibling should be None")
915
916    def testSiblings(self):
917        doc = parseString("<doc><?pi?>text?<elm/></doc>")
918        root = doc.documentElement
919        (pi, text, elm) = root.childNodes
920
921        self.confirm(pi.nextSibling is text and
922                pi.previousSibling is None and
923                text.nextSibling is elm and
924                text.previousSibling is pi and
925                elm.nextSibling is None and
926                elm.previousSibling is text, "testSiblings")
927
928        doc.unlink()
929
930    def testParents(self):
931        doc = parseString(
932            "<doc><elm1><elm2/><elm2><elm3/></elm2></elm1></doc>")
933        root = doc.documentElement
934        elm1 = root.childNodes[0]
935        (elm2a, elm2b) = elm1.childNodes
936        elm3 = elm2b.childNodes[0]
937
938        self.confirm(root.parentNode is doc and
939                elm1.parentNode is root and
940                elm2a.parentNode is elm1 and
941                elm2b.parentNode is elm1 and
942                elm3.parentNode is elm2b, "testParents")
943        doc.unlink()
944
945    def testNodeListItem(self):
946        doc = parseString("<doc><e/><e/></doc>")
947        children = doc.childNodes
948        docelem = children[0]
949        self.confirm(children[0] is children.item(0)
950                and children.item(1) is None
951                and docelem.childNodes.item(0) is docelem.childNodes[0]
952                and docelem.childNodes.item(1) is docelem.childNodes[1]
953                and docelem.childNodes.item(0).childNodes.item(0) is None,
954                "test NodeList.item()")
955        doc.unlink()
956
957    def testSAX2DOM(self):
958        from xml.dom import pulldom
959
960        sax2dom = pulldom.SAX2DOM()
961        sax2dom.startDocument()
962        sax2dom.startElement("doc", {})
963        sax2dom.characters("text")
964        sax2dom.startElement("subelm", {})
965        sax2dom.characters("text")
966        sax2dom.endElement("subelm")
967        sax2dom.characters("text")
968        sax2dom.endElement("doc")
969        sax2dom.endDocument()
970
971        doc = sax2dom.document
972        root = doc.documentElement
973        (text1, elm1, text2) = root.childNodes
974        text3 = elm1.childNodes[0]
975
976        self.confirm(text1.previousSibling is None and
977                text1.nextSibling is elm1 and
978                elm1.previousSibling is text1 and
979                elm1.nextSibling is text2 and
980                text2.previousSibling is elm1 and
981                text2.nextSibling is None and
982                text3.previousSibling is None and
983                text3.nextSibling is None, "testSAX2DOM - siblings")
984
985        self.confirm(root.parentNode is doc and
986                text1.parentNode is root and
987                elm1.parentNode is root and
988                text2.parentNode is root and
989                text3.parentNode is elm1, "testSAX2DOM - parents")
990        doc.unlink()
991
992    def testEncodings(self):
993        doc = parseString('<foo>&#x20ac;</foo>')
994        self.confirm(doc.toxml() == u'<?xml version="1.0" ?><foo>\u20ac</foo>'
995                and doc.toxml('utf-8') ==
996                '<?xml version="1.0" encoding="utf-8"?><foo>\xe2\x82\xac</foo>'
997                and doc.toxml('iso-8859-15') ==
998                '<?xml version="1.0" encoding="iso-8859-15"?><foo>\xa4</foo>',
999                "testEncodings - encoding EURO SIGN")
1000
1001        # Verify that character decoding errors raise exceptions instead
1002        # of crashing
1003        self.assertRaises(UnicodeDecodeError, parseString,
1004                '<fran\xe7ais>Comment \xe7a va ? Tr\xe8s bien ?</fran\xe7ais>')
1005
1006        doc.unlink()
1007
1008    class UserDataHandler:
1009        called = 0
1010        def handle(self, operation, key, data, src, dst):
1011            dst.setUserData(key, data + 1, self)
1012            src.setUserData(key, None, None)
1013            self.called = 1
1014
1015    def testUserData(self):
1016        dom = Document()
1017        n = dom.createElement('e')
1018        self.confirm(n.getUserData("foo") is None)
1019        n.setUserData("foo", None, None)
1020        self.confirm(n.getUserData("foo") is None)
1021        n.setUserData("foo", 12, 12)
1022        n.setUserData("bar", 13, 13)
1023        self.confirm(n.getUserData("foo") == 12)
1024        self.confirm(n.getUserData("bar") == 13)
1025        n.setUserData("foo", None, None)
1026        self.confirm(n.getUserData("foo") is None)
1027        self.confirm(n.getUserData("bar") == 13)
1028
1029        handler = self.UserDataHandler()
1030        n.setUserData("bar", 12, handler)
1031        c = n.cloneNode(1)
1032        self.confirm(handler.called
1033                and n.getUserData("bar") is None
1034                and c.getUserData("bar") == 13)
1035        n.unlink()
1036        c.unlink()
1037        dom.unlink()
1038
1039    def checkRenameNodeSharedConstraints(self, doc, node):
1040        # Make sure illegal NS usage is detected:
1041        self.assertRaises(xml.dom.NamespaceErr, doc.renameNode, node,
1042                          "http://xml.python.org/ns", "xmlns:foo")
1043        doc2 = parseString("<doc/>")
1044        self.assertRaises(xml.dom.WrongDocumentErr, doc2.renameNode, node,
1045                          xml.dom.EMPTY_NAMESPACE, "foo")
1046
1047    def testRenameAttribute(self):
1048        doc = parseString("<doc a='v'/>")
1049        elem = doc.documentElement
1050        attrmap = elem.attributes
1051        attr = elem.attributes['a']
1052
1053        # Simple renaming
1054        attr = doc.renameNode(attr, xml.dom.EMPTY_NAMESPACE, "b")
1055        self.confirm(attr.name == "b"
1056                and attr.nodeName == "b"
1057                and attr.localName is None
1058                and attr.namespaceURI == xml.dom.EMPTY_NAMESPACE
1059                and attr.prefix is None
1060                and attr.value == "v"
1061                and elem.getAttributeNode("a") is None
1062                and elem.getAttributeNode("b").isSameNode(attr)
1063                and attrmap["b"].isSameNode(attr)
1064                and attr.ownerDocument.isSameNode(doc)
1065                and attr.ownerElement.isSameNode(elem))
1066
1067        # Rename to have a namespace, no prefix
1068        attr = doc.renameNode(attr, "http://xml.python.org/ns", "c")
1069        self.confirm(attr.name == "c"
1070                and attr.nodeName == "c"
1071                and attr.localName == "c"
1072                and attr.namespaceURI == "http://xml.python.org/ns"
1073                and attr.prefix is None
1074                and attr.value == "v"
1075                and elem.getAttributeNode("a") is None
1076                and elem.getAttributeNode("b") is None
1077                and elem.getAttributeNode("c").isSameNode(attr)
1078                and elem.getAttributeNodeNS(
1079                    "http://xml.python.org/ns", "c").isSameNode(attr)
1080                and attrmap["c"].isSameNode(attr)
1081                and attrmap[("http://xml.python.org/ns", "c")].isSameNode(attr))
1082
1083        # Rename to have a namespace, with prefix
1084        attr = doc.renameNode(attr, "http://xml.python.org/ns2", "p:d")
1085        self.confirm(attr.name == "p:d"
1086                and attr.nodeName == "p:d"
1087                and attr.localName == "d"
1088                and attr.namespaceURI == "http://xml.python.org/ns2"
1089                and attr.prefix == "p"
1090                and attr.value == "v"
1091                and elem.getAttributeNode("a") is None
1092                and elem.getAttributeNode("b") is None
1093                and elem.getAttributeNode("c") is None
1094                and elem.getAttributeNodeNS(
1095                    "http://xml.python.org/ns", "c") is None
1096                and elem.getAttributeNode("p:d").isSameNode(attr)
1097                and elem.getAttributeNodeNS(
1098                    "http://xml.python.org/ns2", "d").isSameNode(attr)
1099                and attrmap["p:d"].isSameNode(attr)
1100                and attrmap[("http://xml.python.org/ns2", "d")].isSameNode(attr))
1101
1102        # Rename back to a simple non-NS node
1103        attr = doc.renameNode(attr, xml.dom.EMPTY_NAMESPACE, "e")
1104        self.confirm(attr.name == "e"
1105                and attr.nodeName == "e"
1106                and attr.localName is None
1107                and attr.namespaceURI == xml.dom.EMPTY_NAMESPACE
1108                and attr.prefix is None
1109                and attr.value == "v"
1110                and elem.getAttributeNode("a") is None
1111                and elem.getAttributeNode("b") is None
1112                and elem.getAttributeNode("c") is None
1113                and elem.getAttributeNode("p:d") is None
1114                and elem.getAttributeNodeNS(
1115                    "http://xml.python.org/ns", "c") is None
1116                and elem.getAttributeNode("e").isSameNode(attr)
1117                and attrmap["e"].isSameNode(attr))
1118
1119        self.assertRaises(xml.dom.NamespaceErr, doc.renameNode, attr,
1120                          "http://xml.python.org/ns", "xmlns")
1121        self.checkRenameNodeSharedConstraints(doc, attr)
1122        doc.unlink()
1123
1124    def testRenameElement(self):
1125        doc = parseString("<doc/>")
1126        elem = doc.documentElement
1127
1128        # Simple renaming
1129        elem = doc.renameNode(elem, xml.dom.EMPTY_NAMESPACE, "a")
1130        self.confirm(elem.tagName == "a"
1131                and elem.nodeName == "a"
1132                and elem.localName is None
1133                and elem.namespaceURI == xml.dom.EMPTY_NAMESPACE
1134                and elem.prefix is None
1135                and elem.ownerDocument.isSameNode(doc))
1136
1137        # Rename to have a namespace, no prefix
1138        elem = doc.renameNode(elem, "http://xml.python.org/ns", "b")
1139        self.confirm(elem.tagName == "b"
1140                and elem.nodeName == "b"
1141                and elem.localName == "b"
1142                and elem.namespaceURI == "http://xml.python.org/ns"
1143                and elem.prefix is None
1144                and elem.ownerDocument.isSameNode(doc))
1145
1146        # Rename to have a namespace, with prefix
1147        elem = doc.renameNode(elem, "http://xml.python.org/ns2", "p:c")
1148        self.confirm(elem.tagName == "p:c"
1149                and elem.nodeName == "p:c"
1150                and elem.localName == "c"
1151                and elem.namespaceURI == "http://xml.python.org/ns2"
1152                and elem.prefix == "p"
1153                and elem.ownerDocument.isSameNode(doc))
1154
1155        # Rename back to a simple non-NS node
1156        elem = doc.renameNode(elem, xml.dom.EMPTY_NAMESPACE, "d")
1157        self.confirm(elem.tagName == "d"
1158                and elem.nodeName == "d"
1159                and elem.localName is None
1160                and elem.namespaceURI == xml.dom.EMPTY_NAMESPACE
1161                and elem.prefix is None
1162                and elem.ownerDocument.isSameNode(doc))
1163
1164        self.checkRenameNodeSharedConstraints(doc, elem)
1165        doc.unlink()
1166
1167    def testRenameOther(self):
1168        # We have to create a comment node explicitly since not all DOM
1169        # builders used with minidom add comments to the DOM.
1170        doc = xml.dom.minidom.getDOMImplementation().createDocument(
1171            xml.dom.EMPTY_NAMESPACE, "e", None)
1172        node = doc.createComment("comment")
1173        self.assertRaises(xml.dom.NotSupportedErr, doc.renameNode, node,
1174                          xml.dom.EMPTY_NAMESPACE, "foo")
1175        doc.unlink()
1176
1177    def testWholeText(self):
1178        doc = parseString("<doc>a</doc>")
1179        elem = doc.documentElement
1180        text = elem.childNodes[0]
1181        self.assertEqual(text.nodeType, Node.TEXT_NODE)
1182
1183        self.checkWholeText(text, "a")
1184        elem.appendChild(doc.createTextNode("b"))
1185        self.checkWholeText(text, "ab")
1186        elem.insertBefore(doc.createCDATASection("c"), text)
1187        self.checkWholeText(text, "cab")
1188
1189        # make sure we don't cross other nodes
1190        splitter = doc.createComment("comment")
1191        elem.appendChild(splitter)
1192        text2 = doc.createTextNode("d")
1193        elem.appendChild(text2)
1194        self.checkWholeText(text, "cab")
1195        self.checkWholeText(text2, "d")
1196
1197        x = doc.createElement("x")
1198        elem.replaceChild(x, splitter)
1199        splitter = x
1200        self.checkWholeText(text, "cab")
1201        self.checkWholeText(text2, "d")
1202
1203        x = doc.createProcessingInstruction("y", "z")
1204        elem.replaceChild(x, splitter)
1205        splitter = x
1206        self.checkWholeText(text, "cab")
1207        self.checkWholeText(text2, "d")
1208
1209        elem.removeChild(splitter)
1210        self.checkWholeText(text, "cabd")
1211        self.checkWholeText(text2, "cabd")
1212
1213    def testPatch1094164(self):
1214        doc = parseString("<doc><e/></doc>")
1215        elem = doc.documentElement
1216        e = elem.firstChild
1217        self.confirm(e.parentNode is elem, "Before replaceChild()")
1218        # Check that replacing a child with itself leaves the tree unchanged
1219        elem.replaceChild(e, e)
1220        self.confirm(e.parentNode is elem, "After replaceChild()")
1221
1222    def testReplaceWholeText(self):
1223        def setup():
1224            doc = parseString("<doc>a<e/>d</doc>")
1225            elem = doc.documentElement
1226            text1 = elem.firstChild
1227            text2 = elem.lastChild
1228            splitter = text1.nextSibling
1229            elem.insertBefore(doc.createTextNode("b"), splitter)
1230            elem.insertBefore(doc.createCDATASection("c"), text1)
1231            return doc, elem, text1, splitter, text2
1232
1233        doc, elem, text1, splitter, text2 = setup()
1234        text = text1.replaceWholeText("new content")
1235        self.checkWholeText(text, "new content")
1236        self.checkWholeText(text2, "d")
1237        self.confirm(len(elem.childNodes) == 3)
1238
1239        doc, elem, text1, splitter, text2 = setup()
1240        text = text2.replaceWholeText("new content")
1241        self.checkWholeText(text, "new content")
1242        self.checkWholeText(text1, "cab")
1243        self.confirm(len(elem.childNodes) == 5)
1244
1245        doc, elem, text1, splitter, text2 = setup()
1246        text = text1.replaceWholeText("")
1247        self.checkWholeText(text2, "d")
1248        self.confirm(text is None
1249                and len(elem.childNodes) == 2)
1250
1251    def testSchemaType(self):
1252        doc = parseString(
1253            "<!DOCTYPE doc [\n"
1254            "  <!ENTITY e1 SYSTEM 'http://xml.python.org/e1'>\n"
1255            "  <!ENTITY e2 SYSTEM 'http://xml.python.org/e2'>\n"
1256            "  <!ATTLIST doc id   ID       #IMPLIED \n"
1257            "                ref  IDREF    #IMPLIED \n"
1258            "                refs IDREFS   #IMPLIED \n"
1259            "                enum (a|b)    #IMPLIED \n"
1260            "                ent  ENTITY   #IMPLIED \n"
1261            "                ents ENTITIES #IMPLIED \n"
1262            "                nm   NMTOKEN  #IMPLIED \n"
1263            "                nms  NMTOKENS #IMPLIED \n"
1264            "                text CDATA    #IMPLIED \n"
1265            "    >\n"
1266            "]><doc id='name' notid='name' text='splat!' enum='b'"
1267            "       ref='name' refs='name name' ent='e1' ents='e1 e2'"
1268            "       nm='123' nms='123 abc' />")
1269        elem = doc.documentElement
1270        # We don't want to rely on any specific loader at this point, so
1271        # just make sure we can get to all the names, and that the
1272        # DTD-based namespace is right.  The names can vary by loader
1273        # since each supports a different level of DTD information.
1274        t = elem.schemaType
1275        self.confirm(t.name is None
1276                and t.namespace == xml.dom.EMPTY_NAMESPACE)
1277        names = "id notid text enum ref refs ent ents nm nms".split()
1278        for name in names:
1279            a = elem.getAttributeNode(name)
1280            t = a.schemaType
1281            self.confirm(hasattr(t, "name")
1282                    and t.namespace == xml.dom.EMPTY_NAMESPACE)
1283
1284    def testSetIdAttribute(self):
1285        doc = parseString("<doc a1='v' a2='w'/>")
1286        e = doc.documentElement
1287        a1 = e.getAttributeNode("a1")
1288        a2 = e.getAttributeNode("a2")
1289        self.confirm(doc.getElementById("v") is None
1290                and not a1.isId
1291                and not a2.isId)
1292        e.setIdAttribute("a1")
1293        self.confirm(e.isSameNode(doc.getElementById("v"))
1294                and a1.isId
1295                and not a2.isId)
1296        e.setIdAttribute("a2")
1297        self.confirm(e.isSameNode(doc.getElementById("v"))
1298                and e.isSameNode(doc.getElementById("w"))
1299                and a1.isId
1300                and a2.isId)
1301        # replace the a1 node; the new node should *not* be an ID
1302        a3 = doc.createAttribute("a1")
1303        a3.value = "v"
1304        e.setAttributeNode(a3)
1305        self.confirm(doc.getElementById("v") is None
1306                and e.isSameNode(doc.getElementById("w"))
1307                and not a1.isId
1308                and a2.isId
1309                and not a3.isId)
1310        # renaming an attribute should not affect its ID-ness:
1311        doc.renameNode(a2, xml.dom.EMPTY_NAMESPACE, "an")
1312        self.confirm(e.isSameNode(doc.getElementById("w"))
1313                and a2.isId)
1314
1315    def testSetIdAttributeNS(self):
1316        NS1 = "http://xml.python.org/ns1"
1317        NS2 = "http://xml.python.org/ns2"
1318        doc = parseString("<doc"
1319                          " xmlns:ns1='" + NS1 + "'"
1320                          " xmlns:ns2='" + NS2 + "'"
1321                          " ns1:a1='v' ns2:a2='w'/>")
1322        e = doc.documentElement
1323        a1 = e.getAttributeNodeNS(NS1, "a1")
1324        a2 = e.getAttributeNodeNS(NS2, "a2")
1325        self.confirm(doc.getElementById("v") is None
1326                and not a1.isId
1327                and not a2.isId)
1328        e.setIdAttributeNS(NS1, "a1")
1329        self.confirm(e.isSameNode(doc.getElementById("v"))
1330                and a1.isId
1331                and not a2.isId)
1332        e.setIdAttributeNS(NS2, "a2")
1333        self.confirm(e.isSameNode(doc.getElementById("v"))
1334                and e.isSameNode(doc.getElementById("w"))
1335                and a1.isId
1336                and a2.isId)
1337        # replace the a1 node; the new node should *not* be an ID
1338        a3 = doc.createAttributeNS(NS1, "a1")
1339        a3.value = "v"
1340        e.setAttributeNode(a3)
1341        self.confirm(e.isSameNode(doc.getElementById("w")))
1342        self.confirm(not a1.isId)
1343        self.confirm(a2.isId)
1344        self.confirm(not a3.isId)
1345        self.confirm(doc.getElementById("v") is None)
1346        # renaming an attribute should not affect its ID-ness:
1347        doc.renameNode(a2, xml.dom.EMPTY_NAMESPACE, "an")
1348        self.confirm(e.isSameNode(doc.getElementById("w"))
1349                and a2.isId)
1350
1351    def testSetIdAttributeNode(self):
1352        NS1 = "http://xml.python.org/ns1"
1353        NS2 = "http://xml.python.org/ns2"
1354        doc = parseString("<doc"
1355                          " xmlns:ns1='" + NS1 + "'"
1356                          " xmlns:ns2='" + NS2 + "'"
1357                          " ns1:a1='v' ns2:a2='w'/>")
1358        e = doc.documentElement
1359        a1 = e.getAttributeNodeNS(NS1, "a1")
1360        a2 = e.getAttributeNodeNS(NS2, "a2")
1361        self.confirm(doc.getElementById("v") is None
1362                and not a1.isId
1363                and not a2.isId)
1364        e.setIdAttributeNode(a1)
1365        self.confirm(e.isSameNode(doc.getElementById("v"))
1366                and a1.isId
1367                and not a2.isId)
1368        e.setIdAttributeNode(a2)
1369        self.confirm(e.isSameNode(doc.getElementById("v"))
1370                and e.isSameNode(doc.getElementById("w"))
1371                and a1.isId
1372                and a2.isId)
1373        # replace the a1 node; the new node should *not* be an ID
1374        a3 = doc.createAttributeNS(NS1, "a1")
1375        a3.value = "v"
1376        e.setAttributeNode(a3)
1377        self.confirm(e.isSameNode(doc.getElementById("w")))
1378        self.confirm(not a1.isId)
1379        self.confirm(a2.isId)
1380        self.confirm(not a3.isId)
1381        self.confirm(doc.getElementById("v") is None)
1382        # renaming an attribute should not affect its ID-ness:
1383        doc.renameNode(a2, xml.dom.EMPTY_NAMESPACE, "an")
1384        self.confirm(e.isSameNode(doc.getElementById("w"))
1385                and a2.isId)
1386
1387    def assert_recursive_equal(self, doc, doc2):
1388        stack = [(doc, doc2)]
1389        while stack:
1390            n1, n2 = stack.pop()
1391            self.assertEqual(n1.nodeType, n2.nodeType)
1392            self.assertEqual(len(n1.childNodes), len(n2.childNodes))
1393            self.assertEqual(n1.nodeName, n2.nodeName)
1394            self.assertFalse(n1.isSameNode(n2))
1395            self.assertFalse(n2.isSameNode(n1))
1396            if n1.nodeType == Node.DOCUMENT_TYPE_NODE:
1397                len(n1.entities)
1398                len(n2.entities)
1399                len(n1.notations)
1400                len(n2.notations)
1401                self.assertEqual(len(n1.entities), len(n2.entities))
1402                self.assertEqual(len(n1.notations), len(n2.notations))
1403                for i in range(len(n1.notations)):
1404                    # XXX this loop body doesn't seem to be executed?
1405                    no1 = n1.notations.item(i)
1406                    no2 = n1.notations.item(i)
1407                    self.assertEqual(no1.name, no2.name)
1408                    self.assertEqual(no1.publicId, no2.publicId)
1409                    self.assertEqual(no1.systemId, no2.systemId)
1410                    stack.append((no1, no2))
1411                for i in range(len(n1.entities)):
1412                    e1 = n1.entities.item(i)
1413                    e2 = n2.entities.item(i)
1414                    self.assertEqual(e1.notationName, e2.notationName)
1415                    self.assertEqual(e1.publicId, e2.publicId)
1416                    self.assertEqual(e1.systemId, e2.systemId)
1417                    stack.append((e1, e2))
1418            if n1.nodeType != Node.DOCUMENT_NODE:
1419                self.assertTrue(n1.ownerDocument.isSameNode(doc))
1420                self.assertTrue(n2.ownerDocument.isSameNode(doc2))
1421            for i in range(len(n1.childNodes)):
1422                stack.append((n1.childNodes[i], n2.childNodes[i]))
1423
1424    def testPickledDocument(self):
1425        doc = parseString(sample)
1426        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1427            s = pickle.dumps(doc, proto)
1428            doc2 = pickle.loads(s)
1429            self.assert_recursive_equal(doc, doc2)
1430
1431    def testDeepcopiedDocument(self):
1432        doc = parseString(sample)
1433        doc2 = copy.deepcopy(doc)
1434        self.assert_recursive_equal(doc, doc2)
1435
1436    def testSerializeCommentNodeWithDoubleHyphen(self):
1437        doc = create_doc_without_doctype()
1438        doc.appendChild(doc.createComment("foo--bar"))
1439        self.assertRaises(ValueError, doc.toxml)
1440
1441    def testEmptyXMLNSValue(self):
1442        doc = parseString("<element xmlns=''>\n"
1443                          "<foo/>\n</element>")
1444        doc2 = parseString(doc.toxml())
1445        self.confirm(doc2.namespaceURI == xml.dom.EMPTY_NAMESPACE)
1446
1447
1448def test_main():
1449    run_unittest(MinidomTest)
1450
1451if __name__ == "__main__":
1452    test_main()
1453