Forward pyexpat lexical events (#8264)

* Restore pyexpat lexical callback compatibility

Forward processing instructions, comments, and CDATA boundaries already emitted by xml-rs so registered Python handlers observe CPython-compatible events.

Constraint: Reuse existing xml-rs events without changing the dependency
Confidence: high
Scope-risk: narrow
Tested: prek; test_pyexpat; test_sax; 402 extra tests; workspace tests; clippy
Assisted-by: Codex:gpt-5.6-sol

* Recognize XML compatibility restored by lexical events

Remove stale expected-failure markers for CPython XML tests now satisfied by pyexpat lexical-event forwarding, and simplify parser event error propagation.

Constraint: CPython test edits are limited to removing stale expectedFailure markers
Confidence: high
Scope-risk: narrow
Directive: Keep parser event dispatch aligned with pyexpat handler behavior
Tested: user-verified full suite; prek run --all-files; cargo run --release -- -m test test_pyexpat test_xml_etree test_xml_etree_c test_minidom test_sax
Assisted-by: Codex:gpt-5.6-sol
This commit is contained in:
Kwak Byoung Min authored and GitHub committed 2026-07-13 18:40:18 +09:00
1 parent 8e2872538b
commit fe7993572c
5 files changed
+43 -15

No files matched your search

-5
View File
@@ -659,7 +659,6 @@ class MinidomTest(unittest.TestCase):
self.assertIsNone(pi.localName)
self.assertEqual(pi.namespaceURI, xml.dom.EMPTY_NAMESPACE)
@unittest.expectedFailure # TODO: RUSTPYTHON
def testProcessingInstructionRepr(self):
dom = parseString('<e><?mypi \t\n data \t\n ?></e>')
pi = dom.documentElement.firstChild
@@ -957,11 +956,9 @@ class MinidomTest(unittest.TestCase):
self.confirm(clone.target == pi.target
and clone.data == pi.data)
@unittest.expectedFailure # TODO: RUSTPYTHON
def testClonePIShallow(self):
self.check_clone_pi(0, "testClonePIShallow")
@unittest.expectedFailure # TODO: RUSTPYTHON
def testClonePIDeep(self):
self.check_clone_pi(1, "testClonePIDeep")
@@ -1219,7 +1216,6 @@ class MinidomTest(unittest.TestCase):
self.assertIsNone(node.childNodes[-1].nextSibling,
"Final child's .nextSibling should be None")
@unittest.expectedFailure # TODO: RUSTPYTHON
def testSiblings(self):
doc = parseString("<doc><?pi?>text?<elm/></doc>")
root = doc.documentElement
@@ -1792,7 +1788,6 @@ class MinidomTest(unittest.TestCase):
'<?xml version="1.0" ?>\n'
'<curriculum status="public" company="example"/>\n')
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_toprettyxml_with_cdata(self):
xml_str = '<?xml version="1.0" ?><root><node><![CDATA[</data>]]></node></root>'
doc = parseString(xml_str)
-1
View File
@@ -560,7 +560,6 @@ class BufferTextTest(unittest.TestCase):
["<a>", "1", "<b>", "</b>", "2", "<c>", "</c>", "345", "</a>"],
"buffered text not properly split")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test7(self):
self.setHandlers(["CommentHandler", "EndElementHandler",
"StartElementHandler"])
-2
View File
@@ -1800,7 +1800,6 @@ class XMLPullParserTest(unittest.TestCase):
self._feed(parser, "<!-- text here -->\n")
self.assert_events(parser, [('comment', (ET.Comment, ' text here '))])
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_events_pi(self):
parser = ET.XMLPullParser(events=('start', 'pi', 'end'))
self._feed(parser, "<?pitarget?>\n")
@@ -3821,7 +3820,6 @@ class TreeBuilderTest(unittest.TestCase):
a = parser.close()
self.assertEqual(a.text, "texttail")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_late_tail_mix_pi_comments(self):
# Issue #37399: The tail of an ignored comment could overwrite the text before it.
# Test appending tails to comments/pis.
+22 -7
View File
@@ -305,8 +305,9 @@ mod _pyexpat {
fn create_config(&self) -> xml::ParserConfig {
xml::ParserConfig::new()
.cdata_to_characters(true)
.cdata_to_characters(false)
.coalesce_characters(false)
.ignore_comments(false)
.whitespace_to_characters(true)
}
@@ -350,10 +351,10 @@ mod _pyexpat {
T: std::io::Read,
{
for e in parser {
match e {
Ok(XmlEvent::StartElement {
match e? {
XmlEvent::StartElement {
name, attributes, ..
}) => {
} => {
let dict = vm.ctx.new_dict();
for attribute in attributes {
let attr_name = self.make_name(&attribute.name);
@@ -368,15 +369,29 @@ mod _pyexpat {
let name_str = PyStr::from(self.make_name(&name)).into_ref(&vm.ctx);
invoke_handler(vm, &self.start_element, (name_str, dict));
}
Ok(XmlEvent::EndElement { name, .. }) => {
XmlEvent::EndElement { name, .. } => {
let name_str = PyStr::from(self.make_name(&name)).into_ref(&vm.ctx);
invoke_handler(vm, &self.end_element, (name_str,));
}
Ok(XmlEvent::Characters(chars)) => {
XmlEvent::Characters(chars) => {
let str = PyStr::from(chars).into_ref(&vm.ctx);
invoke_handler(vm, &self.character_data, (str,));
}
Err(e) => return Err(e),
XmlEvent::ProcessingInstruction { name, data } => {
let name = PyStr::from(name).into_ref(&vm.ctx);
let data = PyStr::from(data.unwrap_or_default()).into_ref(&vm.ctx);
invoke_handler(vm, &self.processing_instruction, (name, data));
}
XmlEvent::Comment(comment) => {
let comment = PyStr::from(comment).into_ref(&vm.ctx);
invoke_handler(vm, &self.comment, (comment,));
}
XmlEvent::CData(chars) => {
invoke_handler(vm, &self.start_cdata_section, ());
let str = PyStr::from(chars).into_ref(&vm.ctx);
invoke_handler(vm, &self.character_data, (str,));
invoke_handler(vm, &self.end_cdata_section, ());
}
_ => {}
}
}
+21
View File
@@ -45,3 +45,24 @@ assert handler.events == [
("end", "child"),
("end", "main"),
]
events = []
parser = expat.ParserCreate()
parser.ProcessingInstructionHandler = lambda target, data: events.append(
("processing-instruction", target, data)
)
parser.CommentHandler = lambda data: events.append(("comment", data))
parser.StartCdataSectionHandler = lambda: events.append(("start-cdata",))
parser.CharacterDataHandler = lambda data: events.append(("characters", data))
parser.EndCdataSectionHandler = lambda: events.append(("end-cdata",))
parser.Parse(
"<?target data?><?empty?><root><!--comment--><![CDATA[text]]></root>", True
)
assert events == [
("processing-instruction", "target", "data"),
("processing-instruction", "empty", ""),
("comment", "comment"),
("start-cdata",),
("characters", "text"),
("end-cdata",),
]