Detailed changes
@@ -94,6 +94,14 @@ fn empty_roundtrip() {
roundtrip_full::<Empty>("<foo xmlns='urn:example:ns1'/>");
}
+#[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 {
@@ -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.
@@ -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<XmlNameMatcher> {
+ 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<XmlNameMatcher> {
+ 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<XmlNameMatcher> {
+ 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<XmlNameMatcher> {
+ 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()?,
})
}
@@ -71,6 +71,7 @@ fn from_xml_impl(input: Item) -> Result<TokenStream> {
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<TokenStream> {
) -> ::core::result::Result<Self::Builder, ::xso::error::FromEventsError> {
#from_events_body
}
+
+ fn xml_name_matcher() -> ::xso::fromxml::XmlNameMatcher<'static> {
+ #name_matcher
+ }
}
};
@@ -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<XmlNameMatcher> {
+ 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()?,
})
}