diff --git a/parsers/src/util/macro_tests.rs b/parsers/src/util/macro_tests.rs index 242dc409b2522f9e2124f6bdddb95de283ab730b..2a247fc7e2007a3c3eb3a3ceaf89c786ec74706d 100644 --- a/parsers/src/util/macro_tests.rs +++ b/parsers/src/util/macro_tests.rs @@ -245,3 +245,40 @@ fn renamed_attribute_roundtrip() { }; roundtrip_full::(""); } + +#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)] +#[xml(namespace = NS1, name = "attr")] +struct NamespacedAttribute { + #[xml(attribute(namespace = "urn:example:ns1", name = "foo"))] + foo: String, + #[xml(attribute(namespace = "urn:example:ns2", name = "foo"))] + bar: String, +} + +#[test] +fn namespaced_attribute_roundtrip_a() { + #[allow(unused_imports)] + use std::{ + option::Option::{None, Some}, + result::Result::{Err, Ok}, + }; + roundtrip_full::( + "", + ); +} + +#[test] +fn namespaced_attribute_roundtrip_b() { + #[allow(unused_imports)] + use std::{ + option::Option::{None, Some}, + result::Result::{Err, Ok}, + }; + roundtrip_full::( + "", + ); +} diff --git a/xso-proc/src/field.rs b/xso-proc/src/field.rs index 04ccb48de4e92da576cee684b4ce3cec1f3c405a..f9b6dcd1501b6b4c34d3e9ea5068af011641a479 100644 --- a/xso-proc/src/field.rs +++ b/xso-proc/src/field.rs @@ -7,13 +7,13 @@ //! Compound (struct or enum variant) field types use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, ToTokens}; use syn::{spanned::Spanned, *}; use rxml_validation::NcName; use crate::error_message::{self, ParentRef}; -use crate::meta::{NameRef, XmlFieldMeta}; +use crate::meta::{NameRef, NamespaceRef, XmlFieldMeta}; use crate::scope::{FromEventsScope, IntoEventsScope}; /// Code slices necessary for declaring and initializing a temporary variable @@ -61,6 +61,9 @@ pub(crate) enum FieldIteratorPart { enum FieldKind { /// The field maps to an attribute. Attribute { + /// The optional XML namespace of the attribute. + xml_namespace: Option, + /// The XML name of the attribute. xml_name: NameRef, }, @@ -73,7 +76,11 @@ impl FieldKind { /// it is not specified explicitly. fn from_meta(meta: XmlFieldMeta, field_ident: Option<&Ident>) -> Result { match meta { - XmlFieldMeta::Attribute { span, name } => { + XmlFieldMeta::Attribute { + span, + namespace, + name, + } => { let xml_name = match name { Some(v) => v, None => match field_ident { @@ -96,7 +103,10 @@ impl FieldKind { } }; - Ok(Self::Attribute { xml_name }) + Ok(Self::Attribute { + xml_name, + xml_namespace: namespace, + }) } } } @@ -167,16 +177,26 @@ impl FieldDef { container_name: &ParentRef, ) -> Result { match self.kind { - FieldKind::Attribute { ref xml_name } => { + FieldKind::Attribute { + ref xml_name, + ref xml_namespace, + } => { let FromEventsScope { ref attrs, .. } = scope; let missing_msg = error_message::on_missing_attribute(container_name, &self.member); + let xml_namespace = match xml_namespace { + Some(v) => v.to_token_stream(), + None => quote! { + ::xso::exports::rxml::Namespace::none() + }, + }; + return Ok(FieldBuilderPart::Init { value: FieldTempInit { ty: self.ty.clone(), init: quote! { - match #attrs.remove(::xso::exports::rxml::Namespace::none(), #xml_name) { + match #attrs.remove(#xml_namespace, #xml_name) { ::core::option::Option::Some(v) => v, ::core::option::Option::None => return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into()), } @@ -197,13 +217,23 @@ impl FieldDef { bound_name: &Ident, ) -> Result { match self.kind { - FieldKind::Attribute { ref xml_name } => { + FieldKind::Attribute { + ref xml_name, + ref xml_namespace, + } => { let IntoEventsScope { ref attrs, .. } = scope; + let xml_namespace = match xml_namespace { + Some(v) => quote! { ::xso::exports::rxml::Namespace::from(#v) }, + None => quote! { + ::xso::exports::rxml::Namespace::NONE + }, + }; + return Ok(FieldIteratorPart::Header { setter: quote! { #attrs.insert( - ::xso::exports::rxml::Namespace::NONE, + #xml_namespace, #xml_name.to_owned(), #bound_name, ); diff --git a/xso-proc/src/meta.rs b/xso-proc/src/meta.rs index b9410474a2bd2d38b2ed1c5055d5317fa4bebeab..5e35eecf34bef132248a3a642cee00dedb41f8bf 100644 --- a/xso-proc/src/meta.rs +++ b/xso-proc/src/meta.rs @@ -204,6 +204,9 @@ pub(crate) enum XmlFieldMeta { /// This is useful for error messages. span: Span, + /// The XML namespace supplied. + namespace: Option, + /// The XML name supplied. name: Option, }, @@ -222,10 +225,12 @@ impl XmlFieldMeta { Ok(Self::Attribute { span: meta.path.span(), name: Some(meta.value()?.parse()?), + namespace: None, }) } else if meta.input.peek(syn::token::Paren) { // full syntax let mut name: Option = None; + let mut namespace: Option = None; meta.parse_nested_meta(|meta| { if meta.path.is_ident("name") { if name.is_some() { @@ -233,6 +238,12 @@ impl XmlFieldMeta { } name = Some(meta.value()?.parse()?); Ok(()) + } else if meta.path.is_ident("namespace") { + if namespace.is_some() { + return Err(Error::new_spanned(meta.path, "duplicate `namespace` key")); + } + namespace = Some(meta.value()?.parse()?); + Ok(()) } else { Err(Error::new_spanned(meta.path, "unsupported key")) } @@ -240,12 +251,14 @@ impl XmlFieldMeta { Ok(Self::Attribute { span: meta.path.span(), name, + namespace, }) } else { // argument-less syntax Ok(Self::Attribute { span: meta.path.span(), name: None, + namespace: None, }) } } diff --git a/xso/src/from_xml_doc.md b/xso/src/from_xml_doc.md index f98b04348434f4db49185070853d650d5e6993af..01041965ad4b123047854985fc0655670c9c61ce 100644 --- a/xso/src/from_xml_doc.md +++ b/xso/src/from_xml_doc.md @@ -69,11 +69,12 @@ The following keys can be used inside the `#[xml(attribute(..))]` meta: | Key | Value type | Description | | --- | --- | --- | +| `namespace` | *string literal* or *path* | The optional namespace of the XML attribute to match. If it is a *path*, it must point at a `&'static str`. Note that attributes, unlike elements, are unnamespaced by default. | | `name` | *string literal* or *path* | The name of the XML attribute to match. If it is a *path*, it must point at a `&'static NcNameStr`. | The `attribute` meta also supports a shorthand syntax, `#[xml(attribute = ..)]`, where the value is treated as the value for the -`name` key. +`name` key and the `namespace` is unset. ##### Example @@ -88,12 +89,19 @@ struct Foo { b: String, #[xml(attribute(name = "baz"))] c: String, + #[xml(attribute(namespace = "urn:example", name = "fnord"))] + d: String, }; -let foo: Foo = xso::from_bytes(b"").unwrap(); +let foo: Foo = xso::from_bytes(b"").unwrap(); assert_eq!(foo, Foo { a: "1".to_string(), b: "2".to_string(), c: "3".to_string(), + d: "4".to_string(), }); ```