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