Merge PyXML 1.11-1.26:
Re-arrange the imports into "Python normal form."
Add test of the getUserData() / setUserData() methods, including the
NODE_CLONED callback.
Added support for renameNode() and getInterface().
Changed Node.unlink() so an unlinked node is not rendered completely
unusable by setting childNodes to None.
Element.removeAttributeNode() is slightly less destructive.
Added test for the wholeText attribute.
Added a test for Text.replaceWholeText().
Fixed to properly create Element in test of user data
Rename a local variable so it makes sense when viewed as a sequence.
Unlink a few documents when we're done with them.
Added tests to define the behavior of the cloneNode() and importNode()
mehods, especially in the "difficult" cases of document and
document-type nodes.
Filled in a few more of the other cloneNode() tests.
NodeList.item() does not exist before Python 2.2, since it requires being
able to create subtypes of list. Use the subscript syntax instead.
Added a test that minidom documents can be pickled and unpickled.
Closes SF bug #609641.
Fill in an empty test, making sure we get the whitespace right for the
data attribute of a processing instruction.
Added checks for a few more invariants for processing instructions.
testProcessingInstruction(): The length attribute of the NodeList
interface is not implemented for Python 2.0, 2.1, so only use
len() to test the length.
testSchemaType(): New test, testing just the minimum of schemaType
support; this is different from the test_xmlbuilder version of the
test since it doesn't rely on using a specific builder, and the
builders support different levels of DTD support.
Add tests for the removeNamedItem() and removeNamedItemNS() methods of
the NamedNodeMap instances found on Element nodes.
These do not pass; the fix will be committed shortly.
Added support for the DOM Level 3 (draft) Element.setIdAttribute*() methods.
Do more to avoid creating new Attr nodes, so that attributes do not lose
their ID-ness when set using setIdAttribute*().
diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py
index 0791bbf..c39c616 100644
--- a/Lib/test/test_minidom.py
+++ b/Lib/test/test_minidom.py
@@ -23,8 +23,6 @@
print "Failed " + testname
raise Exception
-Node._debug = 1
-
def testParseFromFile():
from StringIO import StringIO
dom = parse(StringIO(open(tstfile).read()))
@@ -283,7 +281,8 @@
confirm(len(child.attributes) == 1)
node = child.getAttributeNode("spam")
child.removeAttributeNode(node)
- confirm(len(child.attributes) == 0)
+ confirm(len(child.attributes) == 0
+ and child.getAttributeNode("spam") is None)
dom.unlink()
@@ -293,13 +292,36 @@
el.setAttribute("spam", "jam")
confirm(len(el.attributes) == 1)
el.setAttribute("spam", "bam")
- confirm(len(el.attributes) == 1)
+ # Set this attribute to be an ID and make sure that doesn't change
+ # when changing the value:
+ el.setIdAttribute("spam")
+ confirm(len(el.attributes) == 1
+ and el.attributes["spam"].value == "bam"
+ and el.attributes["spam"].nodeValue == "bam"
+ and el.getAttribute("spam") == "bam"
+ and el.getAttributeNode("spam").isId)
el.attributes["spam"] = "ham"
- confirm(len(el.attributes) == 1)
+ confirm(len(el.attributes) == 1
+ and el.attributes["spam"].value == "ham"
+ and el.attributes["spam"].nodeValue == "ham"
+ and el.getAttribute("spam") == "ham"
+ and el.attributes["spam"].isId)
el.setAttribute("spam2", "bam")
- confirm(len(el.attributes) == 2)
- el.attributes[ "spam2"] = "bam2"
- confirm(len(el.attributes) == 2)
+ confirm(len(el.attributes) == 2
+ and el.attributes["spam"].value == "ham"
+ and el.attributes["spam"].nodeValue == "ham"
+ and el.getAttribute("spam") == "ham"
+ and el.attributes["spam2"].value == "bam"
+ and el.attributes["spam2"].nodeValue == "bam"
+ and el.getAttribute("spam2") == "bam")
+ el.attributes["spam2"] = "bam2"
+ confirm(len(el.attributes) == 2
+ and el.attributes["spam"].value == "ham"
+ and el.attributes["spam"].nodeValue == "ham"
+ and el.getAttribute("spam") == "ham"
+ and el.attributes["spam2"].value == "bam2"
+ and el.attributes["spam2"].nodeValue == "bam2"
+ and el.getAttribute("spam2") == "bam2")
dom.unlink()
def testGetAttrList():
@@ -316,15 +338,39 @@
def testGetAttributeNode(): pass
def testGetElementsByTagNameNS():
- d="""<foo xmlns:minidom="http://pyxml.sf.net/minidom">
+ d="""<foo xmlns:minidom='http://pyxml.sf.net/minidom'>
<minidom:myelem/>
</foo>"""
dom = parseString(d)
- elem = dom.getElementsByTagNameNS("http://pyxml.sf.net/minidom","myelem")
- confirm(len(elem) == 1)
+ elems = dom.getElementsByTagNameNS("http://pyxml.sf.net/minidom", "myelem")
+ confirm(len(elems) == 1
+ and elems[0].namespaceURI == "http://pyxml.sf.net/minidom"
+ and elems[0].localName == "myelem"
+ and elems[0].prefix == "minidom"
+ and elems[0].tagName == "minidom:myelem"
+ and elems[0].nodeName == "minidom:myelem")
dom.unlink()
-def testGetEmptyNodeListFromElementsByTagNameNS(): pass
+def get_empty_nodelist_from_elements_by_tagName_ns_helper(doc, nsuri, lname):
+ nodelist = doc.getElementsByTagNameNS(nsuri, lname)
+ confirm(len(nodelist) == 0)
+
+def testGetEmptyNodeListFromElementsByTagNameNS():
+ doc = parseString('<doc/>')
+ get_empty_nodelist_from_elements_by_tagName_ns_helper(
+ doc, 'http://xml.python.org/namespaces/a', 'localname')
+ get_empty_nodelist_from_elements_by_tagName_ns_helper(
+ doc, '*', 'splat')
+ get_empty_nodelist_from_elements_by_tagName_ns_helper(
+ doc, 'http://xml.python.org/namespaces/a', '*')
+
+ doc = parseString('<doc xmlns="http://xml.python.org/splat"><e/></doc>')
+ get_empty_nodelist_from_elements_by_tagName_ns_helper(
+ doc, "http://xml.python.org/splat", "not-there")
+ get_empty_nodelist_from_elements_by_tagName_ns_helper(
+ doc, "*", "not-there")
+ get_empty_nodelist_from_elements_by_tagName_ns_helper(
+ doc, "http://somewhere.else.net/not-there", "e")
def testElementReprAndStr():
dom = Document()
@@ -370,7 +416,20 @@
dom.unlink()
confirm(str == domstr)
-def testProcessingInstruction(): pass
+def testProcessingInstruction():
+ dom = parseString('<e><?mypi \t\n data \t\n ?></e>')
+ pi = dom.documentElement.firstChild
+ confirm(pi.target == "mypi"
+ and pi.data == "data \t\n "
+ and pi.nodeName == "mypi"
+ and pi.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+ and pi.attributes is None
+ and not pi.hasChildNodes()
+ and len(pi.childNodes) == 0
+ and pi.firstChild is None
+ and pi.lastChild is None
+ and pi.localName is None
+ and pi.namespaceURI == xml.dom.EMPTY_NAMESPACE)
def testProcessingInstructionRepr(): pass
@@ -413,6 +472,30 @@
def testAttrListKeysNS(): pass
+def testRemoveNamedItem():
+ doc = parseString("<doc a=''/>")
+ e = doc.documentElement
+ attrs = e.attributes
+ a1 = e.getAttributeNode("a")
+ a2 = attrs.removeNamedItem("a")
+ confirm(a1.isSameNode(a2))
+ try:
+ attrs.removeNamedItem("a")
+ except xml.dom.NotFoundErr:
+ pass
+
+def testRemoveNamedItemNS():
+ doc = parseString("<doc xmlns:a='http://xml.python.org/' a:b=''/>")
+ e = doc.documentElement
+ attrs = e.attributes
+ a1 = e.getAttributeNodeNS("http://xml.python.org/", "b")
+ a2 = attrs.removeNamedItemNS("http://xml.python.org/", "b")
+ confirm(a1.isSameNode(a2))
+ try:
+ attrs.removeNamedItemNS("http://xml.python.org/", "b")
+ except xml.dom.NotFoundErr:
+ pass
+
def testAttrListValues(): pass
def testAttrListLength(): pass
@@ -489,18 +572,205 @@
confirm(a2.ownerElement is e2,
"clone of attribute node correctly owned")
+def testCloneDocumentShallow():
+ doc = parseString("<?xml version='1.0'?>\n"
+ "<!-- comment -->"
+ "<!DOCTYPE doc [\n"
+ "<!NOTATION notation SYSTEM 'http://xml.python.org/'>\n"
+ "]>\n"
+ "<doc attr='value'/>")
+ doc2 = doc.cloneNode(0)
+ confirm(doc2 is None,
+ "testCloneDocumentShallow:"
+ " shallow cloning of documents makes no sense!")
-def testCloneDocumentShallow(): pass
+def testCloneDocumentDeep():
+ doc = parseString("<?xml version='1.0'?>\n"
+ "<!-- comment -->"
+ "<!DOCTYPE doc [\n"
+ "<!NOTATION notation SYSTEM 'http://xml.python.org/'>\n"
+ "]>\n"
+ "<doc attr='value'/>")
+ doc2 = doc.cloneNode(1)
+ confirm(not (doc.isSameNode(doc2) or doc2.isSameNode(doc)),
+ "testCloneDocumentDeep: document objects not distinct")
+ confirm(len(doc.childNodes) == len(doc2.childNodes),
+ "testCloneDocumentDeep: wrong number of Document children")
+ confirm(doc2.documentElement.nodeType == Node.ELEMENT_NODE,
+ "testCloneDocumentDeep: documentElement not an ELEMENT_NODE")
+ confirm(doc2.documentElement.ownerDocument.isSameNode(doc2),
+ "testCloneDocumentDeep: documentElement owner is not new document")
+ confirm(not doc.documentElement.isSameNode(doc2.documentElement),
+ "testCloneDocumentDeep: documentElement should not be shared")
+ if doc.doctype is not None:
+ # check the doctype iff the original DOM maintained it
+ confirm(doc2.doctype.nodeType == Node.DOCUMENT_TYPE_NODE,
+ "testCloneDocumentDeep: doctype not a DOCUMENT_TYPE_NODE")
+ confirm(doc2.doctype.ownerDocument.isSameNode(doc2))
+ confirm(not doc.doctype.isSameNode(doc2.doctype))
-def testCloneDocumentDeep(): pass
+def testCloneDocumentTypeDeepOk():
+ doctype = create_nonempty_doctype()
+ clone = doctype.cloneNode(1)
+ confirm(clone is not None
+ and clone.nodeName == doctype.nodeName
+ and clone.name == doctype.name
+ and clone.publicId == doctype.publicId
+ and clone.systemId == doctype.systemId
+ and len(clone.entities) == len(doctype.entities)
+ and clone.entities.item(len(clone.entities)) is None
+ and len(clone.notations) == len(doctype.notations)
+ and clone.notations.item(len(clone.notations)) is None
+ and len(clone.childNodes) == 0)
+ for i in range(len(doctype.entities)):
+ se = doctype.entities.item(i)
+ ce = clone.entities.item(i)
+ confirm((not se.isSameNode(ce))
+ and (not ce.isSameNode(se))
+ and ce.nodeName == se.nodeName
+ and ce.notationName == se.notationName
+ and ce.publicId == se.publicId
+ and ce.systemId == se.systemId
+ and ce.encoding == se.encoding
+ and ce.actualEncoding == se.actualEncoding
+ and ce.version == se.version)
+ for i in range(len(doctype.notations)):
+ sn = doctype.notations.item(i)
+ cn = clone.notations.item(i)
+ confirm((not sn.isSameNode(cn))
+ and (not cn.isSameNode(sn))
+ and cn.nodeName == sn.nodeName
+ and cn.publicId == sn.publicId
+ and cn.systemId == sn.systemId)
-def testCloneAttributeShallow(): pass
+def testCloneDocumentTypeDeepNotOk():
+ doc = create_doc_with_doctype()
+ clone = doc.doctype.cloneNode(1)
+ confirm(clone is None, "testCloneDocumentTypeDeepNotOk")
-def testCloneAttributeDeep(): pass
+def testCloneDocumentTypeShallowOk():
+ doctype = create_nonempty_doctype()
+ clone = doctype.cloneNode(0)
+ confirm(clone is not None
+ and clone.nodeName == doctype.nodeName
+ and clone.name == doctype.name
+ and clone.publicId == doctype.publicId
+ and clone.systemId == doctype.systemId
+ and len(clone.entities) == 0
+ and clone.entities.item(0) is None
+ and len(clone.notations) == 0
+ and clone.notations.item(0) is None
+ and len(clone.childNodes) == 0)
-def testClonePIShallow(): pass
+def testCloneDocumentTypeShallowNotOk():
+ doc = create_doc_with_doctype()
+ clone = doc.doctype.cloneNode(0)
+ confirm(clone is None, "testCloneDocumentTypeShallowNotOk")
-def testClonePIDeep(): pass
+def check_import_document(deep, testName):
+ doc1 = parseString("<doc/>")
+ doc2 = parseString("<doc/>")
+ try:
+ doc1.importNode(doc2, deep)
+ except xml.dom.NotSupportedErr:
+ pass
+ else:
+ raise Exception(testName +
+ ": expected NotSupportedErr when importing a document")
+
+def testImportDocumentShallow():
+ check_import_document(0, "testImportDocumentShallow")
+
+def testImportDocumentDeep():
+ check_import_document(1, "testImportDocumentDeep")
+
+# The tests of DocumentType importing use these helpers to construct
+# the documents to work with, since not all DOM builders actually
+# create the DocumentType nodes.
+
+def create_doc_without_doctype(doctype=None):
+ return getDOMImplementation().createDocument(None, "doc", doctype)
+
+def create_nonempty_doctype():
+ doctype = getDOMImplementation().createDocumentType("doc", None, None)
+ doctype.entities._seq = []
+ doctype.notations._seq = []
+ notation = xml.dom.minidom.Notation("my-notation", None,
+ "http://xml.python.org/notations/my")
+ doctype.notations._seq.append(notation)
+ entity = xml.dom.minidom.Entity("my-entity", None,
+ "http://xml.python.org/entities/my",
+ "my-notation")
+ entity.version = "1.0"
+ entity.encoding = "utf-8"
+ entity.actualEncoding = "us-ascii"
+ doctype.entities._seq.append(entity)
+ return doctype
+
+def create_doc_with_doctype():
+ doctype = create_nonempty_doctype()
+ doc = create_doc_without_doctype(doctype)
+ doctype.entities.item(0).ownerDocument = doc
+ doctype.notations.item(0).ownerDocument = doc
+ return doc
+
+def testImportDocumentTypeShallow():
+ src = create_doc_with_doctype()
+ target = create_doc_without_doctype()
+ try:
+ imported = target.importNode(src.doctype, 0)
+ except xml.dom.NotSupportedErr:
+ pass
+ else:
+ raise Exception(
+ "testImportDocumentTypeShallow: expected NotSupportedErr")
+
+def testImportDocumentTypeDeep():
+ src = create_doc_with_doctype()
+ target = create_doc_without_doctype()
+ try:
+ imported = target.importNode(src.doctype, 1)
+ except xml.dom.NotSupportedErr:
+ pass
+ else:
+ raise Exception(
+ "testImportDocumentTypeDeep: expected NotSupportedErr")
+
+# Testing attribute clones uses a helper, and should always be deep,
+# even if the argument to cloneNode is false.
+def check_clone_attribute(deep, testName):
+ doc = parseString("<doc attr='value'/>")
+ attr = doc.documentElement.getAttributeNode("attr")
+ assert attr is not None
+ clone = attr.cloneNode(deep)
+ confirm(not clone.isSameNode(attr))
+ confirm(not attr.isSameNode(clone))
+ confirm(clone.ownerElement is None,
+ testName + ": ownerElement should be None")
+ confirm(clone.ownerDocument.isSameNode(attr.ownerDocument),
+ testName + ": ownerDocument does not match")
+ confirm(clone.specified,
+ testName + ": cloned attribute must have specified == True")
+
+def testCloneAttributeShallow():
+ check_clone_attribute(0, "testCloneAttributeShallow")
+
+def testCloneAttributeDeep():
+ check_clone_attribute(1, "testCloneAttributeDeep")
+
+def check_clone_pi(deep, testName):
+ doc = parseString("<?target data?><doc/>")
+ pi = doc.firstChild
+ assert pi.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+ clone = pi.cloneNode(deep)
+ confirm(clone.target == pi.target
+ and clone.data == pi.data)
+
+def testClonePIShallow():
+ check_clone_pi(0, "testClonePIShallow")
+
+def testClonePIDeep():
+ check_clone_pi(1, "testClonePIDeep")
def testNormalize():
doc = parseString("<doc/>")
@@ -611,6 +881,444 @@
"testEncodings - encoding EURO SIGN")
doc.unlink()
+class UserDataHandler:
+ called = 0
+ def handle(self, operation, key, data, src, dst):
+ dst.setUserData(key, data + 1, self)
+ src.setUserData(key, None, None)
+ self.called = 1
+
+def testUserData():
+ dom = Document()
+ n = dom.createElement('e')
+ confirm(n.getUserData("foo") is None)
+ n.setUserData("foo", None, None)
+ confirm(n.getUserData("foo") is None)
+ n.setUserData("foo", 12, 12)
+ n.setUserData("bar", 13, 13)
+ confirm(n.getUserData("foo") == 12)
+ confirm(n.getUserData("bar") == 13)
+ n.setUserData("foo", None, None)
+ confirm(n.getUserData("foo") is None)
+ confirm(n.getUserData("bar") == 13)
+
+ handler = UserDataHandler()
+ n.setUserData("bar", 12, handler)
+ c = n.cloneNode(1)
+ confirm(handler.called
+ and n.getUserData("bar") is None
+ and c.getUserData("bar") == 13)
+ n.unlink()
+ c.unlink()
+ dom.unlink()
+
+def testRenameAttribute():
+ doc = parseString("<doc a='v'/>")
+ elem = doc.documentElement
+ attrmap = elem.attributes
+ attr = elem.attributes['a']
+
+ # Simple renaming
+ attr = doc.renameNode(attr, xml.dom.EMPTY_NAMESPACE, "b")
+ confirm(attr.name == "b"
+ and attr.nodeName == "b"
+ and attr.localName is None
+ and attr.namespaceURI == xml.dom.EMPTY_NAMESPACE
+ and attr.prefix is None
+ and attr.value == "v"
+ and elem.getAttributeNode("a") is None
+ and elem.getAttributeNode("b").isSameNode(attr)
+ and attrmap["b"].isSameNode(attr)
+ and attr.ownerDocument.isSameNode(doc)
+ and attr.ownerElement.isSameNode(elem))
+
+ # Rename to have a namespace, no prefix
+ attr = doc.renameNode(attr, "http://xml.python.org/ns", "c")
+ confirm(attr.name == "c"
+ and attr.nodeName == "c"
+ and attr.localName == "c"
+ and attr.namespaceURI == "http://xml.python.org/ns"
+ and attr.prefix is None
+ and attr.value == "v"
+ and elem.getAttributeNode("a") is None
+ and elem.getAttributeNode("b") is None
+ and elem.getAttributeNode("c").isSameNode(attr)
+ and elem.getAttributeNodeNS(
+ "http://xml.python.org/ns", "c").isSameNode(attr)
+ and attrmap["c"].isSameNode(attr)
+ and attrmap[("http://xml.python.org/ns", "c")].isSameNode(attr))
+
+ # Rename to have a namespace, with prefix
+ attr = doc.renameNode(attr, "http://xml.python.org/ns2", "p:d")
+ confirm(attr.name == "p:d"
+ and attr.nodeName == "p:d"
+ and attr.localName == "d"
+ and attr.namespaceURI == "http://xml.python.org/ns2"
+ and attr.prefix == "p"
+ and attr.value == "v"
+ and elem.getAttributeNode("a") is None
+ and elem.getAttributeNode("b") is None
+ and elem.getAttributeNode("c") is None
+ and elem.getAttributeNodeNS(
+ "http://xml.python.org/ns", "c") is None
+ and elem.getAttributeNode("p:d").isSameNode(attr)
+ and elem.getAttributeNodeNS(
+ "http://xml.python.org/ns2", "d").isSameNode(attr)
+ and attrmap["p:d"].isSameNode(attr)
+ and attrmap[("http://xml.python.org/ns2", "d")].isSameNode(attr))
+
+ # Rename back to a simple non-NS node
+ attr = doc.renameNode(attr, xml.dom.EMPTY_NAMESPACE, "e")
+ confirm(attr.name == "e"
+ and attr.nodeName == "e"
+ and attr.localName is None
+ and attr.namespaceURI == xml.dom.EMPTY_NAMESPACE
+ and attr.prefix is None
+ and attr.value == "v"
+ and elem.getAttributeNode("a") is None
+ and elem.getAttributeNode("b") is None
+ and elem.getAttributeNode("c") is None
+ and elem.getAttributeNode("p:d") is None
+ and elem.getAttributeNodeNS(
+ "http://xml.python.org/ns", "c") is None
+ and elem.getAttributeNode("e").isSameNode(attr)
+ and attrmap["e"].isSameNode(attr))
+
+ try:
+ doc.renameNode(attr, "http://xml.python.org/ns", "xmlns")
+ except xml.dom.NamespaceErr:
+ pass
+ else:
+ print "expected NamespaceErr"
+
+ checkRenameNodeSharedConstraints(doc, attr)
+ doc.unlink()
+
+def testRenameElement():
+ doc = parseString("<doc/>")
+ elem = doc.documentElement
+
+ # Simple renaming
+ elem = doc.renameNode(elem, xml.dom.EMPTY_NAMESPACE, "a")
+ confirm(elem.tagName == "a"
+ and elem.nodeName == "a"
+ and elem.localName is None
+ and elem.namespaceURI == xml.dom.EMPTY_NAMESPACE
+ and elem.prefix is None
+ and elem.ownerDocument.isSameNode(doc))
+
+ # Rename to have a namespace, no prefix
+ elem = doc.renameNode(elem, "http://xml.python.org/ns", "b")
+ confirm(elem.tagName == "b"
+ and elem.nodeName == "b"
+ and elem.localName == "b"
+ and elem.namespaceURI == "http://xml.python.org/ns"
+ and elem.prefix is None
+ and elem.ownerDocument.isSameNode(doc))
+
+ # Rename to have a namespace, with prefix
+ elem = doc.renameNode(elem, "http://xml.python.org/ns2", "p:c")
+ confirm(elem.tagName == "p:c"
+ and elem.nodeName == "p:c"
+ and elem.localName == "c"
+ and elem.namespaceURI == "http://xml.python.org/ns2"
+ and elem.prefix == "p"
+ and elem.ownerDocument.isSameNode(doc))
+
+ # Rename back to a simple non-NS node
+ elem = doc.renameNode(elem, xml.dom.EMPTY_NAMESPACE, "d")
+ confirm(elem.tagName == "d"
+ and elem.nodeName == "d"
+ and elem.localName is None
+ and elem.namespaceURI == xml.dom.EMPTY_NAMESPACE
+ and elem.prefix is None
+ and elem.ownerDocument.isSameNode(doc))
+
+ checkRenameNodeSharedConstraints(doc, elem)
+ doc.unlink()
+
+def checkRenameNodeSharedConstraints(doc, node):
+ # Make sure illegal NS usage is detected:
+ try:
+ doc.renameNode(node, "http://xml.python.org/ns", "xmlns:foo")
+ except xml.dom.NamespaceErr:
+ pass
+ else:
+ print "expected NamespaceErr"
+
+ doc2 = parseString("<doc/>")
+ try:
+ doc2.renameNode(node, xml.dom.EMPTY_NAMESPACE, "foo")
+ except xml.dom.WrongDocumentErr:
+ pass
+ else:
+ print "expected WrongDocumentErr"
+
+def testRenameOther():
+ # We have to create a comment node explicitly since not all DOM
+ # builders used with minidom add comments to the DOM.
+ doc = xml.dom.minidom.getDOMImplementation().createDocument(
+ xml.dom.EMPTY_NAMESPACE, "e", None)
+ node = doc.createComment("comment")
+ try:
+ doc.renameNode(node, xml.dom.EMPTY_NAMESPACE, "foo")
+ except xml.dom.NotSupportedErr:
+ pass
+ else:
+ print "expected NotSupportedErr when renaming comment node"
+ doc.unlink()
+
+def checkWholeText(node, s):
+ t = node.wholeText
+ confirm(t == s, "looking for %s, found %s" % (repr(s), repr(t)))
+
+def testWholeText():
+ doc = parseString("<doc>a</doc>")
+ elem = doc.documentElement
+ text = elem.childNodes[0]
+ assert text.nodeType == Node.TEXT_NODE
+
+ checkWholeText(text, "a")
+ elem.appendChild(doc.createTextNode("b"))
+ checkWholeText(text, "ab")
+ elem.insertBefore(doc.createCDATASection("c"), text)
+ checkWholeText(text, "cab")
+
+ # make sure we don't cross other nodes
+ splitter = doc.createComment("comment")
+ elem.appendChild(splitter)
+ text2 = doc.createTextNode("d")
+ elem.appendChild(text2)
+ checkWholeText(text, "cab")
+ checkWholeText(text2, "d")
+
+ x = doc.createElement("x")
+ elem.replaceChild(x, splitter)
+ splitter = x
+ checkWholeText(text, "cab")
+ checkWholeText(text2, "d")
+
+ x = doc.createProcessingInstruction("y", "z")
+ elem.replaceChild(x, splitter)
+ splitter = x
+ checkWholeText(text, "cab")
+ checkWholeText(text2, "d")
+
+ elem.removeChild(splitter)
+ checkWholeText(text, "cabd")
+ checkWholeText(text2, "cabd")
+
+def testReplaceWholeText():
+ def setup():
+ doc = parseString("<doc>a<e/>d</doc>")
+ elem = doc.documentElement
+ text1 = elem.firstChild
+ text2 = elem.lastChild
+ splitter = text1.nextSibling
+ elem.insertBefore(doc.createTextNode("b"), splitter)
+ elem.insertBefore(doc.createCDATASection("c"), text1)
+ return doc, elem, text1, splitter, text2
+
+ doc, elem, text1, splitter, text2 = setup()
+ text = text1.replaceWholeText("new content")
+ checkWholeText(text, "new content")
+ checkWholeText(text2, "d")
+ confirm(len(elem.childNodes) == 3)
+
+ doc, elem, text1, splitter, text2 = setup()
+ text = text2.replaceWholeText("new content")
+ checkWholeText(text, "new content")
+ checkWholeText(text1, "cab")
+ confirm(len(elem.childNodes) == 5)
+
+ doc, elem, text1, splitter, text2 = setup()
+ text = text1.replaceWholeText("")
+ checkWholeText(text2, "d")
+ confirm(text is None
+ and len(elem.childNodes) == 2)
+
+def testSchemaType():
+ doc = parseString(
+ "<!DOCTYPE doc [\n"
+ " <!ENTITY e1 SYSTEM 'http://xml.python.org/e1'>\n"
+ " <!ENTITY e2 SYSTEM 'http://xml.python.org/e2'>\n"
+ " <!ATTLIST doc id ID #IMPLIED \n"
+ " ref IDREF #IMPLIED \n"
+ " refs IDREFS #IMPLIED \n"
+ " enum (a|b) #IMPLIED \n"
+ " ent ENTITY #IMPLIED \n"
+ " ents ENTITIES #IMPLIED \n"
+ " nm NMTOKEN #IMPLIED \n"
+ " nms NMTOKENS #IMPLIED \n"
+ " text CDATA #IMPLIED \n"
+ " >\n"
+ "]><doc id='name' notid='name' text='splat!' enum='b'"
+ " ref='name' refs='name name' ent='e1' ents='e1 e2'"
+ " nm='123' nms='123 abc' />")
+ elem = doc.documentElement
+ # We don't want to rely on any specific loader at this point, so
+ # just make sure we can get to all the names, and that the
+ # DTD-based namespace is right. The names can vary by loader
+ # since each supports a different level of DTD information.
+ t = elem.schemaType
+ confirm(t.name is None
+ and t.namespace == xml.dom.EMPTY_NAMESPACE)
+ names = "id notid text enum ref refs ent ents nm nms".split()
+ for name in names:
+ a = elem.getAttributeNode(name)
+ t = a.schemaType
+ confirm(hasattr(t, "name")
+ and t.namespace == xml.dom.EMPTY_NAMESPACE)
+
+def testSetIdAttribute():
+ doc = parseString("<doc a1='v' a2='w'/>")
+ e = doc.documentElement
+ a1 = e.getAttributeNode("a1")
+ a2 = e.getAttributeNode("a2")
+ confirm(doc.getElementById("v") is None
+ and not a1.isId
+ and not a2.isId)
+ e.setIdAttribute("a1")
+ confirm(e.isSameNode(doc.getElementById("v"))
+ and a1.isId
+ and not a2.isId)
+ e.setIdAttribute("a2")
+ confirm(e.isSameNode(doc.getElementById("v"))
+ and e.isSameNode(doc.getElementById("w"))
+ and a1.isId
+ and a2.isId)
+ # replace the a1 node; the new node should *not* be an ID
+ a3 = doc.createAttribute("a1")
+ a3.value = "v"
+ e.setAttributeNode(a3)
+ confirm(doc.getElementById("v") is None
+ and e.isSameNode(doc.getElementById("w"))
+ and not a1.isId
+ and a2.isId
+ and not a3.isId)
+ # renaming an attribute should not affect it's ID-ness:
+ doc.renameNode(a2, xml.dom.EMPTY_NAMESPACE, "an")
+ confirm(e.isSameNode(doc.getElementById("w"))
+ and a2.isId)
+
+def testSetIdAttributeNS():
+ NS1 = "http://xml.python.org/ns1"
+ NS2 = "http://xml.python.org/ns2"
+ doc = parseString("<doc"
+ " xmlns:ns1='" + NS1 + "'"
+ " xmlns:ns2='" + NS2 + "'"
+ " ns1:a1='v' ns2:a2='w'/>")
+ e = doc.documentElement
+ a1 = e.getAttributeNodeNS(NS1, "a1")
+ a2 = e.getAttributeNodeNS(NS2, "a2")
+ confirm(doc.getElementById("v") is None
+ and not a1.isId
+ and not a2.isId)
+ e.setIdAttributeNS(NS1, "a1")
+ confirm(e.isSameNode(doc.getElementById("v"))
+ and a1.isId
+ and not a2.isId)
+ e.setIdAttributeNS(NS2, "a2")
+ confirm(e.isSameNode(doc.getElementById("v"))
+ and e.isSameNode(doc.getElementById("w"))
+ and a1.isId
+ and a2.isId)
+ # replace the a1 node; the new node should *not* be an ID
+ a3 = doc.createAttributeNS(NS1, "a1")
+ a3.value = "v"
+ e.setAttributeNode(a3)
+ confirm(e.isSameNode(doc.getElementById("w")))
+ confirm(not a1.isId)
+ confirm(a2.isId)
+ confirm(not a3.isId)
+ confirm(doc.getElementById("v") is None)
+ # renaming an attribute should not affect it's ID-ness:
+ doc.renameNode(a2, xml.dom.EMPTY_NAMESPACE, "an")
+ confirm(e.isSameNode(doc.getElementById("w"))
+ and a2.isId)
+
+def testSetIdAttributeNode():
+ NS1 = "http://xml.python.org/ns1"
+ NS2 = "http://xml.python.org/ns2"
+ doc = parseString("<doc"
+ " xmlns:ns1='" + NS1 + "'"
+ " xmlns:ns2='" + NS2 + "'"
+ " ns1:a1='v' ns2:a2='w'/>")
+ e = doc.documentElement
+ a1 = e.getAttributeNodeNS(NS1, "a1")
+ a2 = e.getAttributeNodeNS(NS2, "a2")
+ confirm(doc.getElementById("v") is None
+ and not a1.isId
+ and not a2.isId)
+ e.setIdAttributeNode(a1)
+ confirm(e.isSameNode(doc.getElementById("v"))
+ and a1.isId
+ and not a2.isId)
+ e.setIdAttributeNode(a2)
+ confirm(e.isSameNode(doc.getElementById("v"))
+ and e.isSameNode(doc.getElementById("w"))
+ and a1.isId
+ and a2.isId)
+ # replace the a1 node; the new node should *not* be an ID
+ a3 = doc.createAttributeNS(NS1, "a1")
+ a3.value = "v"
+ e.setAttributeNode(a3)
+ confirm(e.isSameNode(doc.getElementById("w")))
+ confirm(not a1.isId)
+ confirm(a2.isId)
+ confirm(not a3.isId)
+ confirm(doc.getElementById("v") is None)
+ # renaming an attribute should not affect it's ID-ness:
+ doc.renameNode(a2, xml.dom.EMPTY_NAMESPACE, "an")
+ confirm(e.isSameNode(doc.getElementById("w"))
+ and a2.isId)
+
+def testPickledDocument():
+ doc = parseString("<?xml version='1.0' encoding='us-ascii'?>\n"
+ "<!DOCTYPE doc PUBLIC 'http://xml.python.org/public'"
+ " 'http://xml.python.org/system' [\n"
+ " <!ELEMENT e EMPTY>\n"
+ " <!ENTITY ent SYSTEM 'http://xml.python.org/entity'>\n"
+ "]><doc attr='value'> text\n"
+ "<?pi sample?> <!-- comment --> <e/> </doc>")
+ s = pickle.dumps(doc)
+ doc2 = pickle.loads(s)
+ stack = [(doc, doc2)]
+ while stack:
+ n1, n2 = stack.pop()
+ confirm(n1.nodeType == n2.nodeType
+ and len(n1.childNodes) == len(n2.childNodes)
+ and n1.nodeName == n2.nodeName
+ and not n1.isSameNode(n2)
+ and not n2.isSameNode(n1))
+ if n1.nodeType == Node.DOCUMENT_TYPE_NODE:
+ len(n1.entities)
+ len(n2.entities)
+ len(n1.notations)
+ len(n2.notations)
+ confirm(len(n1.entities) == len(n2.entities)
+ and len(n1.notations) == len(n2.notations))
+ for i in range(len(n1.notations)):
+ no1 = n1.notations.item(i)
+ no2 = n1.notations.item(i)
+ confirm(no1.name == no2.name
+ and no1.publicId == no2.publicId
+ and no1.systemId == no2.systemId)
+ statck.append((no1, no2))
+ for i in range(len(n1.entities)):
+ e1 = n1.entities.item(i)
+ e2 = n2.entities.item(i)
+ confirm(e1.notationName == e2.notationName
+ and e1.publicId == e2.publicId
+ and e1.systemId == e2.systemId)
+ stack.append((e1, e2))
+ if n1.nodeType != Node.DOCUMENT_NODE:
+ confirm(n1.ownerDocument.isSameNode(doc)
+ and n2.ownerDocument.isSameNode(doc2))
+ for i in range(len(n1.childNodes)):
+ stack.append((n1.childNodes[i], n2.childNodes[i]))
+
+
# --- MAIN PROGRAM
names = globals().keys()