diff --git a/parsers/src/util/macro_tests.rs b/parsers/src/util/macro_tests.rs index 4be80c6a0e43c9ec73205a08ea7f207a62403c08..1a7048920df84a8fb4c3cda24a2cc360dd1d8bda 100644 --- a/parsers/src/util/macro_tests.rs +++ b/parsers/src/util/macro_tests.rs @@ -94,6 +94,14 @@ fn empty_roundtrip() { roundtrip_full::(""); } +#[test] +fn empty_xml_name_matcher_is_specific() { + assert_eq!( + Empty::xml_name_matcher(), + xso::fromxml::XmlNameMatcher::Specific(NS1, "foo") + ); +} + #[test] fn empty_name_mismatch() { #[allow(unused_imports)] @@ -717,6 +725,14 @@ enum NameSwitchedEnum { }, } +#[test] +fn name_switched_enum_matcher_is_in_namespace() { + assert_eq!( + NameSwitchedEnum::xml_name_matcher(), + xso::fromxml::XmlNameMatcher::InNamespace(NS1) + ); +} + #[test] fn name_switched_enum_positive_variant_1() { #[allow(unused_imports)] @@ -1844,6 +1860,14 @@ enum DynamicEnum { }, } +#[test] +fn dynamic_enum_matcher_is_any() { + assert_eq!( + DynamicEnum::xml_name_matcher(), + xso::fromxml::XmlNameMatcher::Any + ); +} + #[test] fn dynamic_enum_roundtrip_a() { #[allow(unused_imports)] @@ -1903,6 +1927,27 @@ fn fallible_parse_positive_err() { } } +#[derive(FromXml, AsXml, PartialEq, Debug, Clone)] +#[xml()] +enum DynamicEnumWithSharedNamespace { + #[xml(transparent)] + A(RequiredAttribute), + + #[xml(namespace = NS1, name = "b")] + B { + #[xml(text)] + contents: String, + }, +} + +#[test] +fn dynamic_enum_with_shared_namespace_matcher_is_in_namespace() { + assert_eq!( + DynamicEnumWithSharedNamespace::xml_name_matcher(), + xso::fromxml::XmlNameMatcher::InNamespace(NS1) + ); +} + #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "parent")] struct ExtractTupleToCollection { diff --git a/xso-proc/src/common.rs b/xso-proc/src/common.rs index 565d7fa414f0b7adfc7c929371d355224f13a1c1..1bc09d05eaf92d6f7d78d1bdde7cbc37a37cd6a9 100644 --- a/xso-proc/src/common.rs +++ b/xso-proc/src/common.rs @@ -7,8 +7,45 @@ //! Definitions common to both enums and structs use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; use syn::*; +/// Template which renders to a `xso::fromxml::XmlNameMatcher` value. +pub(crate) enum XmlNameMatcher { + /// Renders as `xso::fromxml::XmlNameMatcher::Any`. + #[allow(dead_code)] // We keep it for completeness. + Any, + + /// Renders as `xso::fromxml::XmlNameMatcher::InNamespace(#0)`. + InNamespace(TokenStream), + + /// Renders as `xso::fromxml::XmlNameMatcher::Specific(#0, #1)`. + Specific(TokenStream, TokenStream), + + /// Renders as `#0`. + /// + /// This is an escape hatch for more complicated constructs, e.g. when + /// a superset of multiple matchers is required. + Custom(TokenStream), +} + +impl ToTokens for XmlNameMatcher { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Any => tokens.extend(quote! { + ::xso::fromxml::XmlNameMatcher::<'static>::Any + }), + Self::InNamespace(ref namespace) => tokens.extend(quote! { + ::xso::fromxml::XmlNameMatcher::<'static>::InNamespace(#namespace) + }), + Self::Specific(ref namespace, ref name) => tokens.extend(quote! { + ::xso::fromxml::XmlNameMatcher::<'static>::Specific(#namespace, #name) + }), + Self::Custom(ref stream) => tokens.extend(stream.clone()), + } + } +} + /// Parts necessary to construct a `::xso::FromXml` implementation. pub(crate) struct FromXmlParts { /// Additional items necessary for the implementation. @@ -19,6 +56,9 @@ pub(crate) struct FromXmlParts { /// The name of the type which is the `::xso::FromXml::Builder`. pub(crate) builder_ty_ident: Ident, + + /// The `XmlNameMatcher` to pre-select elements for this implementation. + pub(crate) name_matcher: XmlNameMatcher, } /// Parts necessary to construct a `::xso::AsXml` implementation. diff --git a/xso-proc/src/enums.rs b/xso-proc/src/enums.rs index 7ca4b0f01fbc2e6e35d245e6c2976697ef9af49f..3debf457a189f5e159a250d05eab5ec049770345 100644 --- a/xso-proc/src/enums.rs +++ b/xso-proc/src/enums.rs @@ -12,7 +12,7 @@ use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::*; -use crate::common::{AsXmlParts, FromXmlParts, ItemDef}; +use crate::common::{AsXmlParts, FromXmlParts, ItemDef, XmlNameMatcher}; use crate::compound::Compound; use crate::error_message::ParentRef; use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta}; @@ -203,6 +203,16 @@ impl NameSwitchedEnum { }) } + /// Provide the `XmlNameMatcher` template for this enum. + /// + /// Name-switched enums always return a matcher in the namespace of the + /// elements they match against. + fn xml_name_matcher(&self) -> Result { + Ok(XmlNameMatcher::InNamespace( + self.namespace.to_token_stream(), + )) + } + /// Build the deserialisation statemachine for the name-switched enum. fn make_from_events_statemachine( &self, @@ -461,6 +471,17 @@ impl AttributeSwitchedEnum { }) } + /// Provide the `XmlNameMatcher` template for this enum. + /// + /// Attribute-switched enums always return a matcher specific to the + /// element they operate on. + fn xml_name_matcher(&self) -> Result { + Ok(XmlNameMatcher::Specific( + self.elem_namespace.to_token_stream(), + self.elem_name.to_token_stream(), + )) + } + /// Build the deserialisation statemachine for the attribute-switched enum. fn make_from_events_statemachine( &self, @@ -613,6 +634,31 @@ impl DynamicEnum { Ok(Self { variants }) } + /// Provide the `XmlNameMatcher` template for this dynamic enum. + /// + /// Dynamic enums return the superset of the matchers of their variants, + /// which in many cases will be XmlNameMatcher::Any. + fn xml_name_matcher(&self) -> Result { + let mut iter = self.variants.iter(); + let Some(first) = iter.next() else { + // Since the enum has no variants, it cannot match anything. We + // use an empty namespace and an empty name, which will never + // match. + return Ok(XmlNameMatcher::Custom(quote! { + ::xso::fromxml::XmlNameMatcher::<'static>::Specific("", "") + })); + }; + + let first_matcher = first.inner.xml_name_matcher()?; + let mut composition = quote! { #first_matcher }; + for variant in iter { + let next_matcher = variant.inner.xml_name_matcher()?; + composition.extend(quote! { .superset(#next_matcher) }); + } + + Ok(XmlNameMatcher::Custom(composition)) + } + /// Build the deserialisation statemachine for the dynamic enum. fn make_from_events_statemachine( &self, @@ -781,6 +827,15 @@ impl EnumInner { } } + /// Provide the `XmlNameMatcher` template for this enum. + fn xml_name_matcher(&self) -> Result { + match self { + Self::NameSwitched(ref inner) => inner.xml_name_matcher(), + Self::AttributeSwitched(ref inner) => inner.xml_name_matcher(), + Self::Dynamic(ref inner) => inner.xml_name_matcher(), + } + } + /// Build the deserialisation statemachine for the enum. fn make_from_events_statemachine( &self, @@ -911,6 +966,7 @@ impl ItemDef for EnumDef { #builder_ty_ident::new(#name_ident, #attrs_ident, ctx) }, builder_ty_ident: builder_ty_ident.clone(), + name_matcher: self.inner.xml_name_matcher()?, }) } diff --git a/xso-proc/src/lib.rs b/xso-proc/src/lib.rs index 8da74a0cf1007ba72fdd51103463525999f6e3ec..8a06fd6fb8ef5007bb7236fd53613c1293d9a7cc 100644 --- a/xso-proc/src/lib.rs +++ b/xso-proc/src/lib.rs @@ -71,6 +71,7 @@ fn from_xml_impl(input: Item) -> Result { defs, from_events_body, builder_ty_ident, + name_matcher, } = def.make_from_events_builder(&vis, &name_ident, &attrs_ident)?; #[cfg_attr(not(feature = "minidom"), allow(unused_mut))] @@ -87,6 +88,10 @@ fn from_xml_impl(input: Item) -> Result { ) -> ::core::result::Result { #from_events_body } + + fn xml_name_matcher() -> ::xso::fromxml::XmlNameMatcher<'static> { + #name_matcher + } } }; diff --git a/xso-proc/src/structs.rs b/xso-proc/src/structs.rs index 61e8ac2da1cc6db53d267698ab828eedf71e5440..6b6642928969ad900186b3946df36853c0648b58 100644 --- a/xso-proc/src/structs.rs +++ b/xso-proc/src/structs.rs @@ -7,10 +7,10 @@ //! Handling of structs use proc_macro2::{Span, TokenStream}; -use quote::quote; +use quote::{quote, ToTokens}; use syn::{spanned::Spanned, *}; -use crate::common::{AsXmlParts, FromXmlParts, ItemDef}; +use crate::common::{AsXmlParts, FromXmlParts, ItemDef, XmlNameMatcher}; use crate::compound::Compound; use crate::error_message::ParentRef; use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta}; @@ -168,6 +168,22 @@ impl StructInner { } } + pub(crate) fn xml_name_matcher(&self) -> Result { + match self { + Self::Transparent { ty, .. } => Ok(XmlNameMatcher::Custom(quote! { + <#ty as ::xso::FromXml>::xml_name_matcher() + })), + Self::Compound { + xml_namespace, + xml_name, + .. + } => Ok(XmlNameMatcher::Specific( + xml_namespace.to_token_stream(), + xml_name.to_token_stream(), + )), + } + } + pub(crate) fn make_from_events_statemachine( &self, state_ty_ident: &Ident, @@ -399,6 +415,7 @@ impl ItemDef for StructDef { #builder_ty_ident::new(#name_ident, #attrs_ident, ctx) }, builder_ty_ident: builder_ty_ident.clone(), + name_matcher: self.inner.xml_name_matcher()?, }) }