GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Implement pyexpat ordered_attributes by name-of-okja · Pull Request #8305 · RustPython/RustPython

Implement pyexpat ordered_attributes #8305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Sign up for GitHub

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
youknowone merged 1 commit into RustPython:main from name-of-okja:feat/xml-expat-ordered-attributes
Jul 18, 2026

Uh oh!

There was an error while loading. Please reload this page.

Merged

Implement pyexpat ordered_attributes #8305

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
1 commit
Select commit
437b912
Implement pyexpat ordered_attributes
name-of-okja Jul 17, 2026
File filter
Filter by extension

Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view

There are no files selected for viewing

15 changes: 0 additions & 15 deletions 15 Lib/test/test_minidom.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,6 @@ def testAttributeRepr(self):
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)
Expand Down Expand Up @@ -601,7 +600,6 @@ def test_toxml_quote_attrib(self):
'lflf="&#10;&#10;" '
'ws="&#9;&#10;&#13; "/>')

@unittest.expectedFailure # TODO: RUSTPYTHON
def testAltNewline(self):
str = '<?xml version="1.0" ?>\n<a b="c"/>\n'
dom = parseString(str)
Expand Down Expand Up @@ -705,7 +703,6 @@ def testAttrListKeys(self): pass

def testAttrListKeysNS(self): pass

@unittest.expectedFailure # TODO: RUSTPYTHON
def testRemoveNamedItem(self):
doc = parseString("<doc a=''/>")
e = doc.documentElement
Expand All @@ -715,7 +712,6 @@ def testRemoveNamedItem(self):
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
Expand Down Expand Up @@ -788,7 +784,6 @@ def _setupCloneElement(self, deep):
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)
Expand Down Expand Up @@ -940,11 +935,9 @@ def check_clone_attribute(self, deep, testName):
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")

Expand Down Expand Up @@ -1336,7 +1329,6 @@ def checkRenameNodeSharedConstraints(self, doc, node):
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
Expand Down Expand Up @@ -1541,7 +1533,6 @@ def setup():
self.confirm(text is None
and len(elem.childNodes) == 2)

@unittest.expectedFailure # TODO: RUSTPYTHON
def testSchemaType(self):
doc = parseString(
"<!DOCTYPE doc [\n"
Expand Down Expand Up @@ -1575,7 +1566,6 @@ def testSchemaType(self):
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
Expand Down Expand Up @@ -1607,7 +1597,6 @@ def testSetIdAttribute(self):
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"
Expand Down Expand Up @@ -1644,7 +1633,6 @@ def testSetIdAttributeNS(self):
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"
Expand Down Expand Up @@ -1766,21 +1754,18 @@ def testProcessingInstructionNameError(self):
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)
output = io.StringIO()
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)
Expand Down
1 change: 0 additions & 1 deletion 1 Lib/test/test_regrtest.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -2355,7 +2355,6 @@ def test_pass(self):
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
Expand Down
23 changes: 0 additions & 23 deletions 23 Lib/test/test_xml_etree.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,6 @@ def test_cdata(self):
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)
Expand Down Expand Up @@ -508,7 +507,6 @@ def test_makeelement(self):
elem[:] = tuple([subelem])
self.serialize_check(elem, '<tag><subtag key="value" /></tag>')

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_parsefile(self):
# Test parsing from file.

Expand Down Expand Up @@ -688,7 +686,6 @@ def test_initialize_parser_without_target(self):
parser2 = ET.XMLParser()
self.assertIsInstance(parser2.target, ET.TreeBuilder)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_children(self):
# Test Element children iteration

Expand Down Expand Up @@ -1091,7 +1088,6 @@ def test_entity(self):
self.assertEqual(str(cm.exception),
'undefined entity &entity;: line 4, column 10')

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_namespace(self):
# Test namespace issues.

Expand Down Expand Up @@ -1335,7 +1331,6 @@ def test_attlist_default(self):
class IterparseTest(unittest.TestCase):
# Test iterparse interface.

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_basic(self):
iterparse = ET.iterparse

Expand All @@ -1361,7 +1356,6 @@ def test_basic(self):
])
it.close()

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_external_file(self):
with open(SIMPLE_XMLFILE, 'rb') as source:
it = ET.iterparse(source)
Expand All @@ -1374,7 +1368,6 @@ def test_external_file(self):
])
self.assertEqual(it.root.tag, 'root')

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_events(self):
iterparse = ET.iterparse

Expand Down Expand Up @@ -1475,7 +1468,6 @@ def test_nonexistent_file(self):
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)
Expand Down Expand Up @@ -1514,7 +1506,6 @@ def test_resource_warnings_exhausted(self):
del it
gc_collect()

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_close_not_exhausted(self):
iterparse = ET.iterparse

Expand Down Expand Up @@ -2041,7 +2032,6 @@ def _my_loader(self, href, parse):
else:
return None

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_xinclude_default(self):
from xml.etree import ElementInclude
doc = self.xinclude_loader('default.xml')
Expand All @@ -2056,7 +2046,6 @@ def test_xinclude_default(self):
'</root>\n'
'</document>')

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_xinclude(self):
from xml.etree import ElementInclude

Expand Down Expand Up @@ -2121,15 +2110,13 @@ def test_xinclude(self):
' </ns0:include>\n'
'</div>') # C5

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_xinclude_repeated(self):
from xml.etree import ElementInclude

document = self.xinclude_loader("include_c1_repeated.xml")
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

Expand Down Expand Up @@ -2234,7 +2221,6 @@ def check(elem):
elem.set("123", 123)
check(elem) # attribute value

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_bug_xmltoolkit25(self):
# typo in ElementTree.findtext

Expand All @@ -2258,7 +2244,6 @@ def test_bug_xmltoolkitX1(self):
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

Expand Down Expand Up @@ -2345,7 +2330,6 @@ def xmltoolkit63():
xmltoolkit63()
self.assertEqual(sys.getrefcount(None), count)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_bug_200708_newline(self):
# Preserve newlines in attributes.

Expand Down Expand Up @@ -2461,7 +2445,6 @@ def test_issue6233(self):
b"<?xml version='1.0' encoding='ascii'?>\n"
b'<body>t&#227;g</body>')

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_issue6565(self):
elem = ET.XML("<body><tag/></body>")
self.assertEqual(summarize_list(elem), ['tag'])
Expand Down Expand Up @@ -2533,7 +2516,6 @@ def check_expat224_utf8_bug(self, text):
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.
Expand Down Expand Up @@ -3355,7 +3337,6 @@ class MyElement(ET.Element):


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')
Expand All @@ -3379,7 +3360,6 @@ def test_find_simple(self):
# Issue #16922
self.assertEqual(ET.XML('<tag><empty /></tag>').findtext('empty'), '')

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_find_xpath(self):
LINEAR_XML = '''
<body>
Expand All @@ -3402,7 +3382,6 @@ def test_find_xpath(self):
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)
Expand Down Expand Up @@ -3591,7 +3570,6 @@ def test_bad_find(self):
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')
Expand Down Expand Up @@ -4576,7 +4554,6 @@ def test_correct_import_pyET(self):
# --------------------------------------------------------------------

class BoolTest(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_warning(self):
e = ET.fromstring('<a style="new"></a>')
msg = (
Expand Down
36 changes: 24 additions & 12 deletions 36 crates/stdlib/src/pyexpat.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

// 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);
Expand Down
Loading
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.