xso-proc: provide FromXml::xml_name_matcher on derived impls
That way, derived implementations can also make use of the performance benefits.
This commit is contained in:
parent
126588b47d
commit
59af49c691
5 changed files with 166 additions and 3 deletions
|
|
@ -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()?,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue