xso-proc: allow paths as XML names

Jonas SchΓ€fer created

Not sure if this is something useful to have, but it feels consistent
with `namespace`.

Change summary

parsers/src/util/macro_tests.rs | 21 +++++++++
xso-proc/src/lib.rs             |  9 ++-
xso-proc/src/meta.rs            | 80 +++++++++++++++++++++++-----------
xso/src/from_xml_doc.md         |  2 
4 files changed, 82 insertions(+), 30 deletions(-)

Detailed changes

parsers/src/util/macro_tests.rs πŸ”—

@@ -148,3 +148,24 @@ fn empty_qname_check_has_precedence_over_attr_check() {
         other => panic!("unexpected result: {:?}", other),
     }
 }
+
+static SOME_NAME: &::xso::exports::rxml::strings::NcNameStr = {
+    #[allow(unsafe_code)]
+    unsafe {
+        ::xso::exports::rxml::strings::NcNameStr::from_str_unchecked("bar")
+    }
+};
+
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = NS1, name = SOME_NAME)]
+struct NamePath;
+
+#[test]
+fn name_path_roundtrip() {
+    #[allow(unused_imports)]
+    use std::{
+        option::Option::{None, Some},
+        result::Result::{Err, Ok},
+    };
+    roundtrip_full::<NamePath>("<bar xmlns='urn:example:ns1'/>");
+}

xso-proc/src/lib.rs πŸ”—

@@ -77,8 +77,11 @@ fn from_xml_impl(input: Item) -> Result<TokenStream> {
     let from_events_builder_ty_name = quote::format_ident!("{}FromEvents", ident);
     let state_ty_name = quote::format_ident!("{}FromEventsState", ident);
 
-    let unknown_attr_err = format!("Unknown attribute in {} element.", xml_name.as_str());
-    let unknown_child_err = format!("Unknown child in {} element.", xml_name.as_str());
+    let unknown_attr_err = format!(
+        "Unknown attribute in {} element.",
+        xml_name.repr_to_string()
+    );
+    let unknown_child_err = format!("Unknown child in {} element.", xml_name.repr_to_string());
     let docstr = format!("Build a [`{}`] from XML events", ident);
 
     #[cfg_attr(not(feature = "minidom"), allow(unused_mut))]
@@ -215,7 +218,7 @@ fn into_xml_impl(input: Item) -> Result<TokenStream> {
                             ::xso::exports::rxml::parser::EventMetrics::zero(),
                             (
                                 ::xso::exports::rxml::Namespace::from_str(#xml_namespace),
-                                ::xso::exports::rxml::NcName::from(#xml_name),
+                                #xml_name.to_owned(),
                             ),
                             ::xso::exports::rxml::AttrMap::new(),
                         )))

xso-proc/src/meta.rs πŸ”—

@@ -9,6 +9,8 @@
 //! This module is concerned with parsing attributes from the Rust "meta"
 //! annotations on structs, enums, enum variants and fields.
 
+use std::borrow::Cow;
+
 use proc_macro2::{Span, TokenStream};
 use quote::{quote, quote_spanned};
 use syn::{spanned::Spanned, *};
@@ -23,47 +25,73 @@ pub(crate) type NamespaceRef = Path;
 
 /// Value for the `#[xml(name = .. )]` attribute.
 #[derive(Debug)]
-pub(crate) struct NameRef {
-    value: NcName,
-    span: Span,
+pub(crate) enum NameRef {
+    /// The XML name is specified as a string literal.
+    Literal {
+        /// The validated XML name.
+        value: NcName,
+
+        /// The span of the original [`syn::LitStr`].
+        span: Span,
+    },
+
+    /// The XML name is specified as a path.
+    Path(Path),
 }
 
 impl NameRef {
-    /// Access the XML name as str.
+    /// Access a representation of the XML name as str.
+    ///
+    /// If this name reference is a [`Self::Path`], this will return the name
+    /// of the rightmost identifier in the path.
     ///
-    /// *Note*: This function may vanish in the future if we ever support
-    /// non-literal XML names.
-    pub(crate) fn as_str(&self) -> &str {
-        self.value.as_str()
+    /// If this name reference is a [`Self::Literal`], this will return the
+    /// contents of the literal.
+    pub(crate) fn repr_to_string(&self) -> Cow<'_, str> {
+        match self {
+            Self::Literal { ref value, .. } => Cow::Borrowed(value.as_str()),
+            Self::Path(ref path) => path.segments.last().unwrap().ident.to_string().into(),
+        }
     }
 }
 
 impl syn::parse::Parse for NameRef {
     fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
-        let s: LitStr = input.parse()?;
-        let span = s.span();
-        match NcName::try_from(s.value()) {
-            Ok(value) => Ok(Self { value, span }),
-            Err(e) => Err(Error::new(
-                span,
-                format!("not a valid XML element name: {}", e),
-            )),
+        if input.peek(syn::LitStr) {
+            let s: LitStr = input.parse()?;
+            let span = s.span();
+            match NcName::try_from(s.value()) {
+                Ok(value) => Ok(Self::Literal { value, span }),
+                Err(e) => Err(Error::new(
+                    span,
+                    format!("not a valid XML element name: {}", e),
+                )),
+            }
+        } else {
+            let p: Path = input.parse()?;
+            Ok(Self::Path(p))
         }
     }
 }
 
 impl quote::ToTokens for NameRef {
     fn to_tokens(&self, tokens: &mut TokenStream) {
-        let value = self.value.as_str();
-        let value = quote_spanned! { self.span=> #value };
-        // SAFETY: self.0 is a known-good NcName, so converting it to an
-        // NcNameStr is known to be safe.
-        // NOTE: we cannot use `quote_spanned! { self.span=> }` for the unsafe
-        // block as that would then in fact trip a `#[deny(unsafe_code)]` lint
-        // at the use site of the macro.
-        tokens.extend(quote! {
-            unsafe { ::xso::exports::rxml::NcNameStr::from_str_unchecked(#value) }
-        })
+        match self {
+            Self::Literal { ref value, span } => {
+                let span = *span;
+                let value = value.as_str();
+                let value = quote_spanned! { span=> #value };
+                // SAFETY: self.0 is a known-good NcName, so converting it to an
+                // NcNameStr is known to be safe.
+                // NOTE: we cannot use `quote_spanned! { self.span=> }` for the unsafe
+                // block as that would then in fact trip a `#[deny(unsafe_code)]` lint
+                // at the use site of the macro.
+                tokens.extend(quote! {
+                    unsafe { ::xso::exports::rxml::NcNameStr::from_str_unchecked(#value) }
+                })
+            }
+            Self::Path(ref path) => path.to_tokens(tokens),
+        }
     }
 }
 

xso/src/from_xml_doc.md πŸ”—

@@ -32,7 +32,7 @@ All key-value pairs interpreted by these derive macros must be wrapped in a
 | Key | Value type | Description |
 | --- | --- | --- |
 | `namespace` | *path* | The path to a `&'static str` which holds the XML namespace to match. |
-| `name` | *string literal* | The XML element name to match. |
+| `name` | *string literal* or *path* | The XML element name to match. If it is a *path*, it must point at a `&'static NcNameStr`. |
 
 Note that the `name` value must be a valid XML element name, without colons.
 The namespace prefix, if any, is assigned automatically at serialisation time