mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Implement pyexpat ordered_attributes (#8305)
When `ordered_attributes` is set, report attributes to the
StartElementHandler as a flat `[name, value, ...]` list matching
CPython's my_StartElementHandler, instead of always passing a dict.
`xml.etree.ElementTree` and `xml.dom.minidom` (via expatbuilder) both
enable this flag and rely on the flat-list contract, so parsing any
element with attributes previously failed: the dict made `_start` do
integer indexing, raising KeyError which was silently swallowed by the
handler invoker, leaving the tree empty ("missing toplevel element").
Reference (CPython 3.14):
https://github.com/python/cpython/blob/3.14/Modules/pyexpat.c#L439-L443
Remove `@unittest.expectedFailure` from tests that now pass
(23 in test_xml_etree, 15 in test_minidom, 1 in test_regrtest).
Assisted-by: Claude:claude-opus-4-8
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
b052b16e6a
commit
f74e7256a0
4 files changed
+24
-51
No files matched your search
Vendored
-15
@@ -553,7 +553,6 @@ class MinidomTest(unittest.TestCase):
|
||||
self.assertEqual(str(node), repr(node))
|
||||
dom.unlink()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testWriteXML(self):
|
||||
str = '<?xml version="1.0" ?><a b="c"/>'
|
||||
dom = parseString(str)
|
||||
@@ -601,7 +600,6 @@ class MinidomTest(unittest.TestCase):
|
||||
'lflf=" " '
|
||||
'ws="	 "/>')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testAltNewline(self):
|
||||
str = '<?xml version="1.0" ?>\n<a b="c"/>\n'
|
||||
dom = parseString(str)
|
||||
@@ -705,7 +703,6 @@ class MinidomTest(unittest.TestCase):
|
||||
|
||||
def testAttrListKeysNS(self): pass
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testRemoveNamedItem(self):
|
||||
doc = parseString("<doc a=''/>")
|
||||
e = doc.documentElement
|
||||
@@ -715,7 +712,6 @@ class MinidomTest(unittest.TestCase):
|
||||
self.assertTrue(a1.isSameNode(a2))
|
||||
self.assertRaises(xml.dom.NotFoundErr, attrs.removeNamedItem, "a")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testRemoveNamedItemNS(self):
|
||||
doc = parseString("<doc xmlns:a='http://xml.python.org/' a:b=''/>")
|
||||
e = doc.documentElement
|
||||
@@ -788,7 +784,6 @@ class MinidomTest(unittest.TestCase):
|
||||
root.setAttribute("added", "VALUE")
|
||||
return dom, clone
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testCloneElementShallow(self):
|
||||
dom, clone = self._setupCloneElement(0)
|
||||
self.assertEqual(len(clone.childNodes), 0)
|
||||
@@ -940,11 +935,9 @@ class MinidomTest(unittest.TestCase):
|
||||
self.confirm(clone.specified,
|
||||
testName + ": cloned attribute must have specified == True")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testCloneAttributeShallow(self):
|
||||
self.check_clone_attribute(0, "testCloneAttributeShallow")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testCloneAttributeDeep(self):
|
||||
self.check_clone_attribute(1, "testCloneAttributeDeep")
|
||||
|
||||
@@ -1336,7 +1329,6 @@ class MinidomTest(unittest.TestCase):
|
||||
self.assertRaises(xml.dom.WrongDocumentErr, doc2.renameNode, node,
|
||||
xml.dom.EMPTY_NAMESPACE, "foo")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testRenameAttribute(self):
|
||||
doc = parseString("<doc a='v'/>")
|
||||
elem = doc.documentElement
|
||||
@@ -1541,7 +1533,6 @@ class MinidomTest(unittest.TestCase):
|
||||
self.confirm(text is None
|
||||
and len(elem.childNodes) == 2)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testSchemaType(self):
|
||||
doc = parseString(
|
||||
"<!DOCTYPE doc [\n"
|
||||
@@ -1575,7 +1566,6 @@ class MinidomTest(unittest.TestCase):
|
||||
self.confirm(hasattr(t, "name")
|
||||
and t.namespace == xml.dom.EMPTY_NAMESPACE)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testSetIdAttribute(self):
|
||||
doc = parseString("<doc a1='v' a2='w'/>")
|
||||
e = doc.documentElement
|
||||
@@ -1607,7 +1597,6 @@ class MinidomTest(unittest.TestCase):
|
||||
self.confirm(e.isSameNode(doc.getElementById("w"))
|
||||
and a2.isId)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testSetIdAttributeNS(self):
|
||||
NS1 = "http://xml.python.org/ns1"
|
||||
NS2 = "http://xml.python.org/ns2"
|
||||
@@ -1644,7 +1633,6 @@ class MinidomTest(unittest.TestCase):
|
||||
self.confirm(e.isSameNode(doc.getElementById("w"))
|
||||
and a2.isId)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testSetIdAttributeNode(self):
|
||||
NS1 = "http://xml.python.org/ns1"
|
||||
NS2 = "http://xml.python.org/ns2"
|
||||
@@ -1766,7 +1754,6 @@ class MinidomTest(unittest.TestCase):
|
||||
pi = doc.createProcessingInstruction("y", "z")
|
||||
pi.nodeValue = "crash"
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_minidom_attribute_order(self):
|
||||
xml_str = '<?xml version="1.0" ?><curriculum status="public" company="example"/>'
|
||||
doc = parseString(xml_str)
|
||||
@@ -1774,13 +1761,11 @@ class MinidomTest(unittest.TestCase):
|
||||
doc.writexml(output)
|
||||
self.assertEqual(output.getvalue(), xml_str)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_toxml_with_attributes_ordered(self):
|
||||
xml_str = '<?xml version="1.0" ?><curriculum status="public" company="example"/>'
|
||||
doc = parseString(xml_str)
|
||||
self.assertEqual(doc.toxml(), xml_str)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_toprettyxml_with_attributes_ordered(self):
|
||||
xml_str = '<?xml version="1.0" ?><curriculum status="public" company="example"/>'
|
||||
doc = parseString(xml_str)
|
||||
|
||||
Vendored
-1
@@ -2355,7 +2355,6 @@ class ArgsTestCase(BaseTestCase):
|
||||
self.check_executed_tests(output, testname, stats=1, parallel=True)
|
||||
self.assertNotIn('SPAM SPAM SPAM', output)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
|
||||
def test_xml(self):
|
||||
code = textwrap.dedent(r"""
|
||||
import unittest
|
||||
|
||||
Vendored
-23
@@ -392,7 +392,6 @@ class ElementTreeTest(unittest.TestCase):
|
||||
self.serialize_check(ET.XML("<tag><![CDATA[hello]]></tag>"),
|
||||
'<tag>hello</tag>')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_file_init(self):
|
||||
stringfile = io.BytesIO(SAMPLE_XML.encode("utf-8"))
|
||||
tree = ET.ElementTree(file=stringfile)
|
||||
@@ -508,7 +507,6 @@ class ElementTreeTest(unittest.TestCase):
|
||||
elem[:] = tuple([subelem])
|
||||
self.serialize_check(elem, '<tag><subtag key="value" /></tag>')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_parsefile(self):
|
||||
# Test parsing from file.
|
||||
|
||||
@@ -688,7 +686,6 @@ class ElementTreeTest(unittest.TestCase):
|
||||
parser2 = ET.XMLParser()
|
||||
self.assertIsInstance(parser2.target, ET.TreeBuilder)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_children(self):
|
||||
# Test Element children iteration
|
||||
|
||||
@@ -1091,7 +1088,6 @@ class ElementTreeTest(unittest.TestCase):
|
||||
self.assertEqual(str(cm.exception),
|
||||
'undefined entity &entity;: line 4, column 10')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_namespace(self):
|
||||
# Test namespace issues.
|
||||
|
||||
@@ -1335,7 +1331,6 @@ class ElementTreeTest(unittest.TestCase):
|
||||
class IterparseTest(unittest.TestCase):
|
||||
# Test iterparse interface.
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_basic(self):
|
||||
iterparse = ET.iterparse
|
||||
|
||||
@@ -1361,7 +1356,6 @@ class IterparseTest(unittest.TestCase):
|
||||
])
|
||||
it.close()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_external_file(self):
|
||||
with open(SIMPLE_XMLFILE, 'rb') as source:
|
||||
it = ET.iterparse(source)
|
||||
@@ -1374,7 +1368,6 @@ class IterparseTest(unittest.TestCase):
|
||||
])
|
||||
self.assertEqual(it.root.tag, 'root')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_events(self):
|
||||
iterparse = ET.iterparse
|
||||
|
||||
@@ -1475,7 +1468,6 @@ class IterparseTest(unittest.TestCase):
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
ET.iterparse("nonexistent")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_resource_warnings_not_exhausted(self):
|
||||
# Not exhausting the iterator still closes the underlying file (bpo-43292)
|
||||
it = ET.iterparse(SIMPLE_XMLFILE)
|
||||
@@ -1514,7 +1506,6 @@ class IterparseTest(unittest.TestCase):
|
||||
del it
|
||||
gc_collect()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_close_not_exhausted(self):
|
||||
iterparse = ET.iterparse
|
||||
|
||||
@@ -2041,7 +2032,6 @@ class XIncludeTest(unittest.TestCase):
|
||||
else:
|
||||
return None
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_xinclude_default(self):
|
||||
from xml.etree import ElementInclude
|
||||
doc = self.xinclude_loader('default.xml')
|
||||
@@ -2056,7 +2046,6 @@ class XIncludeTest(unittest.TestCase):
|
||||
'</root>\n'
|
||||
'</document>')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_xinclude(self):
|
||||
from xml.etree import ElementInclude
|
||||
|
||||
@@ -2121,7 +2110,6 @@ class XIncludeTest(unittest.TestCase):
|
||||
' </ns0:include>\n'
|
||||
'</div>') # C5
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_xinclude_repeated(self):
|
||||
from xml.etree import ElementInclude
|
||||
|
||||
@@ -2129,7 +2117,6 @@ class XIncludeTest(unittest.TestCase):
|
||||
ElementInclude.include(document, self.xinclude_loader)
|
||||
self.assertEqual(1+4*2, len(document.findall(".//p")))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_xinclude_failures(self):
|
||||
from xml.etree import ElementInclude
|
||||
|
||||
@@ -2234,7 +2221,6 @@ class BugsTest(unittest.TestCase):
|
||||
elem.set("123", 123)
|
||||
check(elem) # attribute value
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_bug_xmltoolkit25(self):
|
||||
# typo in ElementTree.findtext
|
||||
|
||||
@@ -2258,7 +2244,6 @@ class BugsTest(unittest.TestCase):
|
||||
ET.dump(tree)
|
||||
self.assertEqual(stdout.getvalue(), '<doc><table><tbody /></table></doc>\n')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_bug_xmltoolkit39(self):
|
||||
# non-ascii element and attribute names doesn't work
|
||||
|
||||
@@ -2345,7 +2330,6 @@ class BugsTest(unittest.TestCase):
|
||||
xmltoolkit63()
|
||||
self.assertEqual(sys.getrefcount(None), count)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_bug_200708_newline(self):
|
||||
# Preserve newlines in attributes.
|
||||
|
||||
@@ -2461,7 +2445,6 @@ class BugsTest(unittest.TestCase):
|
||||
b"<?xml version='1.0' encoding='ascii'?>\n"
|
||||
b'<body>tãg</body>')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_issue6565(self):
|
||||
elem = ET.XML("<body><tag/></body>")
|
||||
self.assertEqual(summarize_list(elem), ['tag'])
|
||||
@@ -2533,7 +2516,6 @@ class BugsTest(unittest.TestCase):
|
||||
root = ET.XML(xml)
|
||||
self.assertEqual(root.get('b'), text.decode('utf-8'))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_expat224_utf8_bug(self):
|
||||
# bpo-31170: Expat 2.2.3 had a bug in its UTF-8 decoder.
|
||||
# Check that Expat 2.2.4 fixed the bug.
|
||||
@@ -3355,7 +3337,6 @@ class ElementTreeTypeTest(unittest.TestCase):
|
||||
|
||||
|
||||
class ElementFindTest(unittest.TestCase):
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_find_simple(self):
|
||||
e = ET.XML(SAMPLE_XML)
|
||||
self.assertEqual(e.find('tag').tag, 'tag')
|
||||
@@ -3379,7 +3360,6 @@ class ElementFindTest(unittest.TestCase):
|
||||
# Issue #16922
|
||||
self.assertEqual(ET.XML('<tag><empty /></tag>').findtext('empty'), '')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_find_xpath(self):
|
||||
LINEAR_XML = '''
|
||||
<body>
|
||||
@@ -3402,7 +3382,6 @@ class ElementFindTest(unittest.TestCase):
|
||||
self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()-0]')
|
||||
self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()+1]')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_findall(self):
|
||||
e = ET.XML(SAMPLE_XML)
|
||||
e[2] = ET.XML(SAMPLE_SECTION)
|
||||
@@ -3591,7 +3570,6 @@ class ElementFindTest(unittest.TestCase):
|
||||
with self.assertRaisesRegex(SyntaxError, 'cannot use absolute path'):
|
||||
e.findall('/tag')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_find_through_ElementTree(self):
|
||||
e = ET.XML(SAMPLE_XML)
|
||||
self.assertEqual(ET.ElementTree(e).find('tag').tag, 'tag')
|
||||
@@ -4576,7 +4554,6 @@ class NoAcceleratorTest(unittest.TestCase):
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
class BoolTest(unittest.TestCase):
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_warning(self):
|
||||
e = ET.fromstring('<a style="new"></a>')
|
||||
msg = (
|
||||
|
||||
@@ -43,7 +43,8 @@ macro_rules! create_bool_property {
|
||||
#[pymodule(name = "pyexpat")]
|
||||
mod _pyexpat {
|
||||
use crate::vm::{
|
||||
Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine,
|
||||
AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
|
||||
VirtualMachine,
|
||||
builtins::{PyBytesRef, PyException, PyModule, PyStr, PyStrRef, PyType, PyUtf8StrRef},
|
||||
extend_module,
|
||||
function::{ArgBytesLike, ArgPrimitiveIndex, Either, IntoFuncArgs, OptionalArg},
|
||||
@@ -355,19 +356,30 @@ mod _pyexpat {
|
||||
XmlEvent::StartElement {
|
||||
name, attributes, ..
|
||||
} => {
|
||||
let dict = vm.ctx.new_dict();
|
||||
for attribute in attributes {
|
||||
let attr_name = self.make_name(&attribute.name);
|
||||
dict.set_item(
|
||||
attr_name.as_str(),
|
||||
vm.ctx.new_str(attribute.value).into(),
|
||||
vm,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let ordered = self.ordered_attributes.read().is(&vm.ctx.true_value);
|
||||
// Build the container.
|
||||
let attrs: PyObjectRef = if ordered {
|
||||
let mut items = Vec::with_capacity(attributes.len() * 2);
|
||||
for attribute in attributes {
|
||||
items.push(vm.ctx.new_str(self.make_name(&attribute.name)).into());
|
||||
items.push(vm.ctx.new_str(attribute.value).into());
|
||||
}
|
||||
vm.ctx.new_list(items).into()
|
||||
} else {
|
||||
let dict = vm.ctx.new_dict();
|
||||
for attribute in attributes {
|
||||
dict.set_item(
|
||||
self.make_name(&attribute.name).as_str(),
|
||||
vm.ctx.new_str(attribute.value).into(),
|
||||
vm,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
dict.into()
|
||||
};
|
||||
|
||||
let name_str = PyStr::from(self.make_name(&name)).into_ref(&vm.ctx);
|
||||
invoke_handler(vm, &self.start_element, (name_str, dict));
|
||||
invoke_handler(vm, &self.start_element, (name_str, attrs));
|
||||
}
|
||||
XmlEvent::EndElement { name, .. } => {
|
||||
let name_str = PyStr::from(self.make_name(&name)).into_ref(&vm.ctx);
|
||||
|
||||
Reference in new issue
Block a user