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'/>");
|
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
|
//! Compound (struct or enum variant) field types
|
||||||
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::{quote, ToTokens};
|
||||||
use syn::{spanned::Spanned, *};
|
use syn::{spanned::Spanned, *};
|
||||||
|
|
||||||
use rxml_validation::NcName;
|
use rxml_validation::NcName;
|
||||||
|
|
||||||
use crate::error_message::{self, ParentRef};
|
use crate::error_message::{self, ParentRef};
|
||||||
use crate::meta::{NameRef, XmlFieldMeta};
|
use crate::meta::{NameRef, NamespaceRef, XmlFieldMeta};
|
||||||
use crate::scope::{FromEventsScope, IntoEventsScope};
|
use crate::scope::{FromEventsScope, IntoEventsScope};
|
||||||
|
|
||||||
/// Code slices necessary for declaring and initializing a temporary variable
|
/// Code slices necessary for declaring and initializing a temporary variable
|
||||||
|
@ -61,6 +61,9 @@ pub(crate) enum FieldIteratorPart {
|
||||||
enum FieldKind {
|
enum FieldKind {
|
||||||
/// The field maps to an attribute.
|
/// The field maps to an attribute.
|
||||||
Attribute {
|
Attribute {
|
||||||
|
/// The optional XML namespace of the attribute.
|
||||||
|
xml_namespace: Option<NamespaceRef>,
|
||||||
|
|
||||||
/// The XML name of the attribute.
|
/// The XML name of the attribute.
|
||||||
xml_name: NameRef,
|
xml_name: NameRef,
|
||||||
},
|
},
|
||||||
|
@ -73,7 +76,11 @@ impl FieldKind {
|
||||||
/// it is not specified explicitly.
|
/// it is not specified explicitly.
|
||||||
fn from_meta(meta: XmlFieldMeta, field_ident: Option<&Ident>) -> Result<Self> {
|
fn from_meta(meta: XmlFieldMeta, field_ident: Option<&Ident>) -> Result<Self> {
|
||||||
match meta {
|
match meta {
|
||||||
XmlFieldMeta::Attribute { span, name } => {
|
XmlFieldMeta::Attribute {
|
||||||
|
span,
|
||||||
|
namespace,
|
||||||
|
name,
|
||||||
|
} => {
|
||||||
let xml_name = match name {
|
let xml_name = match name {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => match field_ident {
|
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,
|
container_name: &ParentRef,
|
||||||
) -> Result<FieldBuilderPart> {
|
) -> Result<FieldBuilderPart> {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
FieldKind::Attribute { ref xml_name } => {
|
FieldKind::Attribute {
|
||||||
|
ref xml_name,
|
||||||
|
ref xml_namespace,
|
||||||
|
} => {
|
||||||
let FromEventsScope { ref attrs, .. } = scope;
|
let FromEventsScope { ref attrs, .. } = scope;
|
||||||
|
|
||||||
let missing_msg = error_message::on_missing_attribute(container_name, &self.member);
|
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 {
|
return Ok(FieldBuilderPart::Init {
|
||||||
value: FieldTempInit {
|
value: FieldTempInit {
|
||||||
ty: self.ty.clone(),
|
ty: self.ty.clone(),
|
||||||
init: quote! {
|
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::Some(v) => v,
|
||||||
::core::option::Option::None => return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into()),
|
::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,
|
bound_name: &Ident,
|
||||||
) -> Result<FieldIteratorPart> {
|
) -> Result<FieldIteratorPart> {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
FieldKind::Attribute { ref xml_name } => {
|
FieldKind::Attribute {
|
||||||
|
ref xml_name,
|
||||||
|
ref xml_namespace,
|
||||||
|
} => {
|
||||||
let IntoEventsScope { ref attrs, .. } = scope;
|
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 {
|
return Ok(FieldIteratorPart::Header {
|
||||||
setter: quote! {
|
setter: quote! {
|
||||||
#attrs.insert(
|
#attrs.insert(
|
||||||
::xso::exports::rxml::Namespace::NONE,
|
#xml_namespace,
|
||||||
#xml_name.to_owned(),
|
#xml_name.to_owned(),
|
||||||
#bound_name,
|
#bound_name,
|
||||||
);
|
);
|
||||||
|
|
|
@ -204,6 +204,9 @@ pub(crate) enum XmlFieldMeta {
|
||||||
/// This is useful for error messages.
|
/// This is useful for error messages.
|
||||||
span: Span,
|
span: Span,
|
||||||
|
|
||||||
|
/// The XML namespace supplied.
|
||||||
|
namespace: Option<NamespaceRef>,
|
||||||
|
|
||||||
/// The XML name supplied.
|
/// The XML name supplied.
|
||||||
name: Option<NameRef>,
|
name: Option<NameRef>,
|
||||||
},
|
},
|
||||||
|
@ -222,10 +225,12 @@ impl XmlFieldMeta {
|
||||||
Ok(Self::Attribute {
|
Ok(Self::Attribute {
|
||||||
span: meta.path.span(),
|
span: meta.path.span(),
|
||||||
name: Some(meta.value()?.parse()?),
|
name: Some(meta.value()?.parse()?),
|
||||||
|
namespace: None,
|
||||||
})
|
})
|
||||||
} else if meta.input.peek(syn::token::Paren) {
|
} else if meta.input.peek(syn::token::Paren) {
|
||||||
// full syntax
|
// full syntax
|
||||||
let mut name: Option<NameRef> = None;
|
let mut name: Option<NameRef> = None;
|
||||||
|
let mut namespace: Option<NamespaceRef> = None;
|
||||||
meta.parse_nested_meta(|meta| {
|
meta.parse_nested_meta(|meta| {
|
||||||
if meta.path.is_ident("name") {
|
if meta.path.is_ident("name") {
|
||||||
if name.is_some() {
|
if name.is_some() {
|
||||||
|
@ -233,6 +238,12 @@ impl XmlFieldMeta {
|
||||||
}
|
}
|
||||||
name = Some(meta.value()?.parse()?);
|
name = Some(meta.value()?.parse()?);
|
||||||
Ok(())
|
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 {
|
} else {
|
||||||
Err(Error::new_spanned(meta.path, "unsupported key"))
|
Err(Error::new_spanned(meta.path, "unsupported key"))
|
||||||
}
|
}
|
||||||
|
@ -240,12 +251,14 @@ impl XmlFieldMeta {
|
||||||
Ok(Self::Attribute {
|
Ok(Self::Attribute {
|
||||||
span: meta.path.span(),
|
span: meta.path.span(),
|
||||||
name,
|
name,
|
||||||
|
namespace,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// argument-less syntax
|
// argument-less syntax
|
||||||
Ok(Self::Attribute {
|
Ok(Self::Attribute {
|
||||||
span: meta.path.span(),
|
span: meta.path.span(),
|
||||||
name: None,
|
name: None,
|
||||||
|
namespace: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,11 +69,12 @@ The following keys can be used inside the `#[xml(attribute(..))]` meta:
|
||||||
|
|
||||||
| Key | Value type | Description |
|
| 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`. |
|
| `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,
|
The `attribute` meta also supports a shorthand syntax,
|
||||||
`#[xml(attribute = ..)]`, where the value is treated as the value for the
|
`#[xml(attribute = ..)]`, where the value is treated as the value for the
|
||||||
`name` key.
|
`name` key and the `namespace` is unset.
|
||||||
|
|
||||||
##### Example
|
##### Example
|
||||||
|
|
||||||
|
@ -88,12 +89,19 @@ struct Foo {
|
||||||
b: String,
|
b: String,
|
||||||
#[xml(attribute(name = "baz"))]
|
#[xml(attribute(name = "baz"))]
|
||||||
c: String,
|
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 {
|
assert_eq!(foo, Foo {
|
||||||
a: "1".to_string(),
|
a: "1".to_string(),
|
||||||
b: "2".to_string(),
|
b: "2".to_string(),
|
||||||
c: "3".to_string(),
|
c: "3".to_string(),
|
||||||
|
d: "4".to_string(),
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
Loading…
Reference in a new issue