Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions Lib/test/test_sax.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ def test_parse_bytes(self):
with self.assertRaises(SAXException):
self.check_parse(f)

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'SetParamEntityParsing'
def test_parse_path_object(self):
make_xml_file(self.data, 'utf-8', None)
self.check_parse(FakePath(TESTFN))
Expand Down Expand Up @@ -1018,7 +1017,6 @@ def test_expat_external_dtd_enabled(self):
resolver.entities, [(None, 'unsupported://non-existing')]
)

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'SetParamEntityParsing'
def test_expat_external_dtd_default(self):
parser = create_parser()
resolver = self.TestEntityRecorder()
Expand Down Expand Up @@ -1084,7 +1082,6 @@ def startElement(self, name, attrs):
def startElementNS(self, name, qname, attrs):
self._attrs = attrs

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'SetParamEntityParsing'
def test_expat_attrs_empty(self):
parser = create_parser()
gather = self.AttrGatherer()
Expand All @@ -1095,7 +1092,6 @@ def test_expat_attrs_empty(self):

self.verify_empty_attrs(gather._attrs)

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'SetParamEntityParsing'
def test_expat_attrs_wattr(self):
parser = create_parser()
gather = self.AttrGatherer()
Expand All @@ -1106,7 +1102,6 @@ def test_expat_attrs_wattr(self):

self.verify_attrs_wattr(gather._attrs)

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'SetParamEntityParsing'
def test_expat_nsattrs_empty(self):
parser = create_parser(1)
gather = self.AttrGatherer()
Expand Down Expand Up @@ -1300,7 +1295,6 @@ def test_flush_reparse_deferral_disabled(self):

# ===== Locator support

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'SetParamEntityParsing'
def test_expat_locator_noinfo(self):
result = BytesIO()
xmlgen = XMLGenerator(result)
Expand All @@ -1315,7 +1309,6 @@ def test_expat_locator_noinfo(self):
self.assertEqual(parser.getPublicId(), None)
self.assertEqual(parser.getLineNumber(), 1)

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'SetParamEntityParsing'
def test_expat_locator_withinfo(self):
result = BytesIO()
xmlgen = XMLGenerator(result)
Expand All @@ -1326,7 +1319,6 @@ def test_expat_locator_withinfo(self):
self.assertEqual(parser.getSystemId(), TEST_XMLFILE)
self.assertEqual(parser.getPublicId(), None)

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'SetParamEntityParsing'
@requires_nonascii_filenames
def test_expat_locator_withinfo_nonascii(self):
fname = os_helper.TESTFN_UNICODE
Expand Down
35 changes: 34 additions & 1 deletion crates/stdlib/src/pyexpat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ mod _pyexpat {
Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine,
builtins::{PyBytesRef, PyException, PyModule, PyStr, PyStrRef, PyType, PyUtf8StrRef},
extend_module,
function::{ArgBytesLike, Either, IntoFuncArgs, OptionalArg},
function::{ArgBytesLike, ArgPrimitiveIndex, Either, IntoFuncArgs, OptionalArg},
types::Constructor,
};
use rustpython_common::lock::PyRwLock;
Expand All @@ -70,13 +70,22 @@ mod _pyexpat {
#[pyattr(name = "version_info")]
pub(super) const VERSION_INFO: (u32, u32, u32) = (2, 7, 1);

#[pyattr]
const XML_PARAM_ENTITY_PARSING_NEVER: i32 = 0;
#[pyattr]
const XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE: i32 = 1;
#[pyattr]
const XML_PARAM_ENTITY_PARSING_ALWAYS: i32 = 2;

#[pyattr]
#[pyattr(name = "XMLParserType")]
#[pyclass(name = "xmlparser", module = false, traverse)]
#[derive(Debug, PyPayload)]
pub(super) struct PyExpatLikeXmlParser {
#[pytraverse(skip)]
namespace_separator: Option<String>,
#[pytraverse(skip)]
base: PyRwLock<Option<String>>,
start_element: MutableObject,
end_element: MutableObject,
character_data: MutableObject,
Expand Down Expand Up @@ -128,6 +137,7 @@ mod _pyexpat {
let intern_dict = intern.unwrap_or_else(|| vm.ctx.new_dict().into());
Self {
namespace_separator,
base: PyRwLock::new(None),
start_element: MutableObject::new(vm.ctx.none()),
end_element: MutableObject::new(vm.ctx.none()),
character_data: MutableObject::new(vm.ctx.none()),
Expand Down Expand Up @@ -297,6 +307,29 @@ mod _pyexpat {
.whitespace_to_characters(true)
}

#[pymethod(name = "SetParamEntityParsing")]
fn set_param_entity_parsing(&self, _flag: ArgPrimitiveIndex<i32>) -> i32 {
// Compatibility shim: xml.sax requires this setup API, but xml-rs
// does not expose Expat parameter entity parsing configuration.
1
}

#[pymethod(name = "SetBase")]
fn set_base(&self, base: PyStrRef) {
// Store-only compatibility state for xml.sax locator APIs. The
// xml-rs backend still does not perform Expat-style base URI
// resolution for external entities.
*self.base.write() = Some(AsRef::<str>::as_ref(&base).to_owned());
}

#[pymethod(name = "GetBase")]
fn get_base(&self, vm: &VirtualMachine) -> PyObjectRef {
self.base.read().as_ref().map_or_else(
|| vm.ctx.none(),
|base| vm.ctx.new_str(base.as_str()).into(),
)
}

/// Construct element name with namespace if separator is set
fn make_name(&self, name: &xml::name::OwnedName) -> String {
match (&self.namespace_separator, &name.namespace) {
Expand Down
48 changes: 48 additions & 0 deletions extra_tests/snippets/stdlib_xml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import xml.sax
from xml.parsers import expat

from testutils import assert_raises


assert expat.XML_PARAM_ENTITY_PARSING_NEVER == 0
assert expat.XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE == 1
assert expat.XML_PARAM_ENTITY_PARSING_ALWAYS == 2

parser = expat.ParserCreate()
for value in (0, 1, 2, 3, -1, True):
assert parser.SetParamEntityParsing(value) == 1

for value in ("x", None):
with assert_raises(TypeError):
parser.SetParamEntityParsing(value)

with assert_raises(OverflowError):
parser.SetParamEntityParsing(2**100)

assert parser.GetBase() is None
assert parser.SetBase("example.xml") is None
assert parser.GetBase() == "example.xml"
for value in (b"example.xml", None, 123):
with assert_raises(TypeError):
parser.SetBase(value)


class Handler(xml.sax.handler.ContentHandler):
def __init__(self):
self.events = []

def startElement(self, name, attrs):
self.events.append(("start", name))

def endElement(self, name):
self.events.append(("end", name))


handler = Handler()
xml.sax.parseString("<main><child /></main>", handler)
assert handler.events == [
("start", "main"),
("start", "child"),
("end", "child"),
("end", "main"),
]
Loading