xso: make Context use a builder-like pattern

That way, we can (hopefully) add more fields in the future without
breaking stuff.

I don't yet know what such fields could be, though.
This commit is contained in:
Jonas Schäfer 2025-04-27 13:21:01 +02:00
commit cffb5e7e65
2 changed files with 46 additions and 18 deletions

View file

@ -648,7 +648,8 @@ impl<T: FromXml> ReadXsoState<T> {
} }
Ok(Some(rxml::Event::StartElement(_, name, attrs))) => { Ok(Some(rxml::Event::StartElement(_, name, attrs))) => {
let source_tmp = source.as_mut(); let source_tmp = source.as_mut();
let ctx = xso::Context::new(source_tmp.lang_stack.current()); let ctx = xso::Context::empty()
.with_language(source_tmp.lang_stack.current());
*self = ReadXsoState::Parsing( *self = ReadXsoState::Parsing(
<Result<T, xso::error::Error> as FromXml>::from_events( <Result<T, xso::error::Error> as FromXml>::from_events(
name, attrs, &ctx, name, attrs, &ctx,
@ -708,7 +709,7 @@ impl<T: FromXml> ReadXsoState<T> {
}; };
let source_tmp = source.as_mut(); let source_tmp = source.as_mut();
let ctx = xso::Context::new(source_tmp.lang_stack.current()); let ctx = xso::Context::empty().with_language(source_tmp.lang_stack.current());
match builder.feed(ev, &ctx) { match builder.feed(ev, &ctx) {
Err(err) => { Err(err) => {
*self = ReadXsoState::Done; *self = ReadXsoState::Done;

View file

@ -150,24 +150,33 @@ pub trait AsXml {
fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>; fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;
} }
/// Additional parsing context supplied to [`FromEventsBuilder`] /// # Parsing context for [`FromEventsBuilder`]
/// implementations. ///
/// For the most part, [`FromEventsBuilder`] implementations can work with
/// only the information inside the [`rxml::Event`] which is delivered to
/// them (and any information they may have stored from previous events).
///
/// However, there is (currently) one special case: the `xml:lang` attribute.
/// That attribute is inherited across the entire document tree hierarchy. If
/// the parsed element is not the top-level element, there may be an implicit
/// value for `xml:lang`.
#[derive(Debug)]
pub struct Context<'x> { pub struct Context<'x> {
language: Option<&'x str>, language: Option<&'x str>,
} }
impl<'x> Context<'x> { impl<'x> Context<'x> {
/// A context suitable for the beginning of the document. /// A context suitable for the beginning of the document.
///
/// `xml:lang` is assumed to be unset.
pub fn empty() -> Self { pub fn empty() -> Self {
Self { language: None } Self { language: None }
} }
/// Create a new context. /// Set the effective `xml:lang` value on the context and return it.
/// pub fn with_language(mut self, language: Option<&'x str>) -> Self {
/// - `language` must be the effective value of the `xml:lang` value at self.language = language;
/// the end of the current event. self
pub fn new(language: Option<&'x str>) -> Self {
Self { language }
} }
/// Return the `xml:lang` value in effect at the end of the event which /// Return the `xml:lang` value in effect at the end of the event which
@ -474,7 +483,11 @@ pub fn transform<T: FromXml, F: AsXml>(from: &F) -> Result<T, self::error::Error
_ => panic!("into_event_iter did not start with StartElement event!"), _ => panic!("into_event_iter did not start with StartElement event!"),
}; };
languages.push_from_attrs(&attrs); languages.push_from_attrs(&attrs);
let mut sink = match T::from_events(qname, attrs, &Context::new(languages.current())) { let mut sink = match T::from_events(
qname,
attrs,
&Context::empty().with_language(languages.current()),
) {
Ok(v) => v, Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => { Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch) return Err(self::error::Error::TypeMismatch)
@ -484,7 +497,7 @@ pub fn transform<T: FromXml, F: AsXml>(from: &F) -> Result<T, self::error::Error
for event in iter { for event in iter {
let event = event?; let event = event?;
languages.handle_event(&event); languages.handle_event(&event);
if let Some(v) = sink.feed(event, &Context::new(languages.current()))? { if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
return Ok(v); return Ok(v);
} }
} }
@ -510,7 +523,11 @@ pub fn try_from_element<T: FromXml>(
let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?; let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
languages.push_from_attrs(&attrs); languages.push_from_attrs(&attrs);
let mut sink = match T::from_events(qname, attrs, &Context::new(languages.current())) { let mut sink = match T::from_events(
qname,
attrs,
&Context::empty().with_language(languages.current()),
) {
Ok(v) => v, Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => { Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::FromElementError::Mismatch(from)) return Err(self::error::FromElementError::Mismatch(from))
@ -541,7 +558,7 @@ pub fn try_from_element<T: FromXml>(
for event in iter { for event in iter {
let event = event?; let event = event?;
languages.handle_event(&event); languages.handle_event(&event);
if let Some(v) = sink.feed(event, &Context::new(languages.current()))? { if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
return Ok(v); return Ok(v);
} }
} }
@ -576,7 +593,11 @@ pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
} }
}; };
languages.push_from_attrs(&attrs); languages.push_from_attrs(&attrs);
let mut builder = match T::from_events(name, attrs, &Context::new(languages.current())) { let mut builder = match T::from_events(
name,
attrs,
&Context::empty().with_language(languages.current()),
) {
Ok(v) => v, Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => { Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch); return Err(self::error::Error::TypeMismatch);
@ -590,7 +611,9 @@ pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
match parser.parse(&mut buf, true) { match parser.parse(&mut buf, true) {
Ok(Some(ev)) => { Ok(Some(ev)) => {
languages.handle_event(&ev); languages.handle_event(&ev);
if let Some(v) = builder.feed(ev, &Context::new(languages.current()))? { if let Some(v) =
builder.feed(ev, &Context::empty().with_language(languages.current()))?
{
return Ok(v); return Ok(v);
} }
} }
@ -631,7 +654,11 @@ fn read_start_event_io(
pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> { pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
let mut reader = rxml::XmlLangTracker::wrap(rxml::Reader::new(r)); let mut reader = rxml::XmlLangTracker::wrap(rxml::Reader::new(r));
let (name, attrs) = read_start_event_io(&mut reader)?; let (name, attrs) = read_start_event_io(&mut reader)?;
let mut builder = match T::from_events(name, attrs, &Context::new(reader.language())) { let mut builder = match T::from_events(
name,
attrs,
&Context::empty().with_language(reader.language()),
) {
Ok(v) => v, Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => { Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch) return Err(self::error::Error::TypeMismatch)
@ -643,7 +670,7 @@ pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
}; };
while let Some(ev) = reader.next() { while let Some(ev) = reader.next() {
if let Some(v) = builder if let Some(v) = builder
.feed(ev?, &Context::new(reader.language())) .feed(ev?, &Context::empty().with_language(reader.language()))
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
{ {
return Ok(v); return Ok(v);