mirror of
https://gitlab.com/xmpp-rs/xmpp-rs.git
synced 2024-07-12 22:21:53 +00:00
xso-proc: add support for namespaced attributes
This commit is contained in:
parent
219d682295
commit
84de7fc248
4 changed files with 98 additions and 10 deletions
|
@ -245,3 +245,40 @@ fn renamed_attribute_roundtrip() {
|
|||
};
|
||||
roundtrip_full::<RenamedAttribute>("<attr xmlns='urn:example:ns1' a1='bar'/>");
|
||||
}
|
||||
|
||||
#[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::<NamespacedAttribute>(
|
||||
"<attr xmlns='urn:example:ns1'
|
||||
xmlns:tns0='urn:example:ns1' tns0:foo='a1'
|
||||
xmlns:tns1='urn:example:ns2' tns1:foo='a2'/>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespaced_attribute_roundtrip_b() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
roundtrip_full::<NamespacedAttribute>(
|
||||
"<tns0:attr
|
||||
xmlns:tns0='urn:example:ns1' tns0:foo='bar'
|
||||
xmlns:tns1='urn:example:ns2' tns1:foo='a2'/>",
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"<foo xmlns='urn:example' a='1' bar='2' baz='3'/>").unwrap();
|
||||
let foo: Foo = xso::from_bytes(b"<foo
|
||||
xmlns='urn:example'
|
||||
a='1' bar='2' baz='3'
|
||||
xmlns:tns0='urn:example' tns0:fnord='4'
|
||||
/>").unwrap();
|
||||
assert_eq!(foo, Foo {
|
||||
a: "1".to_string(),
|
||||
b: "2".to_string(),
|
||||
c: "3".to_string(),
|
||||
d: "4".to_string(),
|
||||
});
|
||||
```
|
||||
|
|
Loading…
Reference in a new issue