Detailed changes
@@ -228,3 +228,20 @@ fn required_attribute_missing() {
other => panic!("unexpected result: {:?}", other),
}
}
+
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = NS1, name = "attr")]
+struct RenamedAttribute {
+ #[xml(attribute = "a1")]
+ foo: String,
+}
+
+#[test]
+fn renamed_attribute_roundtrip() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ roundtrip_full::<RenamedAttribute>("<attr xmlns='urn:example:ns1' a1='bar'/>");
+}
@@ -73,30 +73,30 @@ impl FieldKind {
/// it is not specified explicitly.
fn from_meta(meta: XmlFieldMeta, field_ident: Option<&Ident>) -> Result<Self> {
match meta {
- XmlFieldMeta::Attribute { span } => {
- let Some(field_ident) = field_ident else {
- return Err(Error::new(
- span,
- "attribute extraction not supported on unnamed fields",
- ));
- };
-
- let xml_name = match NcName::try_from(field_ident.to_string()) {
- Ok(v) => v,
- Err(e) => {
- return Err(Error::new(
- field_ident.span(),
- format!("invalid XML attribute name: {}", e),
- ))
+ XmlFieldMeta::Attribute { span, name } => {
+ let xml_name = match name {
+ Some(v) => v,
+ None => match field_ident {
+ None => return Err(Error::new(
+ span,
+ "attribute name must be explicitly specified using `#[xml(attribute = ..)] on unnamed fields",
+ )),
+ Some(field_ident) => match NcName::try_from(field_ident.to_string()) {
+ Ok(value) => NameRef::Literal {
+ span: field_ident.span(),
+ value,
+ },
+ Err(e) => {
+ return Err(Error::new(
+ field_ident.span(),
+ format!("invalid XML attribute name: {}", e),
+ ))
+ }
+ },
}
};
- Ok(Self::Attribute {
- xml_name: NameRef::Literal {
- span: field_ident.span(),
- value: xml_name,
- },
- })
+ Ok(Self::Attribute { xml_name })
}
}
}
@@ -203,15 +203,51 @@ pub(crate) enum XmlFieldMeta {
///
/// This is useful for error messages.
span: Span,
+
+ /// The XML name supplied.
+ name: Option<NameRef>,
},
}
impl XmlFieldMeta {
/// Parse a `#[xml(attribute(..))]` meta.
+ ///
+ /// That meta can have three distinct syntax styles:
+ /// - argument-less: `#[xml(attribute)]`
+ /// - shorthand: `#[xml(attribute = ..)]`
+ /// - full: `#[xml(attribute(..))]`
fn attribute_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
- Ok(Self::Attribute {
- span: meta.path.span(),
- })
+ if meta.input.peek(Token![=]) {
+ // shorthand syntax
+ Ok(Self::Attribute {
+ span: meta.path.span(),
+ name: Some(meta.value()?.parse()?),
+ })
+ } else if meta.input.peek(syn::token::Paren) {
+ // full syntax
+ let mut name: Option<NameRef> = None;
+ meta.parse_nested_meta(|meta| {
+ if meta.path.is_ident("name") {
+ if name.is_some() {
+ return Err(Error::new_spanned(meta.path, "duplicate `name` key"));
+ }
+ name = Some(meta.value()?.parse()?);
+ Ok(())
+ } else {
+ Err(Error::new_spanned(meta.path, "unsupported key"))
+ }
+ })?;
+ Ok(Self::Attribute {
+ span: meta.path.span(),
+ name,
+ })
+ } else {
+ // argument-less syntax
+ Ok(Self::Attribute {
+ span: meta.path.span(),
+ name: None,
+ })
+ }
}
/// Parse [`Self`] from a nestd meta, switching on the identifier
@@ -62,6 +62,38 @@ The following mapping types are defined:
#### `attribute` meta
-The `attribute` meta does not support additional parameters. The field it is
-used on is mapped to an XML attribute of the same name and must be of type
-[`String`].
+The `attribute` meta causes the field to be mapped to an XML attribute of the
+same name. The field must be of type [`String`].
+
+The following keys can be used inside the `#[xml(attribute(..))]` meta:
+
+| Key | Value type | Description |
+| --- | --- | --- |
+| `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.
+
+##### Example
+
+```rust
+# use xso::FromXml;
+#[derive(FromXml, Debug, PartialEq)]
+#[xml(namespace = "urn:example", name = "foo")]
+struct Foo {
+ #[xml(attribute)]
+ a: String,
+ #[xml(attribute = "bar")]
+ b: String,
+ #[xml(attribute(name = "baz"))]
+ c: String,
+};
+
+let foo: Foo = xso::from_bytes(b"<foo xmlns='urn:example' a='1' bar='2' baz='3'/>").unwrap();
+assert_eq!(foo, Foo {
+ a: "1".to_string(),
+ b: "2".to_string(),
+ c: "3".to_string(),
+});
+```