Index: Doc/lib/libpyexpat.tex =================================================================== --- Doc/lib/libpyexpat.tex (revision 54883) +++ Doc/lib/libpyexpat.tex (working copy) @@ -137,6 +137,16 @@ \method{SetBase()} hasn't been called. \end{methoddesc} +\begin{methoddesc}[xmlparser]{GetSpecifiedAttributeCount}{} +Returns the number of attributes and values passed in the most recent +call to the \function{StartElementHandler} function that were +specified in the start-tag rather than defaulted. Each +attribute/value pair counts as 2. Thus if \member{ordered_attributes} +is set, this corresponds to an index into the list passed to the +\function{StartElementHandler}. +\versionadded{2.6} +\end{methoddesc} + \begin{methoddesc}[xmlparser]{GetInputContext}{} Returns the input data that generated the current event as a string. The data is in the encoding of the entity which contains the text. Index: Lib/test/test_minidom.py =================================================================== --- Lib/test/test_minidom.py (revision 54883) +++ Lib/test/test_minidom.py (working copy) @@ -53,44 +53,32 @@ return doc class MinidomTest(unittest.TestCase): - def tearDown(self): - try: - Node.allnodes - except AttributeError: - # We don't actually have the minidom from the standard library, - # but are picking up the PyXML version from site-packages. - pass - else: - self.confirm(len(Node.allnodes) == 0, - "assertion: len(Node.allnodes) == 0") - if len(Node.allnodes): - print "Garbage left over:" - if verbose: - print Node.allnodes.items()[0:10] - else: - # Don't print specific nodes if repeatable results - # are needed - print len(Node.allnodes) - Node.allnodes = {} - def confirm(self, test, testname = "Test"): self.assertTrue(test, testname) - + + def assertSameNode(self, a, b, msg=None): + if msg is None: + msg = "Expected nodes to be the same, got %r and %r" % (a, b) + self.assert_(a is not None, msg) + self.assert_(b is not None, msg) + self.assert_(a.isSameNode(b), msg) + self.assert_(b.isSameNode(a), msg) + def checkWholeText(self, node, s): t = node.wholeText self.confirm(t == s, "looking for %s, found %s" % (repr(s), repr(t))) - + def testParseFromFile(self): dom = parse(StringIO(open(tstfile).read())) dom.unlink() self.confirm(isinstance(dom,Document)) - + def testGetElementsByTagName(self): dom = parse(tstfile) self.confirm(dom.getElementsByTagName("LI") == \ dom.documentElement.getElementsByTagName("LI")) dom.unlink() - + def testInsertBefore(self): dom = parseString("") root = dom.documentElement @@ -129,11 +117,11 @@ and root.childNodes.item(3) is nelem and nelem2.nextSibling is nelem and nelem.previousSibling is nelem2 - and root.toxml() == + and root.toxml() == "" , "testInsertBefore -- node properly placed in tree") dom.unlink() - + def _create_fragment_test_nodes(self): dom = parseString("") orig = dom.createTextNode("original") @@ -146,11 +134,11 @@ frag.appendChild(c2) frag.appendChild(c3) return dom, orig, c1, c2, c3, frag - + def testInsertBeforeFragment(self): dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes() dom.documentElement.insertBefore(frag, None) - self.confirm(tuple(dom.documentElement.childNodes) == + self.confirm(tuple(dom.documentElement.childNodes) == (orig, c1, c2, c3), "insertBefore(, None)") frag.unlink() @@ -158,28 +146,28 @@ dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes() dom.documentElement.insertBefore(frag, orig) - self.confirm(tuple(dom.documentElement.childNodes) == + self.confirm(tuple(dom.documentElement.childNodes) == (c1, c2, c3, orig), "insertBefore(, orig)") frag.unlink() dom.unlink() - + def testAppendChild(self): dom = parse(tstfile) dom.documentElement.appendChild(dom.createComment(u"Hello")) self.confirm(dom.documentElement.childNodes[-1].nodeName == "#comment") self.confirm(dom.documentElement.childNodes[-1].data == "Hello") dom.unlink() - + def testAppendChildFragment(self): dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes() dom.documentElement.appendChild(frag) - self.confirm(tuple(dom.documentElement.childNodes) == + self.confirm(tuple(dom.documentElement.childNodes) == (orig, c1, c2, c3), "appendChild()") frag.unlink() dom.unlink() - + def testReplaceChildFragment(self): dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes() dom.documentElement.replaceChild(frag, orig) @@ -188,28 +176,147 @@ "replaceChild()") frag.unlink() dom.unlink() - + def testLegalChildren(self): dom = Document() elem = dom.createElement('element') text = dom.createTextNode('text') self.assertRaises(xml.dom.HierarchyRequestErr, dom.appendChild, text) - + dom.appendChild(elem) - self.assertRaises(xml.dom.HierarchyRequestErr, dom.insertBefore, text, + self.assertRaises(xml.dom.HierarchyRequestErr, dom.insertBefore, text, elem) - self.assertRaises(xml.dom.HierarchyRequestErr, dom.replaceChild, text, + self.assertRaises(xml.dom.HierarchyRequestErr, dom.replaceChild, text, elem) - + nodemap = elem.attributes - self.assertRaises(xml.dom.HierarchyRequestErr, nodemap.setNamedItem, + self.assertRaises(xml.dom.HierarchyRequestErr, nodemap.setNamedItem, text) - self.assertRaises(xml.dom.HierarchyRequestErr, nodemap.setNamedItemNS, + self.assertRaises(xml.dom.HierarchyRequestErr, nodemap.setNamedItemNS, text) - + elem.appendChild(text) dom.unlink() - + + def testAncestorLoops(self): + doc = parseString("") + a = doc.documentElement + b = a.firstChild + c = b.firstChild + self.assertRaises(xml.dom.HierarchyRequestErr, + a.appendChild, a) + self.assertRaises(xml.dom.HierarchyRequestErr, + b.appendChild, a) + self.assertRaises(xml.dom.HierarchyRequestErr, + a.insertBefore, a, b) + self.assertRaises(xml.dom.HierarchyRequestErr, + b.insertBefore, a, c) + self.assertRaises(xml.dom.HierarchyRequestErr, + a.replaceChild, a, b) + self.assertRaises(xml.dom.HierarchyRequestErr, + b.replaceChild, a, c) + + + def testWrongDocumentErr(self): + doc1 = parseString("") + doc2 = parseString("") + el1 = doc1.createElement("test") + doc2el = doc2.documentElement + self.assertRaises(xml.dom.WrongDocumentErr, + doc2el.insertBefore, el1, doc2el.firstChild) + self.assertRaises(xml.dom.WrongDocumentErr, + doc2el.replaceChild, el1, doc2el.firstChild) + self.assertRaises(xml.dom.WrongDocumentErr, + doc2el.appendChild, el1) + attr1 = doc1.createAttribute("spam") + attr1.value = "true" + self.assertRaises(xml.dom.WrongDocumentErr, + doc2el.setAttributeNode, attr1) + self.assertRaises(xml.dom.WrongDocumentErr, + doc2el.attributes.setNamedItem, attr1) + + doc2.removeChild(doc2el) + self.assertRaises(xml.dom.WrongDocumentErr, + doc2.appendChild, el1) + + def testInvalidCharacterErr(self): + badNames = ['', '12', 'with space', "can't", + "D\u0133kstra", u'\U00012345'] + uri = 'http://www.python.org/ns/foo' + doc = parseString("") + self.assertEqual(doc.xmlVersion, "1.0") + for name in badNames: + self.assertRaises(xml.dom.InvalidCharacterErr, + doc.createElement, name) + self.assertRaises(xml.dom.InvalidCharacterErr, + doc.createAttribute, name) + self.assertRaises(xml.dom.InvalidCharacterErr, + doc.createAttributeNS, uri, name) + self.assertRaises(xml.dom.InvalidCharacterErr, + doc.createElement, name) + self.assertRaises(xml.dom.InvalidCharacterErr, + doc.createElementNS, uri, name) + self.assertRaises(xml.dom.InvalidCharacterErr, + doc.createEntityReference, name) + self.assertRaises(xml.dom.InvalidCharacterErr, + doc.createProcessingInstruction, name, '') + self.assertRaises(xml.dom.InvalidCharacterErr, + doc.implementation.createDocument, uri, name, None) + self.assertRaises(xml.dom.InvalidCharacterErr, + doc.renameNode, doc.documentElement, None, name) + el = doc.documentElement + self.assertRaises(xml.dom.InvalidCharacterErr, + el.setAttribute, name, 'value') + ## self.assertRaises(xml.dom.InvalidCharacterErr, + ## setattr, el, 'prefix', name) + attrNode = el.getAttributeNode('attr') + self.assertRaises(xml.dom.InvalidCharacterErr, + setattr, attrNode, 'prefix', name) + + # expat doesn't parse XML 1.1 correctly, as of this writing, + # but we can test this by modifying an existing document: + doc.xmlVersion = "1.1" + root = doc.documentElement + xml11_names = [u'D\u0133kstra', u'\U00012345'] + for name in xml11_names: + el = doc.createElement(name) + self.assertEquals(el.tagName, name) + root.attributes[name] = "ok" + self.assertEquals(root.getAttribute(name), 'ok') + + def testInuseAttributeErr(self): + doc = parseString( + "\n" + "\n") + parent = doc.documentElement + attr = parent.getAttributeNode('inuse') + child = parent.firstChild + parent.attributes.setNamedItem(attr) # should be a no-op + self.assertEquals(parent.attributes.length, 1) + self.assertRaises(xml.dom.InuseAttributeErr, + child.attributes.setNamedItem, attr) + parent.setAttributeNode(attr) # no-op + self.assertEquals(parent.attributes.length, 1) + self.assertRaises(xml.dom.InuseAttributeErr, + child.setAttributeNode, attr) + + doc = parseString( + "\n" + "\n") + nsuri = 'http://www.python.org/ns/tests/1.0' + parent = doc.documentElement + attr = parent.getAttributeNodeNS(nsuri, "inuse") + child = parent.firstChild + parent.attributes.setNamedItemNS(attr) # no-op + self.assertEquals(parent.attributes.length, 2) + self.assertRaises(xml.dom.InuseAttributeErr, + child.attributes.setNamedItemNS, attr) + parent.setAttributeNodeNS(attr) # no-op + self.assertEquals(parent.attributes.length, 2) + self.assertRaises(xml.dom.InuseAttributeErr, + child.setAttributeNodeNS, attr) + def testNamedNodeMapSetItem(self): dom = Document() elem = dom.createElement('element') @@ -226,24 +333,48 @@ "NamedNodeMap.__setitem__() sets nodeValue") elem.unlink() dom.unlink() - + def testNonZero(self): dom = parse(tstfile) self.confirm(dom)# should not be zero dom.appendChild(dom.createComment("foo")) self.confirm(not dom.childNodes[-1].childNodes) dom.unlink() - + def testUnlink(self): dom = parse(tstfile) dom.unlink() - + def testElement(self): dom = Document() dom.appendChild(dom.createElement("abc")) self.confirm(dom.documentElement) dom.unlink() - + + def testNodeValueDefinedNull(self): + # According to the spec, for node types where nodeValue is + # defined to be null, setting it has no effect. + doc = parseString( + "\n" + "\n" + "\n" + "]>\n" + "\n") + nodes = [ + doc, + doc.createDocumentFragment(), + doc.doctype, + doc.documentElement, + doc.doctype.entities['version'], + doc.createEntityReference('version'), + doc.doctype.notations['notation']] + for node in nodes: + node.nodeValue = "Tuesday" + self.assert_( + node.nodeValue is None, + "setting %r.nodeValue should have no effect" % node) + def testAAA(self): dom = parseString("") el = dom.documentElement @@ -255,7 +386,33 @@ self.confirm(a.ownerElement is dom.documentElement, "setAttribute() sets ownerElement") dom.unlink() - + + def testAttributesDefinedNull(self): + doc = parseString( + "\n" + "\n" + "\n" + "]>\n" + "\n") + nodes = [ + doc.documentElement.getAttributeNode("test"), + doc.createTextNode("test"), + doc.createCDATASection("1<2"), + doc.createEntityReference("version"), + doc.doctype.entities['version'], + doc.createProcessingInstruction("pragma", "ignore"), + doc.createComment("test"), + doc, + doc.doctype, + doc.createDocumentFragment(), + doc.doctype.notations['notation']] + for node in nodes: + self.assert_(hasattr(node, 'attributes'), + ("Node %r has no 'attributes' property " + "(should be present but null)" % node)) + self.assertEqual(node.attributes, None) + def testAAB(self): dom = parseString("") el = dom.documentElement @@ -263,49 +420,49 @@ el.setAttribute("spam", "jam2") self.confirm(el.toxml() == '', "testAAB") dom.unlink() - + def testAddAttr(self): dom = Document() child = dom.appendChild(dom.createElement("abc")) - + child.setAttribute("def", "ghi") self.confirm(child.getAttribute("def") == "ghi") self.confirm(child.attributes["def"].value == "ghi") - + child.setAttribute("jkl", "mno") self.confirm(child.getAttribute("jkl") == "mno") self.confirm(child.attributes["jkl"].value == "mno") - + self.confirm(len(child.attributes) == 2) - + child.setAttribute("def", "newval") self.confirm(child.getAttribute("def") == "newval") self.confirm(child.attributes["def"].value == "newval") - + self.confirm(len(child.attributes) == 2) dom.unlink() - + def testDeleteAttr(self): dom = Document() child = dom.appendChild(dom.createElement("abc")) - + self.confirm(len(child.attributes) == 0) child.setAttribute("def", "ghi") self.confirm(len(child.attributes) == 1) del child.attributes["def"] self.confirm(len(child.attributes) == 0) dom.unlink() - + def testRemoveAttr(self): dom = Document() child = dom.appendChild(dom.createElement("abc")) - + child.setAttribute("def", "ghi") self.confirm(len(child.attributes) == 1) child.removeAttribute("def") self.confirm(len(child.attributes) == 0) dom.unlink() - + def testRemoveAttrNS(self): dom = Document() child = dom.appendChild( @@ -317,18 +474,27 @@ child.removeAttributeNS("http://www.python.org", "abcattr") self.confirm(len(child.attributes) == 1) dom.unlink() - + def testRemoveAttributeNode(self): dom = Document() child = dom.appendChild(dom.createElement("foo")) child.setAttribute("spam", "jam") self.confirm(len(child.attributes) == 1) node = child.getAttributeNode("spam") - child.removeAttributeNode(node) + removed = child.removeAttributeNode(node) + self.assertSameNode(removed, node) + self.assertEqual(removed.value, "jam") self.confirm(len(child.attributes) == 0 and child.getAttributeNode("spam") is None) + + child.setAttribute("special", "spam") + other = dom.createElement("foo") + other.setAttribute("special", "spam") + self.assertRaises(xml.dom.NotFoundErr, + child.removeAttributeNode, + other.getAttributeNode("special")) dom.unlink() - + def testChangeAttr(self): dom = parseString("") el = dom.documentElement @@ -366,26 +532,68 @@ and el.attributes["spam2"].nodeValue == "bam2" and el.getAttribute("spam2") == "bam2") dom.unlink() - + + def testSetAttributeNodeNS(self): + dom = parseString( + '\n') + el = dom.documentElement + ns = 'http://www.python.org/ns' + origAttr = el.getAttributeNodeNS(ns, 'x') + self.assertEqual(origAttr.nodeName, 'a:x') + self.assertEqual(origAttr.value, "spam") + newAttr = dom.createAttributeNS(ns, 'b:x') + newAttr.value = 'ham' + oldAttr = el.setAttributeNodeNS(newAttr) + self.assertSameNode(oldAttr, origAttr) + self.assertEqual(oldAttr.parentNode, None) + dom.unlink() + + def testAttrSpecified(self): + doc = parseString("") + el = doc.documentElement + self.assert_(el.attributes["brief"].specified) + el.setAttribute("modified", "true") + self.assert_(el.attributes["modified"].specified) + doc.unlink() + + doc = parseString( + "\n" + "\n" + "\n" + "]>\n" + "\n") + test = doc.documentElement + self.assertEquals(test.getAttribute("required"), "true") + self.assert_(not test.getAttributeNode("required").specified) + self.assert_(test.getAttributeNode("date").specified) + self.assert_(test.getAttributeNode("id").specified) + doc.unlink() + def testGetAttrList(self): pass - + def testGetAttrValues(self): pass - + def testGetAttrLength(self): pass - + def testGetAttribute(self): pass - + def testGetAttributeNS(self): pass - + def testGetAttributeNode(self): pass - + def testGetElementsByTagNameNS(self): d=""" """ dom = parseString(d) - elems = dom.getElementsByTagNameNS("http://pyxml.sf.net/minidom", + elems = dom.getElementsByTagNameNS("http://pyxml.sf.net/minidom", "myelem") self.confirm(len(elems) == 1 and elems[0].namespaceURI == "http://pyxml.sf.net/minidom" @@ -394,12 +602,12 @@ and elems[0].tagName == "minidom:myelem" and elems[0].nodeName == "minidom:myelem") dom.unlink() - - def get_empty_nodelist_from_elements_by_tagName_ns_helper(self, doc, nsuri, + + def get_empty_nodelist_from_elements_by_tagName_ns_helper(self, doc, nsuri, lname): nodelist = doc.getElementsByTagNameNS(nsuri, lname) self.confirm(len(nodelist) == 0) - + def testGetEmptyNodeListFromElementsByTagNameNS(self): doc = parseString('') self.get_empty_nodelist_from_elements_by_tagName_ns_helper( @@ -408,7 +616,7 @@ doc, '*', 'splat') self.get_empty_nodelist_from_elements_by_tagName_ns_helper( doc, 'http://xml.python.org/namespaces/a', '*') - + doc = parseString('') self.get_empty_nodelist_from_elements_by_tagName_ns_helper( doc, "http://xml.python.org/splat", "not-there") @@ -416,7 +624,7 @@ doc, "*", "not-there") self.get_empty_nodelist_from_elements_by_tagName_ns_helper( doc, "http://somewhere.else.net/not-there", "e") - + def testElementReprAndStr(self): dom = Document() el = dom.appendChild(dom.createElement("abc")) @@ -424,7 +632,7 @@ string2 = str(el) self.confirm(string1 == string2) dom.unlink() - + def testElementReprAndStrUnicode(self): dom = Document() el = dom.appendChild(dom.createElement(u"abc")) @@ -432,7 +640,7 @@ string2 = str(el) self.confirm(string1 == string2) dom.unlink() - + def testElementReprAndStrUnicodeNS(self): dom = Document() el = dom.appendChild( @@ -442,30 +650,42 @@ self.confirm(string1 == string2) self.confirm(string1.find("slash:abc") != -1) dom.unlink() - + def testAttributeRepr(self): dom = Document() el = dom.appendChild(dom.createElement(u"abc")) node = el.setAttribute("abc", "def") self.confirm(str(node) == repr(node)) dom.unlink() - + def testTextNodeRepr(self): pass - + def testWriteXML(self): str = '' dom = parseString(str) domstr = dom.toxml() dom.unlink() self.confirm(str == domstr) - + def testAltNewline(self): str = '\n\n' dom = parseString(str) domstr = dom.toprettyxml(newl="\r\n") dom.unlink() self.confirm(domstr == str.replace("\n", "\r\n")) - + + def testEntityReference(self): + doc = create_doc_with_doctype() + er = doc.createEntityReference("my-entity") + doc.documentElement.appendChild(er) + self.assertEquals( + doc.documentElement.toxml(), "&my-entity;") + node = doc.createTextNode("hello") + self.assertRaises(xml.dom.NoModificationAllowedErr, + er.appendChild, node) + self.assertRaises(xml.dom.NoModificationAllowedErr, + er.insertBefore, node, None) + def testProcessingInstruction(self): dom = parseString('') pi = dom.documentElement.firstChild @@ -480,15 +700,87 @@ and pi.lastChild is None and pi.localName is None and pi.namespaceURI == xml.dom.EMPTY_NAMESPACE) - + def testProcessingInstructionRepr(self): pass - + + def _checkCharacterDataNode(self, node): + self.assertEqual(node.length, len(node.data)) + n = node.length + d = node.data + + # substringData + self.assertEqual(node.substringData(0, n + 30), node.data) + self.assertEqual(node.substringData(0, 1), node.data[0:1]) + self.assertEqual(node.substringData(n, 0), '') + self.assertEqual(node.substringData(n, 30), '') + self.assertRaises(xml.dom.IndexSizeErr, + node.substringData, -1, 0) + self.assertRaises(xml.dom.IndexSizeErr, + node.substringData, n + 1, 0) + + # appendData + node.appendData("extra") + self.assertEqual(node.length, n + 5) + self.assertEqual(node.data, d + "extra") + + # insertData + n = node.length + d = node.data + self.assertRaises(xml.dom.IndexSizeErr, + node.insertData, -1, "bogus") + self.assertRaises(xml.dom.IndexSizeErr, + node.insertData, n + 1, "bogus") + node.insertData(n, "more") + self.assertEqual(node.length, n + 4) + self.assertEqual(node.data, d + "more") + node.insertData(n, "a bit ") + self.assertEqual(node.length, n + 4 + 6) + self.assertEqual(node.data, d + "a bit more") + self.assertEqual(node.substringData(n + 2, 6), "bit mo") + + # deleteData + node.deleteData(n, 1000000) + self.assertEqual(node.length, n) + self.assertEqual(node.data, d) + node.deleteData(n, 20) # shouldn't throw + self.assertRaises(xml.dom.IndexSizeErr, + node.deleteData, n + 1, 0) + self.assertRaises(xml.dom.IndexSizeErr, + node.deleteData, -1, 0) + + # replaceData + node.replaceData(0, node.length, "test data") + self.assertEqual(node.length, 9) + self.assertEqual(node.data, "test data") + n = node.length + self.assertRaises(xml.dom.IndexSizeErr, + node.replaceData, -1, 0, "") + self.assertRaises(xml.dom.IndexSizeErr, + node.replaceData, n + 1, 0, "") + self.assertRaises(xml.dom.IndexSizeErr, + node.replaceData, 0, -1, "") + node.replaceData(1, 3, "ricky") + self.assertEqual(node.length, 11) + self.assertEqual(node.data, "tricky data") + + def testTextMethods(self): + doc = parseString("This is a text node.") + self._checkCharacterDataNode(doc.documentElement.firstChild) + self._checkCharacterDataNode(doc.createTextNode("hello world")) + self._checkCharacterDataNode(doc.createComment("hello world")) + self._checkCharacterDataNode(doc.createCDATASection("hello world")) + doc.unlink() + + doc = parseString(" node.]]>") + self._checkCharacterDataNode(doc.documentElement.firstChild) + doc.unlink() + def testTextRepr(self): pass - + def testWriteText(self): pass - + def testDocumentElement(self): pass - + def testTooManyDocumentElements(self): doc = parseString("") elem = doc.createElement("extra") @@ -496,27 +788,27 @@ self.assertRaises(xml.dom.HierarchyRequestErr, doc.appendChild, elem) elem.unlink() doc.unlink() - + def testCreateElementNS(self): pass - + def testCreateAttributeNS(self): pass - + def testParse(self): pass - + def testParseString(self): pass - + def testComment(self): pass - + def testAttrListItem(self): pass - + def testAttrListItems(self): pass - + def testAttrListItemNS(self): pass - + def testAttrListKeys(self): pass - + def testAttrListKeysNS(self): pass - + def testRemoveNamedItem(self): doc = parseString("") e = doc.documentElement @@ -525,7 +817,7 @@ a2 = attrs.removeNamedItem("a") self.confirm(a1.isSameNode(a2)) self.assertRaises(xml.dom.NotFoundErr, attrs.removeNamedItem, "a") - + def testRemoveNamedItemNS(self): doc = parseString("") e = doc.documentElement @@ -533,33 +825,33 @@ a1 = e.getAttributeNodeNS("http://xml.python.org/", "b") a2 = attrs.removeNamedItemNS("http://xml.python.org/", "b") self.confirm(a1.isSameNode(a2)) - self.assertRaises(xml.dom.NotFoundErr, attrs.removeNamedItemNS, + self.assertRaises(xml.dom.NotFoundErr, attrs.removeNamedItemNS, "http://xml.python.org/", "b") - + def testAttrListValues(self): pass - + def testAttrListLength(self): pass - + def testAttrList__getitem__(self): pass - + def testAttrList__setitem__(self): pass - + def testSetAttrValueandNodeValue(self): pass - + def testParseElement(self): pass - + def testParseAttributes(self): pass - + def testParseElementNamespaces(self): pass - + def testParseAttributeNamespaces(self): pass - + def testParseProcessingInstructions(self): pass - + def testChildNodes(self): pass - + def testFirstChild(self): pass - + def testHasChildNodes(self): pass def _testCloneElementCopiesAttributes(self, e1, e2, test): @@ -581,7 +873,7 @@ , "clone of attribute node has proper attribute values") self.confirm(a2.ownerElement is e2, "clone of attribute node correctly owned") - + def _setupCloneElement(self, deep): dom = parseString("") root = dom.documentElement @@ -593,7 +885,7 @@ root.setAttribute("attr", "NEW VALUE") root.setAttribute("added", "VALUE") return dom, clone - + def testCloneElementShallow(self): dom, clone = self._setupCloneElement(0) self.confirm(len(clone.childNodes) == 0 @@ -602,7 +894,7 @@ and clone.toxml() == '' , "testCloneElementShallow") dom.unlink() - + def testCloneElementDeep(self): dom, clone = self._setupCloneElement(1) self.confirm(len(clone.childNodes) == 1 @@ -711,25 +1003,25 @@ doc1 = parseString("") doc2 = parseString("") self.assertRaises(xml.dom.NotSupportedErr, doc1.importNode, doc2, deep) - + def testImportDocumentShallow(self): self.check_import_document(0, "testImportDocumentShallow") - + def testImportDocumentDeep(self): self.check_import_document(1, "testImportDocumentDeep") - + def testImportDocumentTypeShallow(self): src = create_doc_with_doctype() target = create_doc_without_doctype() - self.assertRaises(xml.dom.NotSupportedErr, target.importNode, + self.assertRaises(xml.dom.NotSupportedErr, target.importNode, src.doctype, 0) - + def testImportDocumentTypeDeep(self): src = create_doc_with_doctype() target = create_doc_without_doctype() - self.assertRaises(xml.dom.NotSupportedErr, target.importNode, + self.assertRaises(xml.dom.NotSupportedErr, target.importNode, src.doctype, 1) - + # Testing attribute clones uses a helper, and should always be deep, # even if the argument to cloneNode is false. def check_clone_attribute(self, deep, testName): @@ -745,13 +1037,13 @@ testName + ": ownerDocument does not match") self.confirm(clone.specified, testName + ": cloned attribute must have specified == True") - + def testCloneAttributeShallow(self): self.check_clone_attribute(0, "testCloneAttributeShallow") - + def testCloneAttributeDeep(self): self.check_clone_attribute(1, "testCloneAttributeDeep") - + def check_clone_pi(self, deep, testName): doc = parseString("") pi = doc.firstChild @@ -759,10 +1051,10 @@ clone = pi.cloneNode(deep) self.confirm(clone.target == pi.target and clone.data == pi.data) - + def testClonePIShallow(self): self.check_clone_pi(0, "testClonePIShallow") - + def testClonePIDeep(self): self.check_clone_pi(1, "testClonePIDeep") @@ -772,7 +1064,7 @@ root.appendChild(doc.createTextNode("first")) root.appendChild(doc.createTextNode("second")) self.confirm(len(root.childNodes) == 2 - and root.childNodes.length == 2, + and root.childNodes.length == 2, "testNormalize -- preparation") doc.normalize() self.confirm(len(root.childNodes) == 1 @@ -781,7 +1073,7 @@ and root.firstChild.data == "firstsecond" , "testNormalize -- result") doc.unlink() - + doc = parseString("") root = doc.documentElement root.appendChild(doc.createTextNode("")) @@ -790,21 +1082,21 @@ and root.childNodes.length == 0, "testNormalize -- single empty node removed") doc.unlink() - + def testSiblings(self): doc = parseString("text?") root = doc.documentElement (pi, text, elm) = root.childNodes - + self.confirm(pi.nextSibling is text and pi.previousSibling is None and text.nextSibling is elm and text.previousSibling is pi and elm.nextSibling is None and elm.previousSibling is text, "testSiblings") - + doc.unlink() - + def testParents(self): doc = parseString( "") @@ -812,14 +1104,14 @@ elm1 = root.childNodes[0] (elm2a, elm2b) = elm1.childNodes elm3 = elm2b.childNodes[0] - + self.confirm(root.parentNode is doc and elm1.parentNode is root and elm2a.parentNode is elm1 and elm2b.parentNode is elm1 and elm3.parentNode is elm2b, "testParents") doc.unlink() - + def testNodeListItem(self): doc = parseString("") children = doc.childNodes @@ -831,10 +1123,10 @@ and docelem.childNodes.item(0).childNodes.item(0) is None, "test NodeList.item()") doc.unlink() - + def testSAX2DOM(self): from xml.dom import pulldom - + sax2dom = pulldom.SAX2DOM() sax2dom.startDocument() sax2dom.startElement("doc", {}) @@ -845,12 +1137,12 @@ sax2dom.characters("text") sax2dom.endElement("doc") sax2dom.endDocument() - + doc = sax2dom.document root = doc.documentElement (text1, elm1, text2) = root.childNodes text3 = elm1.childNodes[0] - + self.confirm(text1.previousSibling is None and text1.nextSibling is elm1 and elm1.previousSibling is text1 and @@ -859,28 +1151,28 @@ text2.nextSibling is None and text3.previousSibling is None and text3.nextSibling is None, "testSAX2DOM - siblings") - + self.confirm(root.parentNode is doc and text1.parentNode is root and elm1.parentNode is root and text2.parentNode is root and text3.parentNode is elm1, "testSAX2DOM - parents") doc.unlink() - + def testEncodings(self): doc = parseString('') self.confirm(doc.toxml() == u'\u20ac' - and doc.toxml('utf-8') == + and doc.toxml('utf-8') == '\xe2\x82\xac' - and doc.toxml('iso-8859-15') == + and doc.toxml('iso-8859-15') == '\xa4', "testEncodings - encoding EURO SIGN") - - # Verify that character decoding errors throw exceptions instead + + # Verify that character decoding errors throw exceptions instead # of crashing - self.assertRaises(UnicodeDecodeError, parseString, + self.assertRaises(UnicodeDecodeError, parseString, 'Comment \xe7a va ? Tr\xe8s bien ?') - + doc.unlink() class UserDataHandler: @@ -889,7 +1181,7 @@ dst.setUserData(key, data + 1, self) src.setUserData(key, None, None) self.called = 1 - + def testUserData(self): dom = Document() n = dom.createElement('e') @@ -903,7 +1195,7 @@ n.setUserData("foo", None, None) self.confirm(n.getUserData("foo") is None) self.confirm(n.getUserData("bar") == 13) - + handler = self.UserDataHandler() n.setUserData("bar", 12, handler) c = n.cloneNode(1) @@ -916,32 +1208,32 @@ def checkRenameNodeSharedConstraints(self, doc, node): # Make sure illegal NS usage is detected: - self.assertRaises(xml.dom.NamespaceErr, doc.renameNode, node, + self.assertRaises(xml.dom.NamespaceErr, doc.renameNode, node, "http://xml.python.org/ns", "xmlns:foo") doc2 = parseString("") - self.assertRaises(xml.dom.WrongDocumentErr, doc2.renameNode, node, + self.assertRaises(xml.dom.WrongDocumentErr, doc2.renameNode, node, xml.dom.EMPTY_NAMESPACE, "foo") - + def testRenameAttribute(self): doc = parseString("") elem = doc.documentElement attrmap = elem.attributes attr = elem.attributes['a'] - + # Simple renaming attr = doc.renameNode(attr, xml.dom.EMPTY_NAMESPACE, "b") - self.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)) - + self.assertEqual(attr.name, "b") + self.assertEqual(attr.nodeName, "b") + self.assertEqual(attr.localName, None) + self.assertEqual(attr.namespaceURI, xml.dom.EMPTY_NAMESPACE) + self.assertEqual(attr.prefix, None) + self.assertEqual(attr.value, "v") + self.assertEqual(elem.getAttributeNode("a"), None) + self.assertSameNode(elem.getAttributeNode("b"), attr) + self.assertSameNode(attrmap["b"], attr) + self.assertSameNode(attr.ownerDocument, doc) + self.assertSameNode(attr.ownerElement, elem) + # Rename to have a namespace, no prefix attr = doc.renameNode(attr, "http://xml.python.org/ns", "c") self.confirm(attr.name == "c" @@ -957,52 +1249,51 @@ "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") - self.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)) - + self.assertEqual(attr.name, "p:d") + self.assertEqual(attr.nodeName, "p:d") + self.assertEqual(attr.localName, "d") + self.assertEqual(attr.namespaceURI, "http://xml.python.org/ns2") + self.assertEqual(attr.prefix, "p") + self.assertEqual(attr.value, "v") + self.assertEqual(elem.getAttributeNode("a"), None) + self.assertEqual(elem.getAttributeNode("b"), None) + self.assertEqual(elem.getAttributeNode("c"), None) + self.assertEqual(elem.getAttributeNodeNS( + "http://xml.python.org/ns", "c"), None) + self.assertSameNode(elem.getAttributeNode("p:d"), attr) + self.assertSameNode(elem.getAttributeNodeNS( + "http://xml.python.org/ns2", "d"), attr) + self.assertSameNode(attrmap["p:d"], attr) + self.assertSameNode(attrmap[("http://xml.python.org/ns2", "d")], attr) + # Rename back to a simple non-NS node attr = doc.renameNode(attr, xml.dom.EMPTY_NAMESPACE, "e") - self.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)) + self.assertEqual(attr.name, "e") + self.assertEqual(attr.nodeName, "e") + self.assertEqual(attr.localName, None) + self.assertEqual(attr.namespaceURI, xml.dom.EMPTY_NAMESPACE) + self.assertEqual(attr.prefix, None) + self.assertEqual(attr.value, "v") + self.assertEqual(elem.getAttributeNode("a"), None) + self.assertEqual(elem.getAttributeNode("b"), None) + self.assertEqual(elem.getAttributeNode("c"), None) + self.assertEqual(elem.getAttributeNode("p:d"), None) + self.assertEqual(elem.getAttributeNodeNS("http://xml.python.org/ns", "c"), None) + self.assertSameNode(elem.getAttributeNode("e"), attr) + self.assertSameNode(attrmap["e"], attr) - self.assertRaises(xml.dom.NamespaceErr, doc.renameNode, attr, + self.assertRaises(xml.dom.NamespaceErr, doc.renameNode, attr, "http://xml.python.org/ns", "xmlns") self.checkRenameNodeSharedConstraints(doc, attr) doc.unlink() - + def testRenameElement(self): doc = parseString("") elem = doc.documentElement - + # Simple renaming elem = doc.renameNode(elem, xml.dom.EMPTY_NAMESPACE, "a") self.confirm(elem.tagName == "a" @@ -1011,7 +1302,7 @@ 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") self.confirm(elem.tagName == "b" @@ -1020,7 +1311,7 @@ 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") self.confirm(elem.tagName == "p:c" @@ -1029,7 +1320,7 @@ 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") self.confirm(elem.tagName == "d" @@ -1038,17 +1329,17 @@ and elem.namespaceURI == xml.dom.EMPTY_NAMESPACE and elem.prefix is None and elem.ownerDocument.isSameNode(doc)) - + self.checkRenameNodeSharedConstraints(doc, elem) doc.unlink() - + def testRenameOther(self): # 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") - self.assertRaises(xml.dom.NotSupportedErr, doc.renameNode, node, + self.assertRaises(xml.dom.NotSupportedErr, doc.renameNode, node, xml.dom.EMPTY_NAMESPACE, "foo") doc.unlink() @@ -1057,13 +1348,13 @@ elem = doc.documentElement text = elem.childNodes[0] self.assertEquals(text.nodeType, Node.TEXT_NODE) - + self.checkWholeText(text, "a") elem.appendChild(doc.createTextNode("b")) self.checkWholeText(text, "ab") elem.insertBefore(doc.createCDATASection("c"), text) self.checkWholeText(text, "cab") - + # make sure we don't cross other nodes splitter = doc.createComment("comment") elem.appendChild(splitter) @@ -1071,23 +1362,23 @@ elem.appendChild(text2) self.checkWholeText(text, "cab") self.checkWholeText(text2, "d") - + x = doc.createElement("x") elem.replaceChild(x, splitter) splitter = x self.checkWholeText(text, "cab") self.checkWholeText(text2, "d") - + x = doc.createProcessingInstruction("y", "z") elem.replaceChild(x, splitter) splitter = x self.checkWholeText(text, "cab") self.checkWholeText(text2, "d") - + elem.removeChild(splitter) self.checkWholeText(text, "cabd") self.checkWholeText(text2, "cabd") - + def testPatch1094164(self): doc = parseString("") elem = doc.documentElement @@ -1096,7 +1387,7 @@ # Check that replacing a child with itself leaves the tree unchanged elem.replaceChild(e, e) self.confirm(e.parentNode is elem, "After replaceChild()") - + def testReplaceWholeText(self): def setup(): doc = parseString("ad") @@ -1107,25 +1398,25 @@ 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") self.checkWholeText(text, "new content") self.checkWholeText(text2, "d") self.confirm(len(elem.childNodes) == 3) - + doc, elem, text1, splitter, text2 = setup() text = text2.replaceWholeText("new content") self.checkWholeText(text, "new content") self.checkWholeText(text1, "cab") self.confirm(len(elem.childNodes) == 5) - + doc, elem, text1, splitter, text2 = setup() text = text1.replaceWholeText("") self.checkWholeText(text2, "d") self.confirm(text is None and len(elem.childNodes) == 2) - + def testSchemaType(self): doc = parseString( "") e = doc.documentElement @@ -1189,7 +1480,7 @@ doc.renameNode(a2, xml.dom.EMPTY_NAMESPACE, "an") self.confirm(e.isSameNode(doc.getElementById("w")) and a2.isId) - + def testSetIdAttributeNS(self): NS1 = "http://xml.python.org/ns1" NS2 = "http://xml.python.org/ns2" @@ -1309,6 +1600,6 @@ def test_main(): run_unittest(MinidomTest) - + if __name__ == "__main__": test_main() Index: Lib/xml/dom/expatbuilder.py =================================================================== --- Lib/xml/dom/expatbuilder.py (revision 54883) +++ Lib/xml/dom/expatbuilder.py (working copy) @@ -159,7 +159,6 @@ self._intern_setdefault = self._parser.intern.setdefault self._parser.buffer_text = True self._parser.ordered_attributes = True - self._parser.specified_attributes = True self.install(self._parser) return self._parser @@ -350,6 +349,8 @@ def first_element_handler(self, name, attributes): if self._filter is None and not self._elem_info: self._finish_end_element = id + if self.document.xmlVersion is None: + self.document.xmlVersion = "1.0" self.getParser().StartElementHandler = self.start_element_handler self.start_element_handler(name, attributes) @@ -359,6 +360,7 @@ self.curNode = node if attributes: + ispecified = self.getParser().GetSpecifiedAttributeCount() for i in range(0, len(attributes), 2): a = minidom.Attr(attributes[i], EMPTY_NAMESPACE, None, EMPTY_PREFIX) @@ -368,6 +370,7 @@ d = a.__dict__ d['value'] = d['nodeValue'] = value d['ownerDocument'] = self.document + d['specified'] = i < ispecified _set_attribute_node(node, a) if node is not self.document.documentElement: @@ -446,7 +449,7 @@ [None, name, None, None, default, 0, type, required]) def xml_decl_handler(self, version, encoding, standalone): - self.document.version = version + self.document.xmlVersion = version self.document.encoding = encoding # This is still a little ugly, thanks to the pyexpat API. ;-( if standalone >= 0: @@ -772,6 +775,7 @@ if attributes: _attrs = node._attrs _attrsNS = node._attrsNS + ispecified = self.getParser().GetSpecifiedAttributeCount() for i in range(0, len(attributes), 2): aname = attributes[i] value = attributes[i+1] @@ -791,6 +795,7 @@ d['ownerDocument'] = self.document d['value'] = d['nodeValue'] = value d['ownerElement'] = node + d['specified'] = i < ispecified if __debug__: # This only adds some asserts to the original Index: Lib/xml/dom/minidom.py =================================================================== --- Lib/xml/dom/minidom.py (revision 54883) +++ Lib/xml/dom/minidom.py (working copy) @@ -15,6 +15,7 @@ """ import xml.dom +import sys, re from xml.dom import EMPTY_NAMESPACE, EMPTY_PREFIX, XMLNS_NAMESPACE, domreg from xml.dom.minicompat import * @@ -35,9 +36,18 @@ ownerDocument = None nextSibling = None previousSibling = None + attributes = None prefix = EMPTY_PREFIX # non-null only for NS elements and attributes + def __setattr__(self, name, value): + d = self.__dict__ + if name == 'nodeValue': + # For many node types, this is a no-op. + pass + else: + d[name] = value + def __nonzero__(self): return True @@ -82,9 +92,20 @@ self.insertBefore(c, refChild) ### The DOM does not clearly specify what to return in this case return newChild + doc = self.ownerDocument or self + newChildDoc = newChild.ownerDocument + if newChildDoc is not doc and newChildDoc is not None: + raise xml.dom.WrongDocumentErr() if newChild.nodeType not in self._child_node_types: raise xml.dom.HierarchyRequestErr( "%s cannot be child of %s" % (repr(newChild), repr(self))) + if newChild is self: + raise xml.dom.HierarchyRequestErr( + "cannot insert %r into itself" % self) + if _is_ancestor(newChild, self): + raise xml.dom.HierarchyRequestErr( + "new child %r is an ancestor of target node %r" + % (newChild, self)) if newChild.parentNode is not None: newChild.parentNode.removeChild(newChild) if refChild is None: @@ -114,10 +135,20 @@ self.appendChild(c) ### The DOM does not clearly specify what to return in this case return node + doc = self.ownerDocument or self + nodeDoc = node.ownerDocument + if nodeDoc is not doc and nodeDoc is not None: + raise xml.dom.WrongDocumentErr() if node.nodeType not in self._child_node_types: raise xml.dom.HierarchyRequestErr( "%s cannot be child of %s" % (repr(node), repr(self))) - elif node.nodeType in _nodeTypes_with_children: + if node is self: + raise xml.dom.HierarchyRequestErr( + "cannot append a node to itself (%r)" % self) + if _is_ancestor(node, self): + raise xml.dom.HierarchyRequestErr( + "new child %r is an ancestor of target node %r" % (node, self)) + if node.nodeType in _nodeTypes_with_children: _clear_id_cache(self) if node.parentNode is not None: node.parentNode.removeChild(node) @@ -130,11 +161,22 @@ refChild = oldChild.nextSibling self.removeChild(oldChild) return self.insertBefore(newChild, refChild) + doc = self.ownerDocument or self + newChildDoc = newChild.ownerDocument + if newChildDoc is not doc and newChildDoc is not None: + raise xml.dom.WrongDocumentErr() if newChild.nodeType not in self._child_node_types: raise xml.dom.HierarchyRequestErr( "%s cannot be child of %s" % (repr(newChild), repr(self))) if newChild is oldChild: return + if newChild is self: + raise xml.dom.HierarchyRequestErr( + "cannot insert %r into itself" % self) + if _is_ancestor(newChild, self): + raise xml.dom.HierarchyRequestErr( + "new child %r is an ancestor of target node %r" + % (newChild, self)) if newChild.parentNode is not None: newChild.parentNode.removeChild(newChild) try: @@ -294,6 +336,14 @@ node = node.parentNode return False +def _is_ancestor(ancestor, child): + while True: + child = child.parentNode + if child is None: + return False + if ancestor is child: + return True + def _write_data(writer, data): "Writes datachars to writer." data = data.replace("&", "&").replace("<", "<") @@ -321,7 +371,6 @@ nodeType = Node.DOCUMENT_FRAGMENT_NODE nodeName = "#document-fragment" nodeValue = None - attributes = None parentNode = None _child_node_types = (Node.ELEMENT_NODE, Node.TEXT_NODE, @@ -337,9 +386,8 @@ class Attr(Node): nodeType = Node.ATTRIBUTE_NODE - attributes = None ownerElement = None - specified = False + specified = True _is_id = False _child_node_types = (Node.TEXT_NODE, Node.ENTITY_REFERENCE_NODE) @@ -379,10 +427,14 @@ d["name"] = d["nodeName"] = value if self.ownerElement is not None: _clear_id_cache(self.ownerElement) + elif name == 'prefix': + self._set_prefix(value) else: d[name] = value def _set_prefix(self, prefix): + if self.ownerDocument is not None: + _check_name(prefix, self.ownerDocument.xmlVersion) nsuri = self.namespaceURI if prefix == "xmlns": if nsuri and nsuri != XMLNS_NAMESPACE: @@ -398,6 +450,9 @@ _clear_id_cache(self.ownerElement) d['nodeName'] = d['name'] = newName + def _get_value(self): + return self.value + def _set_value(self, value): d = self.__dict__ d['value'] = d['nodeValue'] = value @@ -405,6 +460,9 @@ _clear_id_cache(self.ownerElement) self.childNodes[0].data = value + _get_nodeValue = _get_value + _set_nodeValue = _set_value + def unlink(self): # This implementation does not call the base implementation # since most of that is not needed, and the expense of the @@ -579,9 +637,17 @@ raise xml.dom.NotFoundErr() def setNamedItem(self, node): + if node.ownerDocument is not self._ownerElement.ownerDocument: + raise xml.dom.WrongDocumentErr() if not isinstance(node, Attr): raise xml.dom.HierarchyRequestErr( "%s cannot be child of %s" % (repr(node), repr(self))) + if node.ownerElement is self._ownerElement: + return + if node.ownerElement is not None: + raise xml.dom.InuseAttributeErr( + "Cannot add %r to %r while it is still used in %r" + % (node, self, node.ownerElement)) old = self._attrs.get(node.name) if old: old.unlink() @@ -687,6 +753,7 @@ return "" def setAttribute(self, attname, value): + _check_name(attname, self.ownerDocument.xmlVersion) attr = self.getAttributeNode(attname) if attr is None: attr = Attr(attname) @@ -702,6 +769,7 @@ _clear_id_cache(self) def setAttributeNS(self, namespaceURI, qualifiedName, value): + _check_name(qualifiedName, self.ownerDocument.xmlVersion) prefix, localname = _nssplit(qualifiedName) attr = self.getAttributeNodeNS(namespaceURI, localname) if attr is None: @@ -730,6 +798,8 @@ return self._attrsNS.get((namespaceURI, localName)) def setAttributeNode(self, attr): + if attr.ownerDocument is not self.ownerDocument: + raise xml.dom.WrongDocumentErr() if attr.ownerElement not in (None, self): raise xml.dom.InuseAttributeErr("attribute node already owned") old1 = self._attrs.get(attr.name, None) @@ -740,7 +810,11 @@ self.removeAttributeNode(old2) _set_attribute_node(self, attr) - if old1 is not attr: + ### The DOM does not clearly specify what to return when attr + ### was already in this element. (The W3C DOM Test Suite + ### expects this to return attr in that case, but the + ### recommendation itself doesn't say.) + if old1 is not None and old1 is not attr: # It might have already been part of this node, in which case # it doesn't represent a change, and should not be returned. return old1 @@ -767,14 +841,17 @@ if node is None: raise xml.dom.NotFoundErr() try: - self._attrs[node.name] + target = self._attrs[node.name] except KeyError: raise xml.dom.NotFoundErr() + if node is not target: + raise xml.dom.NotFoundErr() _clear_id_cache(self) node.unlink() # Restore this since the node is still useful and otherwise # unlinked node.ownerDocument = self.ownerDocument + return node removeAttributeNodeNS = removeAttributeNode @@ -868,7 +945,6 @@ the complexity of the Node methods that deal with children. """ - attributes = None childNodes = EmptyNodeList() firstChild = None lastChild = None @@ -912,6 +988,9 @@ d = self.__dict__ d['data'] = d['nodeValue'] = value + _get_nodeValue = _get_data + _set_nodeValue = _set_data + def _get_target(self): return self.target def _set_target(self, value): @@ -962,7 +1041,7 @@ def substringData(self, offset, count): if offset < 0: raise xml.dom.IndexSizeErr("offset cannot be negative") - if offset >= len(self.data): + if offset > len(self.data): raise xml.dom.IndexSizeErr("offset cannot be beyond end of data") if count < 0: raise xml.dom.IndexSizeErr("count cannot be negative") @@ -974,7 +1053,7 @@ def insertData(self, offset, arg): if offset < 0: raise xml.dom.IndexSizeErr("offset cannot be negative") - if offset >= len(self.data): + if offset > len(self.data): raise xml.dom.IndexSizeErr("offset cannot be beyond end of data") if arg: self.data = "%s%s%s" % ( @@ -983,7 +1062,7 @@ def deleteData(self, offset, count): if offset < 0: raise xml.dom.IndexSizeErr("offset cannot be negative") - if offset >= len(self.data): + if offset > len(self.data): raise xml.dom.IndexSizeErr("offset cannot be beyond end of data") if count < 0: raise xml.dom.IndexSizeErr("count cannot be negative") @@ -1012,7 +1091,6 @@ nodeType = Node.TEXT_NODE nodeName = "#text" - attributes = None def splitText(self, offset): if offset < 0 or offset > len(self.data): @@ -1287,7 +1365,6 @@ writer.write(">"+newl) class Entity(Identified, Node): - attributes = None nodeType = Node.ENTITY_NODE nodeValue = None @@ -1326,6 +1403,40 @@ raise xml.dom.HierarchyRequestErr( "cannot replace children of an entity node") +class EntityReference(Node): + nodeType = Node.ENTITY_REFERENCE_NODE + nodeValue = None + + _child_node_types = (Node.ELEMENT_NODE, + Node.TEXT_NODE, + Node.CDATA_SECTION_NODE, + Node.ENTITY_REFERENCE_NODE, + Node.PROCESSING_INSTRUCTION_NODE, + Node.COMMENT_NODE) + + def __init__(self, name): + self.nodeName = name + self.childNodes = NodeList() + + def appendChild(self, newChild): + raise xml.dom.NoModificationAllowedErr( + "EntityReference nodes are readonly") + + def insertBefore(self, newChild, refChild): + raise xml.dom.NoModificationAllowedErr( + "EntityReference nodes are readonly") + + def removeChild(self, oldChild): + raise xml.dom.NoModificationAllowedErr( + "EntityReference nodes are readonly") + + def replaceChild(self, newChild, oldChild): + raise xml.dom.NoModificationAllowedErr( + "EntityReference nodes are readonly") + + def writexml(self, writer, indent="", addindent="", newl=""): + writer.write("&" + self.nodeName + ";") + class Notation(Identified, Childless, Node): nodeType = Node.NOTATION_NODE nodeValue = None @@ -1357,6 +1468,9 @@ if doctype and doctype.parentNode is not None: raise xml.dom.WrongDocumentErr( "doctype object owned by another DOM tree") + if qualifiedName is not None: + # Note: DOM Level 3 specifies XML 1.0 here. + _check_name(qualifiedName, "1.0") doc = self._create_document() add_root_element = not (namespaceURI is None @@ -1368,10 +1482,8 @@ # would be the other obvious candidate. Since Xerces raises # InvalidCharacterErr, and since SyntaxErr is not listed # for createDocument, that seems to be the better choice. - # XXX: need to check for illegal characters here and in - # createElement. - # DOM Level III clears this up when talking about the return value + # DOM Level 3 clears this up when talking about the return value # of this function. If namespaceURI, qName and DocType are # Null the document is returned without a document element # Otherwise if doctype or namespaceURI are not None @@ -1399,6 +1511,9 @@ return doc def createDocumentType(self, qualifiedName, publicId, systemId): + if qualifiedName is not None: + # DOM Level 3 specifies XML 1.0 here + _check_name(qualifiedName, "1.0") doctype = DocumentType(qualifiedName) doctype.publicId = publicId doctype.systemId = systemId @@ -1473,7 +1588,6 @@ nodeType = Node.DOCUMENT_NODE nodeName = "#document" nodeValue = None - attributes = None doctype = None parentNode = None previousSibling = nextSibling = None @@ -1493,6 +1607,7 @@ _magic_id_count = 0 def __init__(self): + self.xmlVersion = "1.0" self.childNodes = NodeList() # mapping of (namespaceURI, localName) -> ElementInfo # and tagName -> ElementInfo @@ -1597,6 +1712,7 @@ return d def createElement(self, tagName): + _check_name(tagName, self.xmlVersion) e = Element(tagName) e.ownerDocument = self return e @@ -1623,23 +1739,27 @@ return c def createProcessingInstruction(self, target, data): + _check_name(target, self.xmlVersion) p = ProcessingInstruction(target, data) p.ownerDocument = self return p def createAttribute(self, qName): + _check_name(qName, self.xmlVersion) a = Attr(qName) a.ownerDocument = self a.value = "" return a def createElementNS(self, namespaceURI, qualifiedName): + _check_name(qualifiedName, self.xmlVersion) prefix, localName = _nssplit(qualifiedName) e = Element(qualifiedName, namespaceURI, prefix) e.ownerDocument = self return e def createAttributeNS(self, namespaceURI, qualifiedName): + _check_name(qualifiedName, self.xmlVersion) prefix, localName = _nssplit(qualifiedName) a = Attr(qualifiedName, namespaceURI, localName, prefix) a.ownerDocument = self @@ -1659,6 +1779,12 @@ n.ownerDocument = self return n + def createEntityReference(self, name): + _check_name(name, self.xmlVersion) + ref = EntityReference(name) + ref.ownerDocument = self + return ref + def getElementById(self, id): if self._id_cache.has_key(id): return self._id_cache[id] @@ -1753,6 +1879,7 @@ if n.nodeType not in (Node.ELEMENT_NODE, Node.ATTRIBUTE_NODE): raise xml.dom.NotSupportedErr( "renameNode() only applies to element and attribute nodes") + _check_name(name, self.xmlVersion) if namespaceURI != EMPTY_NAMESPACE: if ':' in name: prefix, localName = name.split(':', 1) @@ -1894,6 +2021,148 @@ return (None, fields[0]) +# --- XML 1.0 Names + +_xml10_letter = ( + ur'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF' + ur'\u0100-\u0131\u0134-\u013E\u0141-\u0148\u014A-\u017E\u0180-\u01C3' + ur'\u01CD-\u01F0\u01F4-\u01F5\u01FA-\u0217\u0250-\u02A8\u02BB-\u02C1' + ur'\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03D6' + ur'\u03DA\u03DC\u03DE\u03E0\u03E2-\u03F3\u0401-\u040C\u040E-\u044F' + ur'\u0451-\u045C\u045E-\u0481\u0490-\u04C4\u04C7-\u04C8\u04CB-\u04CC' + ur'\u04D0-\u04EB\u04EE-\u04F5\u04F8-\u04F9\u0531-\u0556\u0559' + ur'\u0561-\u0586\u05D0-\u05EA\u05F0-\u05F2\u0621-\u063A\u0641-\u064A' + ur'\u0671-\u06B7\u06BA-\u06BE\u06C0-\u06CE\u06D0-\u06D3\u06D5' + ur'\u06E5-\u06E6\u0905-\u0939\u093D\u0958-\u0961\u0985-\u098C' + ur'\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9' + ur'\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10' + ur'\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39' + ur'\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8B\u0A8D\u0A8F-\u0A91' + ur'\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AE0' + ur'\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33' + ur'\u0B36-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B85-\u0B8A' + ur'\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F' + ur'\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB5\u0BB7-\u0BB9\u0C05-\u0C0C' + ur'\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C60-\u0C61' + ur'\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9' + ur'\u0CDE\u0CE0-\u0CE1' + ur'\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D60-\u0D61' + ur'\u0E01-\u0E2E\u0E30\u0E32-\u0E33\u0E40-\u0E45\u0E81-\u0E82\u0E84' + ur'\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3' + ur'\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EAE\u0EB0\u0EB2-\u0EB3\u0EBD' + ur'\u0EC0-\u0EC4' + ur'\u0F40-\u0F47\u0F49-\u0F69' + ur'\u10A0-\u10C5\u10D0-\u10F6' + ur'\u1100\u1102-\u1103\u1105-\u1107\u1109\u110B-\u110C\u110E-\u1112' + ur'\u113C\u113E\u1140\u114C\u114E\u1150\u1154-\u1155\u1159' + ur'\u115F-\u1161\u1163\u1165\u1167\u1169\u116D-\u116E\u1172-\u1173' + ur'\u1175\u119E\u11A8\u11AB\u11AE-\u11AF\u11B7-\u11B8\u11BA' + ur'\u11BC-\u11C2\u11EB\u11F0\u11F9' + ur'\u1E00-\u1E9B\u1EA0-\u1EF9' + ur'\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57' + ur'\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE' + ur'\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC' + ur'\u1FF2-\u1FF4\u1FF6-\u1FFC' + ur'\u2126\u212A-\u212B\u212E\u2180-\u2182' + ur'\u3007' + ur'\u3021-\u3029' + ur'\u3041-\u3094\u30A1-\u30FA\u3105-\u312C' + ur'\u4E00-\u9FA5' + ur'\uAC00-\uD7A3') + +# [4] NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' +# | CombiningChar | Extender +_xml10_name_char = ( + _xml10_letter + ur'\.\-_:' + + # Digit + ur'\u0030-\u0039\u0660-\u0669\u06F0-\u06F9\u0966-\u096F\u09E6-\u09EF' + ur'\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0BE7-\u0BEF\u0C66-\u0C6F' + ur'\u0CE6-\u0CEF\u0D66-\u0D6F\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F29' + # CombiningChar + ur'\u0300-\u0345\u0360-\u0361\u0483-\u0486' + ur'\u0591-\u05A1\u05A3-\u05B9\u05BB-\u05BD\u05BF\u05C1-\u05C2\u05C4' + ur'\u064B-\u0652\u0670\u06D6-\u06DC\u06DD-\u06DF\u06E0-\u06E4' + ur'\u06E7-\u06E8\u06EA-\u06ED' + ur'\u0901-\u0903\u093C\u093E-\u094C\u094D\u0951-\u0954\u0962-\u0963' + ur'\u0981-\u0983\u09BC\u09BE\u09BF\u09C0-\u09C4\u09C7-\u09C8' + ur'\u09CB-\u09CD\u09D7\u09E2-\u09E3' + ur'\u0A02\u0A3C\u0A3E\u0A3F\u0A40-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D' + ur'\u0A70-\u0A71\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9' + ur'\u0ACB-\u0ACD' + ur'\u0B01-\u0B03\u0B3C\u0B3E-\u0B43\u0B47-\u0B48\u0B4B-\u0B4D' + ur'\u0B56-\u0B57\u0B82-\u0B83\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD' + ur'\u0BD7' + ur'\u0C01-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56' + ur'\u0C82-\u0C83\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5-\u0CD6' + ur'\u0D02-\u0D03\u0D3E-\u0D43\u0D46-\u0D48\u0D4A-\u0D4D\u0D57' + ur'\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB-\u0EBC' + ur'\u0EC8-\u0ECD' + ur'\u0F18-\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84' + ur'\u0F86-\u0F8B\u0F90-\u0F95\u0F97\u0F99-\u0FAD\u0FB1-\u0FB7\u0FB9' + ur'\u20D0-\u20DC\u20E1\u302A-\u302F\u3099\u309A' + # Extender + ur'\u00B7\u02D0\u02D1\u0387\u0640\u0E46\u0EC6\u3005\u3031-\u3035' + ur'\u309D-\u309E\u30FC-\u30FE' + ) + +# [5] Name ::= (Letter | '_' | ':') (NameChar)* +_xml10_name_re = re.compile( + ur'^[' + _xml10_letter + ur'_:]' + ur'[' + _xml10_name_char + ur']*$') + +del _xml10_letter, _xml10_name_char + + +# --- XML 1.1 Names + +# Matches the NameStartChar production except for characters outside +# the BMP, which are handled by _xml11_wrapper (below). +# +#[4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] +# | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] +# | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] +# | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] +# | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] +_xml11_name_start_char = ( + ur':A-Z_a-z\xC0-\xD6\xD8-\xF6' + ur'\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F' + ur'\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD') + +# Matches the XML 1.1 NameChar production except for characters outside +# the BMP. +# +# [4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 +# | [#x0300-#x036F] | [#x203F-#x2040] +_xml11_name_char = ( + _xml11_name_start_char + ur'\-\.0-9\xB7\u0300-\u036F\u203F\u2040') + +# Wrap a character range to add XML 1.1 name characters that live +# outside the BMP. +if sys.maxunicode == 65535: + _xml11_wrapper = ur'(?:[%s]|[\uD800-\uDB7F][\uDC00-\uDFFF])' +else: + _xml11_wrapper = u'[%s\\U00010000-\\U000EFFFF]' + +# [5] Name ::= NameStartChar (NameChar)* +_xml11_name_re = re.compile( + ur'^' + + (_xml11_wrapper % _xml11_name_start_char) + + (_xml11_wrapper % _xml11_name_char) + ur'*$') + +del _xml11_wrapper, _xml11_name_start_char, _xml11_name_char + + +def _check_name(name, xmlVersion): + if xmlVersion == '1.0': + name_re = _xml10_name_re + elif xmlVersion == '1.1': + name_re = _xml11_name_re + else: + return + if name_re.match(name) is None: + raise xml.dom.InvalidCharacterErr(name) + + def _get_StringIO(): # we can't use cStringIO since it doesn't support Unicode strings from StringIO import StringIO Index: Modules/pyexpat.c =================================================================== --- Modules/pyexpat.c (revision 54883) +++ Modules/pyexpat.c (working copy) @@ -1079,6 +1079,17 @@ return Py_BuildValue("z", XML_GetBase(self->itself)); } +PyDoc_STRVAR(xmlparse_GetSpecifiedAttributeCount__doc__, +"GetSpecifiedAttributeCount() -> int\n\ +Return the number of attributes specified (not defaulted)\n\ +in the most recent StartElementHandler call."); + +static PyObject * +xmlparse_GetSpecifiedAttributeCount(xmlparseobject *self, PyObject *unused) +{ + return PyInt_FromLong(XML_GetSpecifiedAttributeCount(self->itself)); +} + PyDoc_STRVAR(xmlparse_GetInputContext__doc__, "GetInputContext() -> string\n\ Return the untranslated text of the input that caused the current event.\n\ @@ -1251,6 +1262,8 @@ METH_VARARGS, xmlparse_SetBase__doc__}, {"GetBase", (PyCFunction)xmlparse_GetBase, METH_NOARGS, xmlparse_GetBase__doc__}, + {"GetSpecifiedAttributeCount", (PyCFunction)xmlparse_GetSpecifiedAttributeCount, + METH_NOARGS, xmlparse_GetSpecifiedAttributeCount__doc__}, {"ExternalEntityParserCreate", (PyCFunction)xmlparse_ExternalEntityParserCreate, METH_VARARGS, xmlparse_ExternalEntityParserCreate__doc__}, {"SetParamEntityParsing", (PyCFunction)xmlparse_SetParamEntityParsing,