Track xml:lang throughout parsing
This commit is contained in:
parent
31d7bbf622
commit
d9a21dd8c9
21 changed files with 446 additions and 88 deletions
|
|
@ -229,6 +229,7 @@ impl ::xso::FromXml for Iq {
|
|||
fn from_events(
|
||||
qname: ::xso::exports::rxml::QName,
|
||||
attrs: ::xso::exports::rxml::AttrMap,
|
||||
_ctx: &::xso::Context<'_>,
|
||||
) -> Result<Self::Builder, ::xso::error::FromEventsError> {
|
||||
if qname.0 != crate::ns::DEFAULT_NS || qname.1 != "iq" {
|
||||
return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ impl ::xso::FromXml for Description {
|
|||
fn from_events(
|
||||
qname: ::xso::exports::rxml::QName,
|
||||
attrs: ::xso::exports::rxml::AttrMap,
|
||||
_ctx: &::xso::Context<'_>,
|
||||
) -> Result<Self::Builder, ::xso::error::FromEventsError> {
|
||||
if qname.1 != "description" {
|
||||
return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });
|
||||
|
|
@ -263,6 +264,7 @@ impl ::xso::FromXml for Transport {
|
|||
fn from_events(
|
||||
qname: ::xso::exports::rxml::QName,
|
||||
attrs: ::xso::exports::rxml::AttrMap,
|
||||
_ctx: &::xso::Context<'_>,
|
||||
) -> Result<Self::Builder, ::xso::error::FromEventsError> {
|
||||
if qname.1 != "transport" {
|
||||
return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });
|
||||
|
|
|
|||
|
|
@ -290,6 +290,7 @@ impl ::xso::FromXml for Transport {
|
|||
fn from_events(
|
||||
qname: ::xso::exports::rxml::QName,
|
||||
attrs: ::xso::exports::rxml::AttrMap,
|
||||
_ctx: &::xso::Context<'_>,
|
||||
) -> Result<Self::Builder, ::xso::error::FromEventsError> {
|
||||
if qname.0 != crate::ns::JINGLE_S5B || qname.1 != "transport" {
|
||||
return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });
|
||||
|
|
|
|||
|
|
@ -2489,3 +2489,65 @@ fn text_vs_attribute_ordering_compile_bug_roundtrip_with_parent() {
|
|||
"<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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@ macro_rules! generate_attribute_enum {
|
|||
fn from_events(
|
||||
qname: ::xso::exports::rxml::QName,
|
||||
attrs: ::xso::exports::rxml::AttrMap,
|
||||
_ctx: &::xso::Context<'_>,
|
||||
) -> Result<Self::Builder, ::xso::error::FromEventsError> {
|
||||
if qname.0 != crate::ns::$ns || qname.1 != $name {
|
||||
return Err(::xso::error::FromEventsError::Mismatch {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ use tokio::{
|
|||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
|
|
@ -165,6 +167,9 @@ pin_project_lite::pin_project! {
|
|||
#[pin]
|
||||
parser: rxml::AsyncReader<CaptureBufRead<Io>>,
|
||||
|
||||
// Tracker for `xml:lang` data.
|
||||
lang_stack: XmlLangStack,
|
||||
|
||||
// The writer used for serialising data.
|
||||
writer: rxml::writer::Encoder<rxml::writer::SimpleNamespaces>,
|
||||
|
||||
|
|
@ -254,6 +259,7 @@ impl<Io: AsyncBufRead + AsyncWrite> RawXmlStream<Io> {
|
|||
Self {
|
||||
parser: rxml::AsyncReader::wrap(io, parser),
|
||||
writer: Self::new_writer(stream_ns),
|
||||
lang_stack: XmlLangStack::new(),
|
||||
timeouts: TimeoutState::new(timeouts),
|
||||
tx_buffer_logged: 0,
|
||||
stream_ns,
|
||||
|
|
@ -270,6 +276,7 @@ impl<Io: AsyncBufRead + AsyncWrite> RawXmlStream<Io> {
|
|||
pub(super) fn reset_state(self: Pin<&mut Self>) {
|
||||
let this = self.project();
|
||||
*this.parser.parser_pinned() = rxml::Parser::default();
|
||||
*this.lang_stack = XmlLangStack::new();
|
||||
*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);
|
||||
RawXmlStream {
|
||||
parser,
|
||||
lang_stack: XmlLangStack::new(),
|
||||
timeouts: self.timeouts,
|
||||
writer: self.writer,
|
||||
tx_buffer: self.tx_buffer,
|
||||
|
|
@ -360,6 +368,10 @@ impl<Io: AsyncBufRead> Stream for RawXmlStream<Io> {
|
|||
match v.transpose() {
|
||||
// Skip the XML declaration, nobody wants to hear about that.
|
||||
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))),
|
||||
}
|
||||
}
|
||||
|
|
@ -635,9 +647,13 @@ impl<T: FromXml> ReadXsoState<T> {
|
|||
}
|
||||
}
|
||||
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(
|
||||
<Result<T, xso::error::Error> as FromXml>::from_events(name, attrs)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
|
||||
<Result<T, xso::error::Error> as FromXml>::from_events(
|
||||
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.
|
||||
|
|
@ -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) => {
|
||||
*self = ReadXsoState::Done;
|
||||
source.as_mut().stream_pinned().discard_capture();
|
||||
|
|
|
|||
|
|
@ -318,7 +318,7 @@ impl Compound {
|
|||
substate_data,
|
||||
&builder,
|
||||
).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) => {
|
||||
#collect
|
||||
::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
|
||||
|
|
@ -467,7 +467,7 @@ impl Compound {
|
|||
substate_data,
|
||||
&discard_builder_ty,
|
||||
).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::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
|
||||
#builder_data_ident,
|
||||
|
|
|
|||
|
|
@ -547,7 +547,7 @@ impl ItemDef for EnumDef {
|
|||
Ok(FromXmlParts {
|
||||
defs,
|
||||
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(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ impl Field for ChildField {
|
|||
|
||||
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());
|
||||
|
||||
(
|
||||
|
|
@ -307,7 +307,7 @@ impl ExtractDef {
|
|||
)?;
|
||||
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();
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ impl Field for ElementField {
|
|||
if #field_access.is_some() {
|
||||
::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs })
|
||||
} else {
|
||||
#from_events(name, attrs)
|
||||
#from_events(name, attrs, ctx)
|
||||
}
|
||||
}),
|
||||
builder,
|
||||
|
|
|
|||
72
xso-proc/src/field/lang.rs
Normal file
72
xso-proc/src/field/lang.rs
Normal 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,
|
||||
)
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ mod child;
|
|||
#[cfg(feature = "minidom")]
|
||||
mod element;
|
||||
mod flag;
|
||||
mod lang;
|
||||
mod text;
|
||||
|
||||
use self::attribute::AttributeField;
|
||||
|
|
@ -28,6 +29,7 @@ use self::child::{ChildField, ExtractDef};
|
|||
#[cfg(feature = "minidom")]
|
||||
use self::element::ElementField;
|
||||
use self::flag::FlagField;
|
||||
use self::lang::LangField;
|
||||
use self::text::TextField;
|
||||
|
||||
/// Code slices necessary for declaring and initializing a temporary variable
|
||||
|
|
@ -444,6 +446,8 @@ fn new_field(
|
|||
xml_name,
|
||||
}))
|
||||
}
|
||||
|
||||
XmlFieldMeta::Language { span: _ } => Ok(Box::new(LangField)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ fn from_xml_impl(input: Item) -> Result<TokenStream> {
|
|||
fn from_events(
|
||||
name: ::xso::exports::rxml::QName,
|
||||
attrs: ::xso::exports::rxml::AttrMap,
|
||||
ctx: &::xso::Context<'_>,
|
||||
) -> ::core::result::Result<Self::Builder, ::xso::error::FromEventsError> {
|
||||
#from_events_body
|
||||
}
|
||||
|
|
|
|||
|
|
@ -874,6 +874,14 @@ pub(crate) enum XmlFieldMeta {
|
|||
/// The namespace/name keys.
|
||||
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 {
|
||||
|
|
@ -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
|
||||
/// of that nested meta.
|
||||
fn parse_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
|
||||
|
|
@ -1197,6 +1212,8 @@ impl XmlFieldMeta {
|
|||
Self::element_from_meta(meta)
|
||||
} else if meta.path.is_ident("flag") {
|
||||
Self::flag_from_meta(meta)
|
||||
} else if meta.path.is_ident("lang") {
|
||||
Self::lang_from_meta(meta)
|
||||
} else {
|
||||
Err(Error::new_spanned(meta.path, "unsupported field meta"))
|
||||
}
|
||||
|
|
@ -1280,6 +1297,7 @@ impl XmlFieldMeta {
|
|||
Self::Extract { ref span, .. } => *span,
|
||||
Self::Element { ref span, .. } => *span,
|
||||
Self::Flag { ref span, .. } => *span,
|
||||
Self::Language { ref span, .. } => *span,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -509,7 +509,7 @@ impl FromEventsStateMachine {
|
|||
}
|
||||
|
||||
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 {
|
||||
#advance_match_arms
|
||||
}.and_then(|__ok| {
|
||||
|
|
@ -527,8 +527,9 @@ impl FromEventsStateMachine {
|
|||
fn new(
|
||||
name: ::xso::exports::rxml::QName,
|
||||
attrs: ::xso::exports::rxml::AttrMap,
|
||||
ctx: &::xso::Context<'_>,
|
||||
) -> ::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 {
|
||||
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");
|
||||
match inner.advance(ev)? {
|
||||
match inner.advance(ev, ctx)? {
|
||||
::core::ops::ControlFlow::Continue(mut value) => {
|
||||
#validate_call
|
||||
::core::result::Result::Ok(::core::option::Option::Some(value))
|
||||
|
|
@ -558,6 +559,7 @@ impl FromEventsStateMachine {
|
|||
fn new(
|
||||
name: ::xso::exports::rxml::QName,
|
||||
mut attrs: ::xso::exports::rxml::AttrMap,
|
||||
ctx: &::xso::Context<'_>,
|
||||
) -> ::core::result::Result<Self, ::xso::error::FromEventsError> {
|
||||
#init_body
|
||||
{ let _ = &mut attrs; }
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ impl StructInner {
|
|||
&from_xml_builder_ty,
|
||||
)
|
||||
.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::result::Result::Ok(::core::ops::ControlFlow::Continue(#output_cons))
|
||||
}
|
||||
|
|
@ -213,7 +213,7 @@ impl StructInner {
|
|||
})
|
||||
],
|
||||
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 {
|
||||
defs,
|
||||
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(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Reference in a new issue