xso-proc: add support for namespaced attributes

This commit is contained in:
Jonas Schäfer 2024-06-24 07:28:04 +02:00
commit 84de7fc248
4 changed files with 98 additions and 10 deletions

View file

@ -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<NamespaceRef>,
/// 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<Self> {
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<FieldBuilderPart> {
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<FieldIteratorPart> {
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,
);

View file

@ -204,6 +204,9 @@ pub(crate) enum XmlFieldMeta {
/// This is useful for error messages.
span: Span,
/// The XML namespace supplied.
namespace: Option<NamespaceRef>,
/// The XML name supplied.
name: Option<NameRef>,
},
@ -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<NameRef> = None;
let mut namespace: Option<NamespaceRef> = 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,
})
}
}