Add missing xmlparser attributes: namespace_prefixes, ordered_attributes, specified_attributes, intern (#6494)

* Add namespace_prefixes and other missing xmlparser attributes

- Added namespace_prefixes, ordered_attributes, specified_attributes (boolean attributes)
- Added intern dictionary attribute
- Added stub handlers for all missing handler types to ensure compatibility
- Created bool_property macro to ensure boolean attributes are converted correctly

* Remove expectedFailure decorators from passing tests

- Tests for buffer_text, namespace_prefixes, ordered_attributes, and specified_attributes now pass

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2025-12-25 09:09:58 +09:00
1 parent be9e44aafb
commit 72cf6c36d5
2 files changed
+182 -7

No files matched your search

-6
View File
@@ -20,28 +20,24 @@ class SetAttributeTest(unittest.TestCase):
def setUp(self):
self.parser = expat.ParserCreate(namespace_separator='!')
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_buffer_text(self):
self.assertIs(self.parser.buffer_text, False)
for x in 0, 1, 2, 0:
self.parser.buffer_text = x
self.assertIs(self.parser.buffer_text, bool(x))
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_namespace_prefixes(self):
self.assertIs(self.parser.namespace_prefixes, False)
for x in 0, 1, 2, 0:
self.parser.namespace_prefixes = x
self.assertIs(self.parser.namespace_prefixes, bool(x))
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_ordered_attributes(self):
self.assertIs(self.parser.ordered_attributes, False)
for x in 0, 1, 2, 0:
self.parser.ordered_attributes = x
self.assertIs(self.parser.ordered_attributes, bool(x))
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_specified_attributes(self):
self.assertIs(self.parser.specified_attributes, False)
for x in 0, 1, 2, 0:
@@ -244,7 +240,6 @@ class ParseTest(unittest.TestCase):
# Issue #6697.
self.assertRaises(AttributeError, getattr, parser, '\uD800')
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_parse_str(self):
out = self.Outputter()
parser = expat.ParserCreate(namespace_separator='!')
@@ -255,7 +250,6 @@ class ParseTest(unittest.TestCase):
operations = out.out
self._verify_parse_output(operations)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_parse_file(self):
# Try parsing a file
out = self.Outputter()
+182 -1
View File
@@ -25,6 +25,26 @@ macro_rules! create_property {
};
}
macro_rules! create_bool_property {
($ctx: expr, $attributes: expr, $name: expr, $class: expr, $element: ident) => {
let attr = $ctx.new_static_getset(
$name,
$class,
move |this: &PyExpatLikeXmlParser| this.$element.read().clone(),
move |this: &PyExpatLikeXmlParser,
value: PyObjectRef,
vm: &VirtualMachine|
-> PyResult<()> {
let bool_value = value.is_true(vm)?;
*this.$element.write() = vm.ctx.new_bool(bool_value).into();
Ok(())
},
);
$attributes.insert($ctx.intern_str($name), attr.into());
};
}
#[pymodule(name = "pyexpat")]
mod _pyexpat {
use crate::vm::{
@@ -51,6 +71,29 @@ mod _pyexpat {
character_data: MutableObject,
entity_decl: MutableObject,
buffer_text: MutableObject,
namespace_prefixes: MutableObject,
ordered_attributes: MutableObject,
specified_attributes: MutableObject,
intern: MutableObject,
// Additional handlers (stubs for compatibility)
processing_instruction: MutableObject,
unparsed_entity_decl: MutableObject,
notation_decl: MutableObject,
start_namespace_decl: MutableObject,
end_namespace_decl: MutableObject,
comment: MutableObject,
start_cdata_section: MutableObject,
end_cdata_section: MutableObject,
default: MutableObject,
default_expand: MutableObject,
not_standalone: MutableObject,
external_entity_ref: MutableObject,
start_doctype_decl: MutableObject,
end_doctype_decl: MutableObject,
xml_decl: MutableObject,
element_decl: MutableObject,
attlist_decl: MutableObject,
skipped_entity: MutableObject,
}
type PyExpatLikeXmlParserRef = PyRef<PyExpatLikeXmlParser>;
@@ -71,6 +114,31 @@ mod _pyexpat {
character_data: MutableObject::new(vm.ctx.none()),
entity_decl: MutableObject::new(vm.ctx.none()),
buffer_text: MutableObject::new(vm.ctx.new_bool(false).into()),
namespace_prefixes: MutableObject::new(vm.ctx.new_bool(false).into()),
ordered_attributes: MutableObject::new(vm.ctx.new_bool(false).into()),
specified_attributes: MutableObject::new(vm.ctx.new_bool(false).into()),
// String interning dictionary - used by the parser to intern element/attribute names
// for memory efficiency and faster comparisons. See CPython's pyexpat documentation.
intern: MutableObject::new(vm.ctx.new_dict().into()),
// Additional handlers (stubs for compatibility)
processing_instruction: MutableObject::new(vm.ctx.none()),
unparsed_entity_decl: MutableObject::new(vm.ctx.none()),
notation_decl: MutableObject::new(vm.ctx.none()),
start_namespace_decl: MutableObject::new(vm.ctx.none()),
end_namespace_decl: MutableObject::new(vm.ctx.none()),
comment: MutableObject::new(vm.ctx.none()),
start_cdata_section: MutableObject::new(vm.ctx.none()),
end_cdata_section: MutableObject::new(vm.ctx.none()),
default: MutableObject::new(vm.ctx.none()),
default_expand: MutableObject::new(vm.ctx.none()),
not_standalone: MutableObject::new(vm.ctx.none()),
external_entity_ref: MutableObject::new(vm.ctx.none()),
start_doctype_decl: MutableObject::new(vm.ctx.none()),
end_doctype_decl: MutableObject::new(vm.ctx.none()),
xml_decl: MutableObject::new(vm.ctx.none()),
element_decl: MutableObject::new(vm.ctx.none()),
attlist_decl: MutableObject::new(vm.ctx.none()),
skipped_entity: MutableObject::new(vm.ctx.none()),
}
.into_ref(&vm.ctx))
}
@@ -89,7 +157,120 @@ mod _pyexpat {
character_data
);
create_property!(ctx, attributes, "EntityDeclHandler", class, entity_decl);
create_property!(ctx, attributes, "buffer_text", class, buffer_text);
create_bool_property!(ctx, attributes, "buffer_text", class, buffer_text);
create_bool_property!(
ctx,
attributes,
"namespace_prefixes",
class,
namespace_prefixes
);
create_bool_property!(
ctx,
attributes,
"ordered_attributes",
class,
ordered_attributes
);
create_bool_property!(
ctx,
attributes,
"specified_attributes",
class,
specified_attributes
);
create_property!(ctx, attributes, "intern", class, intern);
// Additional handlers (stubs for compatibility)
create_property!(
ctx,
attributes,
"ProcessingInstructionHandler",
class,
processing_instruction
);
create_property!(
ctx,
attributes,
"UnparsedEntityDeclHandler",
class,
unparsed_entity_decl
);
create_property!(ctx, attributes, "NotationDeclHandler", class, notation_decl);
create_property!(
ctx,
attributes,
"StartNamespaceDeclHandler",
class,
start_namespace_decl
);
create_property!(
ctx,
attributes,
"EndNamespaceDeclHandler",
class,
end_namespace_decl
);
create_property!(ctx, attributes, "CommentHandler", class, comment);
create_property!(
ctx,
attributes,
"StartCdataSectionHandler",
class,
start_cdata_section
);
create_property!(
ctx,
attributes,
"EndCdataSectionHandler",
class,
end_cdata_section
);
create_property!(ctx, attributes, "DefaultHandler", class, default);
create_property!(
ctx,
attributes,
"DefaultHandlerExpand",
class,
default_expand
);
create_property!(
ctx,
attributes,
"NotStandaloneHandler",
class,
not_standalone
);
create_property!(
ctx,
attributes,
"ExternalEntityRefHandler",
class,
external_entity_ref
);
create_property!(
ctx,
attributes,
"StartDoctypeDeclHandler",
class,
start_doctype_decl
);
create_property!(
ctx,
attributes,
"EndDoctypeDeclHandler",
class,
end_doctype_decl
);
create_property!(ctx, attributes, "XmlDeclHandler", class, xml_decl);
create_property!(ctx, attributes, "ElementDeclHandler", class, element_decl);
create_property!(ctx, attributes, "AttlistDeclHandler", class, attlist_decl);
create_property!(
ctx,
attributes,
"SkippedEntityHandler",
class,
skipped_entity
);
}
fn create_config(&self) -> xml::ParserConfig {