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

@ -229,6 +229,7 @@ impl ::xso::FromXml for Iq {
fn from_events( fn from_events(
qname: ::xso::exports::rxml::QName, qname: ::xso::exports::rxml::QName,
attrs: ::xso::exports::rxml::AttrMap, attrs: ::xso::exports::rxml::AttrMap,
_ctx: &::xso::Context<'_>,
) -> Result<Self::Builder, ::xso::error::FromEventsError> { ) -> Result<Self::Builder, ::xso::error::FromEventsError> {
if qname.0 != crate::ns::DEFAULT_NS || qname.1 != "iq" { if qname.0 != crate::ns::DEFAULT_NS || qname.1 != "iq" {
return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs }); return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });

View file

@ -203,6 +203,7 @@ impl ::xso::FromXml for Description {
fn from_events( fn from_events(
qname: ::xso::exports::rxml::QName, qname: ::xso::exports::rxml::QName,
attrs: ::xso::exports::rxml::AttrMap, attrs: ::xso::exports::rxml::AttrMap,
_ctx: &::xso::Context<'_>,
) -> Result<Self::Builder, ::xso::error::FromEventsError> { ) -> Result<Self::Builder, ::xso::error::FromEventsError> {
if qname.1 != "description" { if qname.1 != "description" {
return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs }); return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });
@ -263,6 +264,7 @@ impl ::xso::FromXml for Transport {
fn from_events( fn from_events(
qname: ::xso::exports::rxml::QName, qname: ::xso::exports::rxml::QName,
attrs: ::xso::exports::rxml::AttrMap, attrs: ::xso::exports::rxml::AttrMap,
_ctx: &::xso::Context<'_>,
) -> Result<Self::Builder, ::xso::error::FromEventsError> { ) -> Result<Self::Builder, ::xso::error::FromEventsError> {
if qname.1 != "transport" { if qname.1 != "transport" {
return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs }); return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });

View file

@ -290,6 +290,7 @@ impl ::xso::FromXml for Transport {
fn from_events( fn from_events(
qname: ::xso::exports::rxml::QName, qname: ::xso::exports::rxml::QName,
attrs: ::xso::exports::rxml::AttrMap, attrs: ::xso::exports::rxml::AttrMap,
_ctx: &::xso::Context<'_>,
) -> Result<Self::Builder, ::xso::error::FromEventsError> { ) -> Result<Self::Builder, ::xso::error::FromEventsError> {
if qname.0 != crate::ns::JINGLE_S5B || qname.1 != "transport" { if qname.0 != crate::ns::JINGLE_S5B || qname.1 != "transport" {
return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs }); return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });

View file

@ -2489,3 +2489,65 @@ fn text_vs_attribute_ordering_compile_bug_roundtrip_with_parent() {
"<thread xmlns='urn:example:ns1' parent='bar'>foo</thread>", "<thread xmlns='urn:example:ns1' parent='bar'>foo</thread>",
); );
} }
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = NS1, name = "elem")]
struct Language {
#[xml(child(default))]
child: core::option::Option<Box<Language>>,
#[xml(lang)]
lang: core::option::Option<String>,
}
#[test]
fn language_roundtrip_absent() {
#[allow(unused_imports)]
use core::{
option::Option::{None, Some},
result::Result::{Err, Ok},
};
roundtrip_full::<Language>("<elem xmlns='urn:example:ns1'/>");
}
#[test]
fn language_roundtrip_present() {
#[allow(unused_imports)]
use core::{
option::Option::{None, Some},
result::Result::{Err, Ok},
};
roundtrip_full::<Language>("<elem xmlns='urn:example:ns1' xml:lang='foo'/>");
}
#[test]
fn language_roundtrip_nested_parse() {
// cannot write a round-trip test for this, because on emission,
// `#[xml(language)]` fields with a non-None value will always emit
// the `xml:lang` attribute to ensure semantic correctness.
#[allow(unused_imports)]
use core::{
option::Option::{None, Some},
result::Result::{Err, Ok},
};
match parse_str::<Language>(
"<elem xmlns='urn:example:ns1' xml:lang='foo'><elem><elem xml:lang='bar'/></elem></elem>",
) {
Ok(Language { child, lang }) => {
assert_eq!(lang.as_deref(), Some("foo"));
let Some(Language { child, lang }) = child.map(|x| *x) else {
panic!("missing child");
};
assert_eq!(lang.as_deref(), Some("foo"));
let Some(Language { child, lang }) = child.map(|x| *x) else {
panic!("missing grand child");
};
assert_eq!(lang.as_deref(), Some("bar"));
assert!(child.is_none());
}
other => panic!("unexpected parse result: {:?}", other),
}
}

View file

@ -262,6 +262,7 @@ macro_rules! generate_attribute_enum {
fn from_events( fn from_events(
qname: ::xso::exports::rxml::QName, qname: ::xso::exports::rxml::QName,
attrs: ::xso::exports::rxml::AttrMap, attrs: ::xso::exports::rxml::AttrMap,
_ctx: &::xso::Context<'_>,
) -> Result<Self::Builder, ::xso::error::FromEventsError> { ) -> Result<Self::Builder, ::xso::error::FromEventsError> {
if qname.0 != crate::ns::$ns || qname.1 != $name { if qname.0 != crate::ns::$ns || qname.1 != $name {
return Err(::xso::error::FromEventsError::Mismatch { return Err(::xso::error::FromEventsError::Mismatch {

View file

@ -23,7 +23,9 @@ use tokio::{
}; };
use xso::{ use xso::{
exports::rxml::{self, writer::TrackNamespace, xml_ncname, Event, Namespace}, exports::rxml::{
self, writer::TrackNamespace, xml_lang::XmlLangStack, xml_ncname, Event, Namespace,
},
AsXml, FromEventsBuilder, FromXml, Item, AsXml, FromEventsBuilder, FromXml, Item,
}; };
@ -165,6 +167,9 @@ pin_project_lite::pin_project! {
#[pin] #[pin]
parser: rxml::AsyncReader<CaptureBufRead<Io>>, parser: rxml::AsyncReader<CaptureBufRead<Io>>,
// Tracker for `xml:lang` data.
lang_stack: XmlLangStack,
// The writer used for serialising data. // The writer used for serialising data.
writer: rxml::writer::Encoder<rxml::writer::SimpleNamespaces>, writer: rxml::writer::Encoder<rxml::writer::SimpleNamespaces>,
@ -254,6 +259,7 @@ impl<Io: AsyncBufRead + AsyncWrite> RawXmlStream<Io> {
Self { Self {
parser: rxml::AsyncReader::wrap(io, parser), parser: rxml::AsyncReader::wrap(io, parser),
writer: Self::new_writer(stream_ns), writer: Self::new_writer(stream_ns),
lang_stack: XmlLangStack::new(),
timeouts: TimeoutState::new(timeouts), timeouts: TimeoutState::new(timeouts),
tx_buffer_logged: 0, tx_buffer_logged: 0,
stream_ns, stream_ns,
@ -270,6 +276,7 @@ impl<Io: AsyncBufRead + AsyncWrite> RawXmlStream<Io> {
pub(super) fn reset_state(self: Pin<&mut Self>) { pub(super) fn reset_state(self: Pin<&mut Self>) {
let this = self.project(); let this = self.project();
*this.parser.parser_pinned() = rxml::Parser::default(); *this.parser.parser_pinned() = rxml::Parser::default();
*this.lang_stack = XmlLangStack::new();
*this.writer = Self::new_writer(this.stream_ns); *this.writer = Self::new_writer(this.stream_ns);
} }
@ -293,6 +300,7 @@ impl<Io: AsyncBufRead + AsyncWrite> RawXmlStream<Io> {
let parser = rxml::AsyncReader::wrap(io, p); let parser = rxml::AsyncReader::wrap(io, p);
RawXmlStream { RawXmlStream {
parser, parser,
lang_stack: XmlLangStack::new(),
timeouts: self.timeouts, timeouts: self.timeouts,
writer: self.writer, writer: self.writer,
tx_buffer: self.tx_buffer, tx_buffer: self.tx_buffer,
@ -360,6 +368,10 @@ impl<Io: AsyncBufRead> Stream for RawXmlStream<Io> {
match v.transpose() { match v.transpose() {
// Skip the XML declaration, nobody wants to hear about that. // Skip the XML declaration, nobody wants to hear about that.
Some(Ok(rxml::Event::XmlDeclaration(_, _))) => continue, Some(Ok(rxml::Event::XmlDeclaration(_, _))) => continue,
Some(Ok(event)) => {
this.lang_stack.handle_event(&event);
return Poll::Ready(Some(Ok(event)));
}
other => return Poll::Ready(other.map(|x| x.map_err(RawError::Io))), other => return Poll::Ready(other.map(|x| x.map_err(RawError::Io))),
} }
} }
@ -635,9 +647,13 @@ 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 ctx = xso::Context::new(source_tmp.lang_stack.current());
*self = ReadXsoState::Parsing( *self = ReadXsoState::Parsing(
<Result<T, xso::error::Error> as FromXml>::from_events(name, attrs) <Result<T, xso::error::Error> as FromXml>::from_events(
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?, name, attrs, &ctx,
)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
); );
} }
// Amounts to EOF, as we expect to start on the stream level. // Amounts to EOF, as we expect to start on the stream level.
@ -691,7 +707,9 @@ impl<T: FromXml> ReadXsoState<T> {
} }
}; };
match builder.feed(ev) { let source_tmp = source.as_mut();
let ctx = xso::Context::new(source_tmp.lang_stack.current());
match builder.feed(ev, &ctx) {
Err(err) => { Err(err) => {
*self = ReadXsoState::Done; *self = ReadXsoState::Done;
source.as_mut().stream_pinned().discard_capture(); source.as_mut().stream_pinned().discard_capture();

View file

@ -318,7 +318,7 @@ impl Compound {
substate_data, substate_data,
&builder, &builder,
).with_mut(substate_data).with_impl(quote! { ).with_mut(substate_data).with_impl(quote! {
match #feed(&mut #substate_data, ev)? { match #feed(&mut #substate_data, ev, ctx)? {
::core::option::Option::Some(#substate_result) => { ::core::option::Option::Some(#substate_result) => {
#collect #collect
::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident { ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
@ -467,7 +467,7 @@ impl Compound {
substate_data, substate_data,
&discard_builder_ty, &discard_builder_ty,
).with_mut(substate_data).with_impl(quote! { ).with_mut(substate_data).with_impl(quote! {
match #discard_feed(&mut #substate_data, ev)? { match #discard_feed(&mut #substate_data, ev, ctx)? {
::core::option::Option::Some(#substate_result) => { ::core::option::Option::Some(#substate_result) => {
::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident { ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
#builder_data_ident, #builder_data_ident,

View file

@ -547,7 +547,7 @@ impl ItemDef for EnumDef {
Ok(FromXmlParts { Ok(FromXmlParts {
defs, defs,
from_events_body: quote! { from_events_body: quote! {
#builder_ty_ident::new(#name_ident, #attrs_ident) #builder_ty_ident::new(#name_ident, #attrs_ident, ctx)
}, },
builder_ty_ident: builder_ty_ident.clone(), builder_ty_ident: builder_ty_ident.clone(),
}) })

View file

@ -68,7 +68,7 @@ impl Field for ChildField {
let from_events = from_events_fn(element_ty.clone()); let from_events = from_events_fn(element_ty.clone());
let matcher = quote! { #from_events(name, attrs) }; let matcher = quote! { #from_events(name, attrs, ctx) };
let builder = from_xml_builder_ty(element_ty.clone()); let builder = from_xml_builder_ty(element_ty.clone());
( (
@ -307,7 +307,7 @@ impl ExtractDef {
)?; )?;
let from_xml_builder_ty = ty_from_ident(from_xml_builder_ty_ident.clone()).into(); let from_xml_builder_ty = ty_from_ident(from_xml_builder_ty_ident.clone()).into();
let matcher = quote! { #state_ty_ident::new(name, attrs).map(|x| #from_xml_builder_ty_ident(::core::option::Option::Some(x))) }; let matcher = quote! { #state_ty_ident::new(name, attrs, ctx).map(|x| #from_xml_builder_ty_ident(::core::option::Option::Some(x))) };
let inner_ty = self.parts.to_single_or_tuple_ty(); let inner_ty = self.parts.to_single_or_tuple_ty();

View file

@ -80,7 +80,7 @@ impl Field for ElementField {
if #field_access.is_some() { if #field_access.is_some() {
::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs })
} else { } else {
#from_events(name, attrs) #from_events(name, attrs, ctx)
} }
}), }),
builder, builder,

View file

@ -0,0 +1,72 @@
// Copyright (c) 2025 Jonas Schäfer <jonas@zombofant.net>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//! This module concerns the processing of inherited `xml:lang` values.
//!
//! In particular, it provides the `#[xml(lang)]` implementation.
use proc_macro2::Span;
use quote::quote;
use syn::*;
use crate::error_message::ParentRef;
use crate::scope::{AsItemsScope, FromEventsScope};
use crate::types::{as_optional_xml_text_fn, option_ty, string_ty};
use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit};
/// The field maps to a potentially inherited `xml:lang` value.
pub(super) struct LangField;
impl Field for LangField {
fn make_builder_part(
&self,
_scope: &FromEventsScope,
_container_name: &ParentRef,
_member: &Member,
_ty: &Type,
) -> Result<FieldBuilderPart> {
let string_ty = string_ty(Span::call_site());
let ty = option_ty(string_ty.clone());
Ok(FieldBuilderPart::Init {
value: FieldTempInit {
ty,
init: quote! {
ctx.language().map(#string_ty::from).into()
},
},
})
}
fn make_iterator_part(
&self,
_scope: &AsItemsScope,
_container_name: &ParentRef,
bound_name: &Ident,
_member: &Member,
ty: &Type,
) -> Result<FieldIteratorPart> {
let as_optional_xml_text = as_optional_xml_text_fn(ty.clone());
Ok(FieldIteratorPart::Header {
generator: quote! {
#as_optional_xml_text(#bound_name)?.map(|#bound_name|
::xso::Item::Attribute(
::xso::exports::rxml::Namespace::XML,
// SAFETY: `lang` is a known-good NcName
unsafe {
::xso::exports::alloc::borrow::Cow::Borrowed(
::xso::exports::rxml::NcNameStr::from_str_unchecked("lang"),
)
},
#bound_name,
)
)
},
})
}
}

View file

@ -21,6 +21,7 @@ mod child;
#[cfg(feature = "minidom")] #[cfg(feature = "minidom")]
mod element; mod element;
mod flag; mod flag;
mod lang;
mod text; mod text;
use self::attribute::AttributeField; use self::attribute::AttributeField;
@ -28,6 +29,7 @@ use self::child::{ChildField, ExtractDef};
#[cfg(feature = "minidom")] #[cfg(feature = "minidom")]
use self::element::ElementField; use self::element::ElementField;
use self::flag::FlagField; use self::flag::FlagField;
use self::lang::LangField;
use self::text::TextField; use self::text::TextField;
/// Code slices necessary for declaring and initializing a temporary variable /// Code slices necessary for declaring and initializing a temporary variable
@ -444,6 +446,8 @@ fn new_field(
xml_name, xml_name,
})) }))
} }
XmlFieldMeta::Language { span: _ } => Ok(Box::new(LangField)),
} }
} }

View file

@ -83,6 +83,7 @@ fn from_xml_impl(input: Item) -> Result<TokenStream> {
fn from_events( fn from_events(
name: ::xso::exports::rxml::QName, name: ::xso::exports::rxml::QName,
attrs: ::xso::exports::rxml::AttrMap, attrs: ::xso::exports::rxml::AttrMap,
ctx: &::xso::Context<'_>,
) -> ::core::result::Result<Self::Builder, ::xso::error::FromEventsError> { ) -> ::core::result::Result<Self::Builder, ::xso::error::FromEventsError> {
#from_events_body #from_events_body
} }

View file

@ -874,6 +874,14 @@ pub(crate) enum XmlFieldMeta {
/// The namespace/name keys. /// The namespace/name keys.
qname: QNameRef, qname: QNameRef,
}, },
/// `#[xml(lang)]`
Language {
/// The span of the `#[xml(lang)]` meta from which this was parsed.
///
/// This is useful for error messages.
span: Span,
},
} }
impl XmlFieldMeta { impl XmlFieldMeta {
@ -1182,6 +1190,13 @@ impl XmlFieldMeta {
}) })
} }
/// Parse a `#[xml(lang)]` meta.
fn lang_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
Ok(Self::Language {
span: meta.path.span(),
})
}
/// Parse [`Self`] from a nestd meta, switching on the identifier /// Parse [`Self`] from a nestd meta, switching on the identifier
/// of that nested meta. /// of that nested meta.
fn parse_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> { fn parse_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
@ -1197,6 +1212,8 @@ impl XmlFieldMeta {
Self::element_from_meta(meta) Self::element_from_meta(meta)
} else if meta.path.is_ident("flag") { } else if meta.path.is_ident("flag") {
Self::flag_from_meta(meta) Self::flag_from_meta(meta)
} else if meta.path.is_ident("lang") {
Self::lang_from_meta(meta)
} else { } else {
Err(Error::new_spanned(meta.path, "unsupported field meta")) Err(Error::new_spanned(meta.path, "unsupported field meta"))
} }
@ -1280,6 +1297,7 @@ impl XmlFieldMeta {
Self::Extract { ref span, .. } => *span, Self::Extract { ref span, .. } => *span,
Self::Element { ref span, .. } => *span, Self::Element { ref span, .. } => *span,
Self::Flag { ref span, .. } => *span, Self::Flag { ref span, .. } => *span,
Self::Language { ref span, .. } => *span,
} }
} }

View file

@ -509,7 +509,7 @@ impl FromEventsStateMachine {
} }
impl #state_ty_ident { impl #state_ty_ident {
fn advance(mut self, ev: ::xso::exports::rxml::Event) -> ::core::result::Result<::core::ops::ControlFlow<Self, #output_ty>, ::xso::error::Error> { fn advance(mut self, ev: ::xso::exports::rxml::Event, ctx: &::xso::Context<'_>) -> ::core::result::Result<::core::ops::ControlFlow<Self, #output_ty>, ::xso::error::Error> {
match self { match self {
#advance_match_arms #advance_match_arms
}.and_then(|__ok| { }.and_then(|__ok| {
@ -527,8 +527,9 @@ impl FromEventsStateMachine {
fn new( fn new(
name: ::xso::exports::rxml::QName, name: ::xso::exports::rxml::QName,
attrs: ::xso::exports::rxml::AttrMap, attrs: ::xso::exports::rxml::AttrMap,
ctx: &::xso::Context<'_>,
) -> ::core::result::Result<Self, ::xso::error::FromEventsError> { ) -> ::core::result::Result<Self, ::xso::error::FromEventsError> {
#state_ty_ident::new(name, attrs).map(|ok| Self(::core::option::Option::Some(ok))) #state_ty_ident::new(name, attrs, ctx).map(|ok| Self(::core::option::Option::Some(ok)))
} }
} }
@ -539,9 +540,9 @@ impl FromEventsStateMachine {
impl ::xso::FromEventsBuilder for #builder_ty_ident { impl ::xso::FromEventsBuilder for #builder_ty_ident {
type Output = #output_ty; type Output = #output_ty;
fn feed(&mut self, ev: ::xso::exports::rxml::Event) -> ::core::result::Result<::core::option::Option<Self::Output>, ::xso::error::Error> { fn feed(&mut self, ev: ::xso::exports::rxml::Event, ctx: &::xso::Context<'_>) -> ::core::result::Result<::core::option::Option<Self::Output>, ::xso::error::Error> {
let inner = self.0.take().expect("feed called after completion"); let inner = self.0.take().expect("feed called after completion");
match inner.advance(ev)? { match inner.advance(ev, ctx)? {
::core::ops::ControlFlow::Continue(mut value) => { ::core::ops::ControlFlow::Continue(mut value) => {
#validate_call #validate_call
::core::result::Result::Ok(::core::option::Option::Some(value)) ::core::result::Result::Ok(::core::option::Option::Some(value))
@ -558,6 +559,7 @@ impl FromEventsStateMachine {
fn new( fn new(
name: ::xso::exports::rxml::QName, name: ::xso::exports::rxml::QName,
mut attrs: ::xso::exports::rxml::AttrMap, mut attrs: ::xso::exports::rxml::AttrMap,
ctx: &::xso::Context<'_>,
) -> ::core::result::Result<Self, ::xso::error::FromEventsError> { ) -> ::core::result::Result<Self, ::xso::error::FromEventsError> {
#init_body #init_body
{ let _ = &mut attrs; } { let _ = &mut attrs; }

View file

@ -200,7 +200,7 @@ impl StructInner {
&from_xml_builder_ty, &from_xml_builder_ty,
) )
.with_impl(quote! { .with_impl(quote! {
match #feed_fn(&mut #builder_data_ident, ev)? { match #feed_fn(&mut #builder_data_ident, ev, ctx)? {
::core::option::Option::Some(result) => { ::core::option::Option::Some(result) => {
::core::result::Result::Ok(::core::ops::ControlFlow::Continue(#output_cons)) ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(#output_cons))
} }
@ -213,7 +213,7 @@ impl StructInner {
}) })
], ],
init: quote! { init: quote! {
#from_events_fn(name, attrs).map(|#builder_data_ident| Self::#state_name { #builder_data_ident }) #from_events_fn(name, attrs, ctx).map(|#builder_data_ident| Self::#state_name { #builder_data_ident })
}, },
}) })
} }
@ -392,7 +392,7 @@ impl ItemDef for StructDef {
Ok(FromXmlParts { Ok(FromXmlParts {
defs, defs,
from_events_body: quote! { from_events_body: quote! {
#builder_ty_ident::new(#name_ident, #attrs_ident) #builder_ty_ident::new(#name_ident, #attrs_ident, ctx)
}, },
builder_ty_ident: builder_ty_ident.clone(), builder_ty_ident: builder_ty_ident.clone(),
}) })

View file

@ -17,6 +17,8 @@ Version NEXT:
equivalent. This is because `xml:lang` is special as per 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 XML 1.0 § 2.12 and it is generally preferable to allow it to occur
anywhere in the document. anywhere in the document.
- The `FromXml::from_events` and `FromEventsBuilder::feed` functions
gained an additional argument to support `xml:lang` inheritance.
* Added * Added
- Support for child elements in derive macros. Child elements may also - Support for child elements in derive macros. Child elements may also
be wrapped in Option or Box or in containers like Vec or HashSet. 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 - Implement `AsXml` and `FromXml` for serde_json::Value` behind
`serde_json` feature. `serde_json` feature.
- Support for a post-deserialization callback function call. (!553) - Support for a post-deserialization callback function call. (!553)
- Support for correctly inheriting `xml:lang` values throughout the
XML document.
* Changes * Changes
- Generated AsXml iterator and FromXml builder types are now - Generated AsXml iterator and FromXml builder types are now
doc(hidden), to not clutter hand-written documentation with auto 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) 3. [`element` meta](#element-meta)
4. [`extract` meta](#extract-meta) 4. [`extract` meta](#extract-meta)
5. [`flag` meta](#flag-meta) 5. [`flag` meta](#flag-meta)
6. [`lang` meta](#lang-meta)
6. [`text` meta](#text-meta) 6. [`text` meta](#text-meta)
## Attributes ## Attributes
@ -627,6 +628,7 @@ error is emitted.
When parsing, any contents within the child element generate a parse error. When parsing, any contents within the child element generate a parse error.
#### Example #### Example
```rust ```rust
# use xso::FromXml; # use xso::FromXml;
#[derive(FromXml, Debug, PartialEq)] #[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 ### `text` meta
The `text` meta causes the field to be mapped to the text content of the 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 alloc::boxed::Box;
use crate::error::{Error, FromEventsError}; use crate::error::{Error, FromEventsError};
use crate::{FromEventsBuilder, FromXml}; use crate::{Context, FromEventsBuilder, FromXml};
/// Helper struct to construct an `Option<T>` from XML events. /// Helper struct to construct an `Option<T>` from XML events.
pub struct OptionBuilder<T: FromEventsBuilder>(T); pub struct OptionBuilder<T: FromEventsBuilder>(T);
@ -23,8 +23,8 @@ pub struct OptionBuilder<T: FromEventsBuilder>(T);
impl<T: FromEventsBuilder> FromEventsBuilder for OptionBuilder<T> { impl<T: FromEventsBuilder> FromEventsBuilder for OptionBuilder<T> {
type Output = Option<T::Output>; type Output = Option<T::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> {
self.0.feed(ev).map(|ok| ok.map(Some)) self.0.feed(ev, ctx).map(|ok| ok.map(Some))
} }
} }
@ -40,8 +40,9 @@ impl<T: FromXml> FromXml for Option<T> {
fn from_events( fn from_events(
name: rxml::QName, name: rxml::QName,
attrs: rxml::AttrMap, attrs: rxml::AttrMap,
ctx: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> { ) -> 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> { impl<T: FromEventsBuilder> FromEventsBuilder for BoxBuilder<T> {
type Output = Box<T::Output>; type Output = Box<T::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> {
self.0.feed(ev).map(|ok| ok.map(Box::new)) 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( fn from_events(
name: rxml::QName, name: rxml::QName,
attrs: rxml::AttrMap, attrs: rxml::AttrMap,
ctx: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> { ) -> 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> { impl<T: FromEventsBuilder, E: From<Error>> FromEventsBuilder for FallibleBuilder<T, E> {
type Output = Result<T::Output, 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 { match self.0 {
FallibleBuilderInner::Processing { FallibleBuilderInner::Processing {
ref mut depth, ref mut depth,
@ -130,7 +132,7 @@ impl<T: FromEventsBuilder, E: From<Error>> FromEventsBuilder for FallibleBuilder
rxml::Event::XmlDeclaration(..) | rxml::Event::Text(..) => Some(*depth), rxml::Event::XmlDeclaration(..) | rxml::Event::Text(..) => Some(*depth),
}; };
match builder.feed(ev) { match builder.feed(ev, ctx) {
Ok(Some(v)) => { Ok(Some(v)) => {
self.0 = FallibleBuilderInner::Done; self.0 = FallibleBuilderInner::Done;
return Ok(Some(Ok(v))); return Ok(Some(Ok(v)));
@ -214,8 +216,9 @@ impl<T: FromXml, E: From<Error>> FromXml for Result<T, E> {
fn from_events( fn from_events(
name: rxml::QName, name: rxml::QName,
attrs: rxml::AttrMap, attrs: rxml::AttrMap,
ctx: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> { ) -> Result<Self::Builder, FromEventsError> {
match T::from_events(name, attrs) { match T::from_events(name, attrs, ctx) {
Ok(builder) => Ok(FallibleBuilder(FallibleBuilderInner::Processing { Ok(builder) => Ok(FallibleBuilder(FallibleBuilderInner::Processing {
depth: 0, depth: 0,
builder, builder,
@ -248,7 +251,7 @@ impl Discard {
impl FromEventsBuilder for Discard { impl FromEventsBuilder for Discard {
type Output = (); 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 { match ev {
rxml::Event::StartElement(..) => { rxml::Event::StartElement(..) => {
self.depth = match self.depth.checked_add(1) { self.depth = match self.depth.checked_add(1) {
@ -284,7 +287,7 @@ pub struct EmptyBuilder {
impl FromEventsBuilder for EmptyBuilder { impl FromEventsBuilder for EmptyBuilder {
type Output = (); 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 { match ev {
rxml::Event::EndElement(..) => Ok(Some(())), rxml::Event::EndElement(..) => Ok(Some(())),
rxml::Event::StartElement(..) => Err(Error::Other(self.childerr)), rxml::Event::StartElement(..) => Err(Error::Other(self.childerr)),
@ -336,7 +339,11 @@ mod tests {
impl FromEventsBuilder for $name { impl FromEventsBuilder for $name {
type Output = $output; 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!(); unreachable!();
} }
} }
@ -355,6 +362,7 @@ mod tests {
fn from_events( fn from_events(
name: rxml::QName, name: rxml::QName,
attrs: rxml::AttrMap, attrs: rxml::AttrMap,
_ctx: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> { ) -> Result<Self::Builder, FromEventsError> {
Err(FromEventsError::Mismatch { name, attrs }) Err(FromEventsError::Mismatch { name, attrs })
} }
@ -366,7 +374,11 @@ mod tests {
impl FromXml for InitialError { impl FromXml for InitialError {
type Builder = InitialErrorBuilder; 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"))) Err(FromEventsError::Invalid(Error::Other("some error")))
} }
} }
@ -377,7 +389,7 @@ mod tests {
impl FromEventsBuilder for FailOnContentBuilder { impl FromEventsBuilder for FailOnContentBuilder {
type Output = FailOnContent; 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")) Err(Error::Other("content error"))
} }
} }
@ -388,7 +400,11 @@ mod tests {
impl FromXml for FailOnContent { impl FromXml for FailOnContent {
type Builder = FailOnContentBuilder; 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) Ok(FailOnContentBuilder)
} }
} }
@ -403,7 +419,7 @@ mod tests {
#[test] #[test]
fn fallible_builder_mismatch_passthrough() { 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 { .. }) => (), Err(FromEventsError::Mismatch { .. }) => (),
other => panic!("unexpected result: {:?}", other), other => panic!("unexpected result: {:?}", other),
} }
@ -411,15 +427,19 @@ mod tests {
#[test] #[test]
fn fallible_builder_initial_error_capture() { 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, Ok(v) => v,
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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")))) => (), Ok(Some(Err(Error::Other("some error")))) => (),
other => panic!("unexpected result: {:?}", other), other => panic!("unexpected result: {:?}", other),
}; };
@ -427,47 +447,66 @@ mod tests {
#[test] #[test]
fn fallible_builder_initial_error_capture_allows_nested_stuff() { 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, Ok(v) => v,
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), other => panic!("unexpected result: {:?}", other),
}; };
match builder.feed(Event::EndElement(EventMetrics::zero())) { match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), other => panic!("unexpected result: {:?}", other),
}; };
match builder.feed(Event::EndElement(EventMetrics::zero())) { match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), other => panic!("unexpected result: {:?}", other),
}; };
match builder.feed(Event::EndElement(EventMetrics::zero())) { match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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")))) => (), Ok(Some(Err(Error::Other("some error")))) => (),
other => panic!("unexpected result: {:?}", other), other => panic!("unexpected result: {:?}", other),
}; };
@ -475,11 +514,13 @@ mod tests {
#[test] #[test]
fn fallible_builder_content_error_capture() { 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, Ok(v) => v,
other => panic!("unexpected result: {:?}", other), 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")))) => (), Ok(Some(Err(Error::Other("content error")))) => (),
other => panic!("unexpected result: {:?}", other), other => panic!("unexpected result: {:?}", other),
}; };
@ -487,15 +528,20 @@ mod tests {
#[test] #[test]
fn fallible_builder_content_error_capture_with_more_content() { 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, Ok(v) => v,
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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")))) => (), Ok(Some(Err(Error::Other("content error")))) => (),
other => panic!("unexpected result: {:?}", other), other => panic!("unexpected result: {:?}", other),
}; };
@ -503,47 +549,67 @@ mod tests {
#[test] #[test]
fn fallible_builder_content_error_capture_with_nested_content() { 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, Ok(v) => v,
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), other => panic!("unexpected result: {:?}", other),
}; };
match builder.feed(Event::EndElement(EventMetrics::zero())) { match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), other => panic!("unexpected result: {:?}", other),
}; };
match builder.feed(Event::EndElement(EventMetrics::zero())) { match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), other => panic!("unexpected result: {:?}", other),
}; };
match builder.feed(Event::EndElement(EventMetrics::zero())) { match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), 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")))) => (), Ok(Some(Err(Error::Other("content error")))) => (),
other => panic!("unexpected result: {:?}", other), 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>; 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 /// Trait for a temporary object allowing to construct a struct from
/// [`rxml::Event`] items. /// [`rxml::Event`] items.
/// ///
@ -151,7 +178,11 @@ pub trait FromEventsBuilder {
/// Feeding more events after an error may result in panics, errors or /// Feeding more events after an error may result in panics, errors or
/// inconsistent result data, though it may never result in unsound or /// inconsistent result data, though it may never result in unsound or
/// unsafe behaviour. /// 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 /// Trait allowing to construct a struct from a stream of
@ -188,6 +219,7 @@ pub trait FromXml {
fn from_events( fn from_events(
name: rxml::QName, name: rxml::QName,
attrs: rxml::AttrMap, attrs: rxml::AttrMap,
ctx: &Context<'_>,
) -> Result<Self::Builder, self::error::FromEventsError>; ) -> Result<Self::Builder, self::error::FromEventsError>;
} }
@ -423,13 +455,15 @@ impl UnknownChildPolicy {
/// Attempt to transform a type implementing [`AsXml`] into another /// Attempt to transform a type implementing [`AsXml`] into another
/// type which implements [`FromXml`]. /// type which implements [`FromXml`].
pub fn transform<T: FromXml, F: AsXml>(from: &F) -> Result<T, self::error::Error> { 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 mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
let (qname, attrs) = match iter.next() { let (qname, attrs) = match iter.next() {
Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs), Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
Some(Err(e)) => return Err(e), Some(Err(e)) => return Err(e),
_ => panic!("into_event_iter did not start with StartElement event!"), _ => 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, 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)
@ -438,7 +472,8 @@ 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?;
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); 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>( pub fn try_from_element<T: FromXml>(
from: minidom::Element, from: minidom::Element,
) -> Result<T, self::error::FromElementError> { ) -> Result<T, self::error::FromElementError> {
let mut languages = rxml::xml_lang::XmlLangStack::new();
let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?; 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, 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))
@ -486,7 +524,8 @@ pub fn try_from_element<T: FromXml>(
let iter = self::rxml_util::ItemToEvent::new(iter); let iter = self::rxml_util::ItemToEvent::new(iter);
for event in iter { for event in iter {
let event = event?; 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); 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")] #[cfg(feature = "std")]
fn read_start_event<I: io::BufRead>( fn read_start_event(
r: &mut rxml::Reader<I>, r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
) -> Result<(rxml::QName, rxml::AttrMap), self::error::Error> { ) -> Result<(rxml::QName, rxml::AttrMap), self::error::Error> {
for ev in r { for ev in r {
match map_nonio_error(ev)? { match map_nonio_error(ev)? {
@ -531,17 +570,17 @@ fn read_start_event<I: io::BufRead>(
/// containing XML data. /// containing XML data.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> { 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 (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, 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)
} }
Err(self::error::FromEventsError::Invalid(e)) => return Err(e), Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
}; };
for ev in reader { while let Some(ev) = reader.next() {
if let Some(v) = builder.feed(map_nonio_error(ev)?)? { if let Some(v) = builder.feed(map_nonio_error(ev)?, &Context::new(reader.language()))? {
return Ok(v); return Ok(v);
} }
} }
@ -549,8 +588,8 @@ pub fn from_bytes<T: FromXml>(mut buf: &[u8]) -> Result<T, self::error::Error> {
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
fn read_start_event_io<I: io::BufRead>( fn read_start_event_io(
r: &mut rxml::Reader<I>, r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
) -> io::Result<(rxml::QName, rxml::AttrMap)> { ) -> io::Result<(rxml::QName, rxml::AttrMap)> {
for ev in r { for ev in r {
match ev? { match ev? {
@ -575,9 +614,9 @@ fn read_start_event_io<I: io::BufRead>(
/// Attempt to parse a type implementing [`FromXml`] from a reader. /// Attempt to parse a type implementing [`FromXml`] from a reader.
#[cfg(feature = "std")] #[cfg(feature = "std")]
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::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) { let mut builder = match T::from_events(name, attrs, &Context::new(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)
@ -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)) 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 if let Some(v) = builder
.feed(ev?) .feed(ev?, &Context::new(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);

View file

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