From d9a21dd8c9bb2a57952a663d3cfec96bfae3d9eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Fri, 18 Apr 2025 17:20:06 +0200 Subject: [PATCH] Track xml:lang throughout parsing --- parsers/src/iq.rs | 1 + parsers/src/jingle.rs | 2 + parsers/src/jingle_s5b.rs | 1 + parsers/src/util/macro_tests.rs | 62 +++++++++++ parsers/src/util/macros.rs | 1 + tokio-xmpp/src/xmlstream/common.rs | 26 ++++- xso-proc/src/compound.rs | 4 +- xso-proc/src/enums.rs | 2 +- xso-proc/src/field/child.rs | 4 +- xso-proc/src/field/element.rs | 2 +- xso-proc/src/field/lang.rs | 72 +++++++++++++ xso-proc/src/field/mod.rs | 4 + xso-proc/src/lib.rs | 1 + xso-proc/src/meta.rs | 18 ++++ xso-proc/src/state.rs | 10 +- xso-proc/src/structs.rs | 6 +- xso/ChangeLog | 4 + xso/src/from_xml_doc.md | 60 +++++++++++ xso/src/fromxml.rs | 160 ++++++++++++++++++++--------- xso/src/lib.rs | 73 ++++++++++--- xso/src/minidom_compat.rs | 21 ++-- 21 files changed, 446 insertions(+), 88 deletions(-) create mode 100644 xso-proc/src/field/lang.rs diff --git a/parsers/src/iq.rs b/parsers/src/iq.rs index 6caff133665120ec7fb0694c02ac7a2d7b796e50..bfd63e19444213e68de42147ef46262096ca60bc 100644 --- a/parsers/src/iq.rs +++ b/parsers/src/iq.rs @@ -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 { if qname.0 != crate::ns::DEFAULT_NS || qname.1 != "iq" { return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs }); diff --git a/parsers/src/jingle.rs b/parsers/src/jingle.rs index f216d6d23ddf4464bb2eb02deff2b11a9134f2e1..abdb5dd278265f3e1223115d5008326dc88a5b92 100644 --- a/parsers/src/jingle.rs +++ b/parsers/src/jingle.rs @@ -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 { 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 { if qname.1 != "transport" { return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs }); diff --git a/parsers/src/jingle_s5b.rs b/parsers/src/jingle_s5b.rs index 474d6eb8bd96e4cb160efc7632648df080c23dd1..7a8dc7753a96c0c0bde5b24f15bf5c3c74f86503 100644 --- a/parsers/src/jingle_s5b.rs +++ b/parsers/src/jingle_s5b.rs @@ -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 { if qname.0 != crate::ns::JINGLE_S5B || qname.1 != "transport" { return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs }); diff --git a/parsers/src/util/macro_tests.rs b/parsers/src/util/macro_tests.rs index a477ed9495478865fcdc96bb0bcaa984cbf01d69..e82323a0b026a57f5aa4ac557079399d16e2dd14 100644 --- a/parsers/src/util/macro_tests.rs +++ b/parsers/src/util/macro_tests.rs @@ -2489,3 +2489,65 @@ fn text_vs_attribute_ordering_compile_bug_roundtrip_with_parent() { "foo", ); } + +#[derive(FromXml, AsXml, PartialEq, Debug, Clone)] +#[xml(namespace = NS1, name = "elem")] +struct Language { + #[xml(child(default))] + child: core::option::Option>, + + #[xml(lang)] + lang: core::option::Option, +} + +#[test] +fn language_roundtrip_absent() { + #[allow(unused_imports)] + use core::{ + option::Option::{None, Some}, + result::Result::{Err, Ok}, + }; + roundtrip_full::(""); +} + +#[test] +fn language_roundtrip_present() { + #[allow(unused_imports)] + use core::{ + option::Option::{None, Some}, + result::Result::{Err, Ok}, + }; + roundtrip_full::(""); +} + +#[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::( + "", + ) { + 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), + } +} diff --git a/parsers/src/util/macros.rs b/parsers/src/util/macros.rs index 7cfb784054289d18d8fd1d2dc32ef831ba5d8cc5..440e683923d679b0b5615c2567a4ddd2a41908cb 100644 --- a/parsers/src/util/macros.rs +++ b/parsers/src/util/macros.rs @@ -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 { if qname.0 != crate::ns::$ns || qname.1 != $name { return Err(::xso::error::FromEventsError::Mismatch { diff --git a/tokio-xmpp/src/xmlstream/common.rs b/tokio-xmpp/src/xmlstream/common.rs index d54b5ab0ba940fb92053504a6824997715d7af77..fbb807be8b1cd095112c5b1bb3f1e7c53a5b98df 100644 --- a/tokio-xmpp/src/xmlstream/common.rs +++ b/tokio-xmpp/src/xmlstream/common.rs @@ -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>, + // Tracker for `xml:lang` data. + lang_stack: XmlLangStack, + // The writer used for serialising data. writer: rxml::writer::Encoder, @@ -254,6 +259,7 @@ impl RawXmlStream { 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 RawXmlStream { 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 RawXmlStream { 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 Stream for RawXmlStream { 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 ReadXsoState { } } 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( - as FromXml>::from_events(name, attrs) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?, + 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 ReadXsoState { } }; - 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(); diff --git a/xso-proc/src/compound.rs b/xso-proc/src/compound.rs index d91fa8c3a5244afcb623e17d61faec5974d8ea0b..cc4af1a5fa57aa309b3374116cb66bd1e9919e2f 100644 --- a/xso-proc/src/compound.rs +++ b/xso-proc/src/compound.rs @@ -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, diff --git a/xso-proc/src/enums.rs b/xso-proc/src/enums.rs index b10c392a6499f6e14c362e942e1cef86059080ac..d960bb2bc355bf2483bc8e1c81fed553416892e0 100644 --- a/xso-proc/src/enums.rs +++ b/xso-proc/src/enums.rs @@ -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(), }) diff --git a/xso-proc/src/field/child.rs b/xso-proc/src/field/child.rs index 79fb4de3e2979947645965f336acd9196b031b7e..14e71708ebcb06672180db06f61d55b71095d7eb 100644 --- a/xso-proc/src/field/child.rs +++ b/xso-proc/src/field/child.rs @@ -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(); diff --git a/xso-proc/src/field/element.rs b/xso-proc/src/field/element.rs index 16f482224809bbeee23f73e7c462939236bd325d..d6ca1accab3b810c027bfd5429ae221cfa22efeb 100644 --- a/xso-proc/src/field/element.rs +++ b/xso-proc/src/field/element.rs @@ -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, diff --git a/xso-proc/src/field/lang.rs b/xso-proc/src/field/lang.rs new file mode 100644 index 0000000000000000000000000000000000000000..8390eea1a65c1f460b429be0f40dc4a2a201d25e --- /dev/null +++ b/xso-proc/src/field/lang.rs @@ -0,0 +1,72 @@ +// Copyright (c) 2025 Jonas Schäfer +// +// 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 { + 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 { + 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, + ) + ) + }, + }) + } +} diff --git a/xso-proc/src/field/mod.rs b/xso-proc/src/field/mod.rs index d345dd1089220d9ef3681ab918bf62a5f9273b06..c3c59c3c999ea7ccde6290671ae286c427eb157c 100644 --- a/xso-proc/src/field/mod.rs +++ b/xso-proc/src/field/mod.rs @@ -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)), } } diff --git a/xso-proc/src/lib.rs b/xso-proc/src/lib.rs index 8388cddc7f29a24e868f2cfb6100947bc51500ef..9045cee61bca4b1bce8bd8771c3911439aead362 100644 --- a/xso-proc/src/lib.rs +++ b/xso-proc/src/lib.rs @@ -83,6 +83,7 @@ fn from_xml_impl(input: Item) -> Result { fn from_events( name: ::xso::exports::rxml::QName, attrs: ::xso::exports::rxml::AttrMap, + ctx: &::xso::Context<'_>, ) -> ::core::result::Result { #from_events_body } diff --git a/xso-proc/src/meta.rs b/xso-proc/src/meta.rs index c2f0951e9009822e2e6506609258b27a82bf80ec..291b0432a06c5103e0d68d7eb198782bb3385d81 100644 --- a/xso-proc/src/meta.rs +++ b/xso-proc/src/meta.rs @@ -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 { + 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 { @@ -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, } } diff --git a/xso-proc/src/state.rs b/xso-proc/src/state.rs index 0c92baa05db9d35576369b2dd6e6461e7d4f37e5..55b171e665d49ad247a8aa444f1457c3d41600e8 100644 --- a/xso-proc/src/state.rs +++ b/xso-proc/src/state.rs @@ -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, ::xso::error::Error> { + fn advance(mut self, ev: ::xso::exports::rxml::Event, ctx: &::xso::Context<'_>) -> ::core::result::Result<::core::ops::ControlFlow, ::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 { - #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, ::xso::error::Error> { + fn feed(&mut self, ev: ::xso::exports::rxml::Event, ctx: &::xso::Context<'_>) -> ::core::result::Result<::core::option::Option, ::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 { #init_body { let _ = &mut attrs; } diff --git a/xso-proc/src/structs.rs b/xso-proc/src/structs.rs index 9e2e8d5edd4c700dfd675a7715f9fcf4262f7ae3..588b7d7fcc302b7905d7b91cc6f6f17354d8e168 100644 --- a/xso-proc/src/structs.rs +++ b/xso-proc/src/structs.rs @@ -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(), }) diff --git a/xso/ChangeLog b/xso/ChangeLog index 002c8236c57d6ac501b4c36082f22fcb618c8282..f1662d165db02015b8c09f975c81c8681177ff45 100644 --- a/xso/ChangeLog +++ b/xso/ChangeLog @@ -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 diff --git a/xso/src/from_xml_doc.md b/xso/src/from_xml_doc.md index 7744bfd002ac025366e8cb58be111e79ebaad238..7d62ee567c8c33f1f077d25a098dec2dab25094a 100644 --- a/xso/src/from_xml_doc.md +++ b/xso/src/from_xml_doc.md @@ -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`. + +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, +}; + +#[derive(FromXml, Debug, PartialEq)] +#[xml(namespace = "urn:example", name = "foo")] +struct Foo { + #[xml(child)] + child: Bar, +}; + +// `xml:lang` gets inherited from to +let foo: Foo = xso::from_bytes(b"").unwrap(); +assert_eq!(foo, Foo { + child: Bar { + lang: Some("en".to_owned()), + }, +}); + +// `xml:lang` gets set/overwritten in +let foo: Foo = xso::from_bytes(b"").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 diff --git a/xso/src/fromxml.rs b/xso/src/fromxml.rs index 5bfc8006e15547cebbc92d255a00100901592c88..6964019aa217827453b13702d9deb967d124ac54 100644 --- a/xso/src/fromxml.rs +++ b/xso/src/fromxml.rs @@ -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` from XML events. pub struct OptionBuilder(T); @@ -23,8 +23,8 @@ pub struct OptionBuilder(T); impl FromEventsBuilder for OptionBuilder { type Output = Option; - fn feed(&mut self, ev: rxml::Event) -> Result, Error> { - self.0.feed(ev).map(|ok| ok.map(Some)) + fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result, Error> { + self.0.feed(ev, ctx).map(|ok| ok.map(Some)) } } @@ -40,8 +40,9 @@ impl FromXml for Option { fn from_events( name: rxml::QName, attrs: rxml::AttrMap, + ctx: &Context<'_>, ) -> Result { - Ok(OptionBuilder(T::from_events(name, attrs)?)) + Ok(OptionBuilder(T::from_events(name, attrs, ctx)?)) } } @@ -51,8 +52,8 @@ pub struct BoxBuilder(Box); impl FromEventsBuilder for BoxBuilder { type Output = Box; - fn feed(&mut self, ev: rxml::Event) -> Result, Error> { - self.0.feed(ev).map(|ok| ok.map(Box::new)) + fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result, Error> { + self.0.feed(ev, ctx).map(|ok| ok.map(Box::new)) } } @@ -63,8 +64,9 @@ impl FromXml for Box { fn from_events( name: rxml::QName, attrs: rxml::AttrMap, + ctx: &Context<'_>, ) -> Result { - 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(FallibleBuilderInner); impl> FromEventsBuilder for FallibleBuilder { type Output = Result; - fn feed(&mut self, ev: rxml::Event) -> Result, Error> { + fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result, Error> { match self.0 { FallibleBuilderInner::Processing { ref mut depth, @@ -130,7 +132,7 @@ impl> 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> FromXml for Result { fn from_events( name: rxml::QName, attrs: rxml::AttrMap, + ctx: &Context<'_>, ) -> Result { - 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, Error> { + fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result, 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, Error> { + fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result, 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, Error> { + fn feed( + &mut self, + _: Event, + _: &Context<'_>, + ) -> Result, Error> { unreachable!(); } } @@ -355,6 +362,7 @@ mod tests { fn from_events( name: rxml::QName, attrs: rxml::AttrMap, + _ctx: &Context<'_>, ) -> Result { 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 { + fn from_events( + _: rxml::QName, + _: rxml::AttrMap, + _: &Context<'_>, + ) -> Result { 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, Error> { + fn feed(&mut self, _: Event, _: &Context<'_>) -> Result, 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 { + fn from_events( + _: rxml::QName, + _: rxml::AttrMap, + _: &Context<'_>, + ) -> Result { Ok(FailOnContentBuilder) } } @@ -403,7 +419,7 @@ mod tests { #[test] fn fallible_builder_mismatch_passthrough() { - match Result::::from_events(qname(), attrs()) { + match Result::::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::::from_events(qname(), attrs()) { + let ctx = Context::empty(); + let mut builder = match Result::::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::::from_events(qname(), attrs()) { + let ctx = Context::empty(); + let mut builder = match Result::::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::::from_events(qname(), attrs()) { + let ctx = Context::empty(); + let mut builder = match Result::::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::::from_events(qname(), attrs()) { + let ctx = Context::empty(); + let mut builder = match Result::::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::::from_events(qname(), attrs()) { + let ctx = Context::empty(); + let mut builder = match Result::::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), }; diff --git a/xso/src/lib.rs b/xso/src/lib.rs index f24cc6fe3ea74675fb4aeefd5883aad46b4aaac3..cb66ae97b974f1c770e112e383e95a6add668cbb 100644 --- a/xso/src/lib.rs +++ b/xso/src/lib.rs @@ -128,6 +128,33 @@ pub trait AsXml { fn as_xml_iter(&self) -> Result, 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, self::error::Error>; + fn feed( + &mut self, + ev: rxml::Event, + ctx: &Context<'_>, + ) -> Result, 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; } @@ -423,13 +455,15 @@ impl UnknownChildPolicy { /// Attempt to transform a type implementing [`AsXml`] into another /// type which implements [`FromXml`]. pub fn transform(from: &F) -> Result { + 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(from: &F) -> Result(from: &F) -> Result( from: minidom::Element, ) -> Result { + 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( 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(r: Result) -> Result } #[cfg(feature = "std")] -fn read_start_event( - r: &mut rxml::Reader, +fn read_start_event( + r: &mut impl Iterator>, ) -> 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( /// containing XML data. #[cfg(feature = "std")] pub fn from_bytes(mut buf: &[u8]) -> Result { - 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(mut buf: &[u8]) -> Result { } #[cfg(feature = "std")] -fn read_start_event_io( - r: &mut rxml::Reader, +fn read_start_event_io( + r: &mut impl Iterator>, ) -> io::Result<(rxml::QName, rxml::AttrMap)> { for ev in r { match ev? { @@ -575,9 +614,9 @@ fn read_start_event_io( /// Attempt to parse a type implementing [`FromXml`] from a reader. #[cfg(feature = "std")] pub fn from_reader(r: R) -> io::Result { - 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(r: R) -> io::Result { 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); diff --git a/xso/src/minidom_compat.rs b/xso/src/minidom_compat.rs index c05551a710bad86e0c3f486e85af71ffd0a8fa7b..79c5f44df85ab1666da590dc2fb2f6dbb26baa06 100644 --- a/xso/src/minidom_compat.rs +++ b/xso/src/minidom_compat.rs @@ -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, Error> { + fn feed(&mut self, ev: Event, ctx: &Context<'_>) -> Result, 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 { Ok(Self::Builder::new(qname, attrs)) } @@ -418,7 +419,13 @@ where pub fn new(qname: rxml::QName, attrs: rxml::AttrMap) -> Result { 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, Error> { - match self.inner.feed(ev) { + fn feed(&mut self, ev: Event, ctx: &Context<'_>) -> Result, Error> { + match self.inner.feed(ev, ctx) { Ok(Some(v)) => Ok(Some(v.try_into()?)), Ok(None) => Ok(None), Err(e) => Err(e),