Track xml:lang throughout parsing

This commit is contained in:
Jonas Schäfer 2025-04-18 17:20:06 +02:00
commit d9a21dd8c9
21 changed files with 446 additions and 88 deletions

View file

@ -17,6 +17,8 @@ Version NEXT:
equivalent. This is because `xml:lang` is special as per
XML 1.0 § 2.12 and it is generally preferable to allow it to occur
anywhere in the document.
- The `FromXml::from_events` and `FromEventsBuilder::feed` functions
gained an additional argument to support `xml:lang` inheritance.
* Added
- Support for child elements in derive macros. Child elements may also
be wrapped in Option or Box or in containers like Vec or HashSet.
@ -46,6 +48,8 @@ Version NEXT:
- Implement `AsXml` and `FromXml` for serde_json::Value` behind
`serde_json` feature.
- Support for a post-deserialization callback function call. (!553)
- Support for correctly inheriting `xml:lang` values throughout the
XML document.
* Changes
- Generated AsXml iterator and FromXml builder types are now
doc(hidden), to not clutter hand-written documentation with auto

View file

@ -28,6 +28,7 @@ assert_eq!(foo, Foo);
3. [`element` meta](#element-meta)
4. [`extract` meta](#extract-meta)
5. [`flag` meta](#flag-meta)
6. [`lang` meta](#lang-meta)
6. [`text` meta](#text-meta)
## Attributes
@ -627,6 +628,7 @@ error is emitted.
When parsing, any contents within the child element generate a parse error.
#### Example
```rust
# use xso::FromXml;
#[derive(FromXml, Debug, PartialEq)]
@ -647,6 +649,64 @@ assert_eq!(foo, Foo {
});
```
### `lang` meta
The `lang` meta allows to access the (potentially inherited) logical
`xml:lang` value as defined in
[XML 1.0 § 2.12](https://www.w3.org/TR/REC-xml/#sec-lang-tag).
This meta supports no arguments and can only be used on fields of type
`Option<String>`.
This meta should not be used alongsite `#[xml(attribute)]` meta which match
the `xml:lang` attribute. Doing so causes unspecified behavior during
serialisation: only one of the values will be in the output, but it is
unspecified which of the two. This is the same as when having two
`#[xml(attribute)]` field which match the same attribute. (Due to indirections
when refering to `static` items for attribute namespaces and names, it is not
possible to check this at compile-time.)
Unlike `#[xml(attribute = "xml:lang")]`, the `#[xml(lang)]` meta takes
inheritance into account.
**Note:** Using this meta is not roundtrip-safe. `rxml` will always emit its
value on serialisation, even if it was inherited during deserialisation.
#### Example
```rust
# use xso::FromXml;
#[derive(FromXml, Debug, PartialEq)]
#[xml(namespace = "urn:example", name = "bar")]
struct Bar {
#[xml(lang)]
lang: Option<String>,
};
#[derive(FromXml, Debug, PartialEq)]
#[xml(namespace = "urn:example", name = "foo")]
struct Foo {
#[xml(child)]
child: Bar,
};
// `xml:lang` gets inherited from <foo/> to <bar/>
let foo: Foo = xso::from_bytes(b"<foo xmlns='urn:example' xml:lang='en'><bar/></foo>").unwrap();
assert_eq!(foo, Foo {
child: Bar {
lang: Some("en".to_owned()),
},
});
// `xml:lang` gets set/overwritten in <bar/>
let foo: Foo = xso::from_bytes(b"<foo xmlns='urn:example' xml:lang='en'><bar xml:lang='de'/></foo>").unwrap();
assert_eq!(foo, Foo {
child: Bar {
lang: Some("de".to_owned()),
},
});
```
### `text` meta
The `text` meta causes the field to be mapped to the text content of the

View file

@ -15,7 +15,7 @@
use alloc::boxed::Box;
use crate::error::{Error, FromEventsError};
use crate::{FromEventsBuilder, FromXml};
use crate::{Context, FromEventsBuilder, FromXml};
/// Helper struct to construct an `Option<T>` from XML events.
pub struct OptionBuilder<T: FromEventsBuilder>(T);
@ -23,8 +23,8 @@ pub struct OptionBuilder<T: FromEventsBuilder>(T);
impl<T: FromEventsBuilder> FromEventsBuilder for OptionBuilder<T> {
type Output = Option<T::Output>;
fn feed(&mut self, ev: rxml::Event) -> Result<Option<Self::Output>, Error> {
self.0.feed(ev).map(|ok| ok.map(Some))
fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
self.0.feed(ev, ctx).map(|ok| ok.map(Some))
}
}
@ -40,8 +40,9 @@ impl<T: FromXml> FromXml for Option<T> {
fn from_events(
name: rxml::QName,
attrs: rxml::AttrMap,
ctx: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> {
Ok(OptionBuilder(T::from_events(name, attrs)?))
Ok(OptionBuilder(T::from_events(name, attrs, ctx)?))
}
}
@ -51,8 +52,8 @@ pub struct BoxBuilder<T: FromEventsBuilder>(Box<T>);
impl<T: FromEventsBuilder> FromEventsBuilder for BoxBuilder<T> {
type Output = Box<T::Output>;
fn feed(&mut self, ev: rxml::Event) -> Result<Option<Self::Output>, Error> {
self.0.feed(ev).map(|ok| ok.map(Box::new))
fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
self.0.feed(ev, ctx).map(|ok| ok.map(Box::new))
}
}
@ -63,8 +64,9 @@ impl<T: FromXml> FromXml for Box<T> {
fn from_events(
name: rxml::QName,
attrs: rxml::AttrMap,
ctx: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> {
Ok(BoxBuilder(Box::new(T::from_events(name, attrs)?)))
Ok(BoxBuilder(Box::new(T::from_events(name, attrs, ctx)?)))
}
}
@ -92,7 +94,7 @@ pub struct FallibleBuilder<T: FromEventsBuilder, E>(FallibleBuilderInner<T, E>);
impl<T: FromEventsBuilder, E: From<Error>> FromEventsBuilder for FallibleBuilder<T, E> {
type Output = Result<T::Output, E>;
fn feed(&mut self, ev: rxml::Event) -> Result<Option<Self::Output>, Error> {
fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
match self.0 {
FallibleBuilderInner::Processing {
ref mut depth,
@ -130,7 +132,7 @@ impl<T: FromEventsBuilder, E: From<Error>> FromEventsBuilder for FallibleBuilder
rxml::Event::XmlDeclaration(..) | rxml::Event::Text(..) => Some(*depth),
};
match builder.feed(ev) {
match builder.feed(ev, ctx) {
Ok(Some(v)) => {
self.0 = FallibleBuilderInner::Done;
return Ok(Some(Ok(v)));
@ -214,8 +216,9 @@ impl<T: FromXml, E: From<Error>> FromXml for Result<T, E> {
fn from_events(
name: rxml::QName,
attrs: rxml::AttrMap,
ctx: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> {
match T::from_events(name, attrs) {
match T::from_events(name, attrs, ctx) {
Ok(builder) => Ok(FallibleBuilder(FallibleBuilderInner::Processing {
depth: 0,
builder,
@ -248,7 +251,7 @@ impl Discard {
impl FromEventsBuilder for Discard {
type Output = ();
fn feed(&mut self, ev: rxml::Event) -> Result<Option<Self::Output>, Error> {
fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
match ev {
rxml::Event::StartElement(..) => {
self.depth = match self.depth.checked_add(1) {
@ -284,7 +287,7 @@ pub struct EmptyBuilder {
impl FromEventsBuilder for EmptyBuilder {
type Output = ();
fn feed(&mut self, ev: rxml::Event) -> Result<Option<Self::Output>, Error> {
fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
match ev {
rxml::Event::EndElement(..) => Ok(Some(())),
rxml::Event::StartElement(..) => Err(Error::Other(self.childerr)),
@ -336,7 +339,11 @@ mod tests {
impl FromEventsBuilder for $name {
type Output = $output;
fn feed(&mut self, _: Event) -> Result<Option<Self::Output>, Error> {
fn feed(
&mut self,
_: Event,
_: &Context<'_>,
) -> Result<Option<Self::Output>, Error> {
unreachable!();
}
}
@ -355,6 +362,7 @@ mod tests {
fn from_events(
name: rxml::QName,
attrs: rxml::AttrMap,
_ctx: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> {
Err(FromEventsError::Mismatch { name, attrs })
}
@ -366,7 +374,11 @@ mod tests {
impl FromXml for InitialError {
type Builder = InitialErrorBuilder;
fn from_events(_: rxml::QName, _: rxml::AttrMap) -> Result<Self::Builder, FromEventsError> {
fn from_events(
_: rxml::QName,
_: rxml::AttrMap,
_: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> {
Err(FromEventsError::Invalid(Error::Other("some error")))
}
}
@ -377,7 +389,7 @@ mod tests {
impl FromEventsBuilder for FailOnContentBuilder {
type Output = FailOnContent;
fn feed(&mut self, _: Event) -> Result<Option<Self::Output>, Error> {
fn feed(&mut self, _: Event, _: &Context<'_>) -> Result<Option<Self::Output>, Error> {
Err(Error::Other("content error"))
}
}
@ -388,7 +400,11 @@ mod tests {
impl FromXml for FailOnContent {
type Builder = FailOnContentBuilder;
fn from_events(_: rxml::QName, _: rxml::AttrMap) -> Result<Self::Builder, FromEventsError> {
fn from_events(
_: rxml::QName,
_: rxml::AttrMap,
_: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> {
Ok(FailOnContentBuilder)
}
}
@ -403,7 +419,7 @@ mod tests {
#[test]
fn fallible_builder_mismatch_passthrough() {
match Result::<AlwaysMismatch, Error>::from_events(qname(), attrs()) {
match Result::<AlwaysMismatch, Error>::from_events(qname(), attrs(), &Context::empty()) {
Err(FromEventsError::Mismatch { .. }) => (),
other => panic!("unexpected result: {:?}", other),
}
@ -411,15 +427,19 @@ mod tests {
#[test]
fn fallible_builder_initial_error_capture() {
let mut builder = match Result::<InitialError, Error>::from_events(qname(), attrs()) {
let ctx = Context::empty();
let mut builder = match Result::<InitialError, Error>::from_events(qname(), attrs(), &ctx) {
Ok(v) => v,
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::Text(EventMetrics::zero(), "hello world!".to_owned())) {
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero())) {
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(Some(Err(Error::Other("some error")))) => (),
other => panic!("unexpected result: {:?}", other),
};
@ -427,47 +447,66 @@ mod tests {
#[test]
fn fallible_builder_initial_error_capture_allows_nested_stuff() {
let mut builder = match Result::<InitialError, Error>::from_events(qname(), attrs()) {
let ctx = Context::empty();
let mut builder = match Result::<InitialError, Error>::from_events(qname(), attrs(), &ctx) {
Ok(v) => v,
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::StartElement(EventMetrics::zero(), qname(), attrs())) {
match builder.feed(
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::Text(EventMetrics::zero(), "hello world!".to_owned())) {
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero())) {
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::Text(EventMetrics::zero(), "hello world!".to_owned())) {
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::StartElement(EventMetrics::zero(), qname(), attrs())) {
match builder.feed(
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::StartElement(EventMetrics::zero(), qname(), attrs())) {
match builder.feed(
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::Text(EventMetrics::zero(), "hello world!".to_owned())) {
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero())) {
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero())) {
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero())) {
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(Some(Err(Error::Other("some error")))) => (),
other => panic!("unexpected result: {:?}", other),
};
@ -475,11 +514,13 @@ mod tests {
#[test]
fn fallible_builder_content_error_capture() {
let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs()) {
let ctx = Context::empty();
let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
{
Ok(v) => v,
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero())) {
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(Some(Err(Error::Other("content error")))) => (),
other => panic!("unexpected result: {:?}", other),
};
@ -487,15 +528,20 @@ mod tests {
#[test]
fn fallible_builder_content_error_capture_with_more_content() {
let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs()) {
let ctx = Context::empty();
let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
{
Ok(v) => v,
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::Text(EventMetrics::zero(), "hello world!".to_owned())) {
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero())) {
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(Some(Err(Error::Other("content error")))) => (),
other => panic!("unexpected result: {:?}", other),
};
@ -503,47 +549,67 @@ mod tests {
#[test]
fn fallible_builder_content_error_capture_with_nested_content() {
let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs()) {
let ctx = Context::empty();
let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
{
Ok(v) => v,
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::StartElement(EventMetrics::zero(), qname(), attrs())) {
match builder.feed(
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::Text(EventMetrics::zero(), "hello world!".to_owned())) {
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero())) {
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::Text(EventMetrics::zero(), "hello world!".to_owned())) {
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::StartElement(EventMetrics::zero(), qname(), attrs())) {
match builder.feed(
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::StartElement(EventMetrics::zero(), qname(), attrs())) {
match builder.feed(
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::Text(EventMetrics::zero(), "hello world!".to_owned())) {
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero())) {
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero())) {
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero())) {
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(Some(Err(Error::Other("content error")))) => (),
other => panic!("unexpected result: {:?}", other),
};

View file

@ -128,6 +128,33 @@ pub trait AsXml {
fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;
}
/// Additional parsing context supplied to [`FromEventsBuilder`]
/// implementations.
pub struct Context<'x> {
language: Option<&'x str>,
}
impl<'x> Context<'x> {
/// A context suitable for the beginning of the document.
pub fn empty() -> Self {
Self { language: None }
}
/// Create a new context.
///
/// - `language` must be the effective value of the `xml:lang` value at
/// the end of the current event.
pub fn new(language: Option<&'x str>) -> Self {
Self { language }
}
/// Return the `xml:lang` value in effect at the end of the event which
/// is currently being processed.
pub fn language(&self) -> Option<&str> {
self.language.as_deref()
}
}
/// Trait for a temporary object allowing to construct a struct from
/// [`rxml::Event`] items.
///
@ -151,7 +178,11 @@ pub trait FromEventsBuilder {
/// Feeding more events after an error may result in panics, errors or
/// inconsistent result data, though it may never result in unsound or
/// unsafe behaviour.
fn feed(&mut self, ev: rxml::Event) -> Result<Option<Self::Output>, self::error::Error>;
fn feed(
&mut self,
ev: rxml::Event,
ctx: &Context<'_>,
) -> Result<Option<Self::Output>, self::error::Error>;
}
/// Trait allowing to construct a struct from a stream of
@ -188,6 +219,7 @@ pub trait FromXml {
fn from_events(
name: rxml::QName,
attrs: rxml::AttrMap,
ctx: &Context<'_>,
) -> Result<Self::Builder, self::error::FromEventsError>;
}
@ -423,13 +455,15 @@ impl UnknownChildPolicy {
/// Attempt to transform a type implementing [`AsXml`] into another
/// type which implements [`FromXml`].
pub fn transform<T: FromXml, F: AsXml>(from: &F) -> Result<T, self::error::Error> {
let mut languages = rxml::xml_lang::XmlLangStack::new();
let mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
let (qname, attrs) = match iter.next() {
Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
Some(Err(e)) => return Err(e),
_ => panic!("into_event_iter did not start with StartElement event!"),
};
let mut sink = match T::from_events(qname, attrs) {
languages.push_from_attrs(&attrs);
let mut sink = match T::from_events(qname, attrs, &Context::new(languages.current())) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch)
@ -438,7 +472,8 @@ pub fn transform<T: FromXml, F: AsXml>(from: &F) -> Result<T, self::error::Error
};
for event in iter {
let event = event?;
if let Some(v) = sink.feed(event)? {
languages.handle_event(&event);
if let Some(v) = sink.feed(event, &Context::new(languages.current()))? {
return Ok(v);
}
}
@ -455,8 +490,11 @@ pub fn transform<T: FromXml, F: AsXml>(from: &F) -> Result<T, self::error::Error
pub fn try_from_element<T: FromXml>(
from: minidom::Element,
) -> Result<T, self::error::FromElementError> {
let mut languages = rxml::xml_lang::XmlLangStack::new();
let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
let mut sink = match T::from_events(qname, attrs) {
languages.push_from_attrs(&attrs);
let mut sink = match T::from_events(qname, attrs, &Context::new(languages.current())) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::FromElementError::Mismatch(from))
@ -486,7 +524,8 @@ pub fn try_from_element<T: FromXml>(
let iter = self::rxml_util::ItemToEvent::new(iter);
for event in iter {
let event = event?;
if let Some(v) = sink.feed(event)? {
languages.handle_event(&event);
if let Some(v) = sink.feed(event, &Context::new(languages.current()))? {
return Ok(v);
}
}
@ -508,8 +547,8 @@ fn map_nonio_error<T>(r: Result<T, io::Error>) -> Result<T, self::error::Error>
}
#[cfg(feature = "std")]
fn read_start_event<I: io::BufRead>(
r: &mut rxml::Reader<I>,
fn read_start_event(
r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
) -> Result<(rxml::QName, rxml::AttrMap), self::error::Error> {
for ev in r {
match map_nonio_error(ev)? {
@ -531,17 +570,17 @@ fn read_start_event<I: io::BufRead>(
/// containing XML data.
#[cfg(feature = "std")]
pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
let mut reader = rxml::Reader::new(&mut buf);
let mut reader = rxml::XmlLangTracker::wrap(rxml::Reader::new(&mut buf));
let (name, attrs) = read_start_event(&mut reader)?;
let mut builder = match T::from_events(name, attrs) {
let mut builder = match T::from_events(name, attrs, &Context::new(reader.language())) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch)
}
Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
};
for ev in reader {
if let Some(v) = builder.feed(map_nonio_error(ev)?)? {
while let Some(ev) = reader.next() {
if let Some(v) = builder.feed(map_nonio_error(ev)?, &Context::new(reader.language()))? {
return Ok(v);
}
}
@ -549,8 +588,8 @@ pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
}
#[cfg(feature = "std")]
fn read_start_event_io<I: io::BufRead>(
r: &mut rxml::Reader<I>,
fn read_start_event_io(
r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
) -> io::Result<(rxml::QName, rxml::AttrMap)> {
for ev in r {
match ev? {
@ -575,9 +614,9 @@ fn read_start_event_io<I: io::BufRead>(
/// Attempt to parse a type implementing [`FromXml`] from a reader.
#[cfg(feature = "std")]
pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
let mut reader = rxml::Reader::new(r);
let mut reader = rxml::XmlLangTracker::wrap(rxml::Reader::new(r));
let (name, attrs) = read_start_event_io(&mut reader)?;
let mut builder = match T::from_events(name, attrs) {
let mut builder = match T::from_events(name, attrs, &Context::new(reader.language())) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch)
@ -587,9 +626,9 @@ pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
return Err(e).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
};
for ev in reader {
while let Some(ev) = reader.next() {
if let Some(v) = builder
.feed(ev?)
.feed(ev?, &Context::new(reader.language()))
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
{
return Ok(v);

View file

@ -24,7 +24,7 @@ use rxml::{
use crate::{
error::{Error, FromEventsError},
rxml_util::{EventToItem, Item},
AsXml, FromEventsBuilder, FromXml,
AsXml, Context, FromEventsBuilder, FromXml,
};
/// State machine for converting a minidom Element into rxml events.
@ -351,13 +351,13 @@ impl ElementFromEvents {
impl FromEventsBuilder for ElementFromEvents {
type Output = minidom::Element;
fn feed(&mut self, ev: Event) -> Result<Option<Self::Output>, Error> {
fn feed(&mut self, ev: Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
let inner = self
.inner
.as_mut()
.expect("feed() called after it finished");
if let Some(nested) = self.nested.as_mut() {
match nested.feed(ev)? {
match nested.feed(ev, ctx)? {
Some(v) => {
inner.append_child(v);
self.nested = None;
@ -369,7 +369,7 @@ impl FromEventsBuilder for ElementFromEvents {
match ev {
Event::XmlDeclaration(_, _) => Ok(None),
Event::StartElement(_, qname, attrs) => {
let nested = match Element::from_events(qname, attrs) {
let nested = match Element::from_events(qname, attrs, ctx) {
Ok(v) => v,
Err(FromEventsError::Invalid(e)) => return Err(e),
Err(FromEventsError::Mismatch { .. }) => {
@ -394,6 +394,7 @@ impl FromXml for Element {
fn from_events(
qname: rxml::QName,
attrs: rxml::AttrMap,
_ctx: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> {
Ok(Self::Builder::new(qname, attrs))
}
@ -418,7 +419,13 @@ where
pub fn new(qname: rxml::QName, attrs: rxml::AttrMap) -> Result<Self, FromEventsError> {
Ok(Self {
_phantom: PhantomData,
inner: Element::from_events(qname, attrs)?,
inner: Element::from_events(
qname,
attrs,
// FromEventsViaElement does not support passing through
// `xml:lang` inheritance, so we don't pass any context.
&Context::empty(),
)?,
})
}
}
@ -429,8 +436,8 @@ where
{
type Output = T;
fn feed(&mut self, ev: Event) -> Result<Option<Self::Output>, Error> {
match self.inner.feed(ev) {
fn feed(&mut self, ev: Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
match self.inner.feed(ev, ctx) {
Ok(Some(v)) => Ok(Some(v.try_into()?)),
Ok(None) => Ok(None),
Err(e) => Err(e),