xso-proc: add support for extracting attributes into collections
This commit is contained in:
parent
8732ca9a28
commit
aa518fd6c1
4 changed files with 83 additions and 0 deletions
|
|
@ -1234,3 +1234,56 @@ fn text_extract_vec_roundtrip() {
|
|||
"<parent xmlns='urn:example:ns1'><child>hello</child><child>world</child></parent>",
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||
#[xml(namespace = NS1, name = "parent")]
|
||||
struct AttributeExtractVec {
|
||||
#[xml(extract(n = .., namespace = NS1, name = "child", fields(attribute(type_ = String, name = "attr"))))]
|
||||
contents: Vec<String>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_extract_attribute_vec_positive_nonempty() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
match parse_str::<AttributeExtractVec>(
|
||||
"<parent xmlns='urn:example:ns1'><child attr='hello'/><child attr='world'/></parent>",
|
||||
) {
|
||||
Ok(AttributeExtractVec { contents }) => {
|
||||
assert_eq!(contents[0], "hello");
|
||||
assert_eq!(contents[1], "world");
|
||||
assert_eq!(contents.len(), 2);
|
||||
}
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_extract_attribute_vec_positive_empty() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
match parse_str::<AttributeExtractVec>("<parent xmlns='urn:example:ns1'/>") {
|
||||
Ok(AttributeExtractVec { contents }) => {
|
||||
assert_eq!(contents.len(), 0);
|
||||
}
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_extract_attribute_vec_roundtrip() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
roundtrip_full::<AttributeExtractVec>(
|
||||
"<parent xmlns='urn:example:ns1'><child attr='hello'/><child attr='world'/></parent>",
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -239,9 +239,20 @@ impl FieldKind {
|
|||
span,
|
||||
qname: QNameRef { namespace, name },
|
||||
default_,
|
||||
type_,
|
||||
} => {
|
||||
let xml_name = default_name(span, name, field_ident)?;
|
||||
|
||||
// This would've been taken via `XmlFieldMeta::take_type` if
|
||||
// this field was within an extract where a `type_` is legal
|
||||
// to have.
|
||||
if let Some(type_) = type_ {
|
||||
return Err(Error::new_spanned(
|
||||
type_,
|
||||
"specifying `type_` on fields inside structs and enum variants is redundant and not allowed."
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Self::Attribute {
|
||||
xml_name,
|
||||
xml_namespace: namespace,
|
||||
|
|
|
|||
|
|
@ -634,6 +634,9 @@ pub(crate) enum XmlFieldMeta {
|
|||
|
||||
/// The `default` flag.
|
||||
default_: Flag,
|
||||
|
||||
/// An explicit type override, only usable within extracts.
|
||||
type_: Option<Type>,
|
||||
},
|
||||
|
||||
/// `#[xml(text)]`
|
||||
|
|
@ -700,11 +703,13 @@ impl XmlFieldMeta {
|
|||
namespace,
|
||||
},
|
||||
default_: Flag::Absent,
|
||||
type_: None,
|
||||
})
|
||||
} else if meta.input.peek(syn::token::Paren) {
|
||||
// full syntax
|
||||
let mut qname = QNameRef::default();
|
||||
let mut default_ = Flag::Absent;
|
||||
let mut type_ = None;
|
||||
meta.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("default") {
|
||||
if default_.is_set() {
|
||||
|
|
@ -712,6 +717,12 @@ impl XmlFieldMeta {
|
|||
}
|
||||
default_ = (&meta.path).into();
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("type_") {
|
||||
if type_.is_some() {
|
||||
return Err(Error::new_spanned(meta.path, "duplicate `type_` key"));
|
||||
}
|
||||
type_ = Some(meta.value()?.parse()?);
|
||||
Ok(())
|
||||
} else {
|
||||
match qname.parse_incremental_from_meta(meta)? {
|
||||
None => Ok(()),
|
||||
|
|
@ -723,6 +734,7 @@ impl XmlFieldMeta {
|
|||
span: meta.path.span(),
|
||||
qname,
|
||||
default_,
|
||||
type_,
|
||||
})
|
||||
} else {
|
||||
// argument-less syntax
|
||||
|
|
@ -730,6 +742,7 @@ impl XmlFieldMeta {
|
|||
span: meta.path.span(),
|
||||
qname: QNameRef::default(),
|
||||
default_: Flag::Absent,
|
||||
type_: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -980,6 +993,7 @@ impl XmlFieldMeta {
|
|||
/// Extract an explicit type specification if it exists.
|
||||
pub(crate) fn take_type(&mut self) -> Option<Type> {
|
||||
match self {
|
||||
Self::Attribute { ref mut type_, .. } => type_.take(),
|
||||
Self::Text { ref mut type_, .. } => type_.take(),
|
||||
_ => None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ The following keys can be used inside the `#[xml(attribute(..))]` meta:
|
|||
| `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`. |
|
||||
| `default` | flag | If present, an absent attribute will substitute the default value instead of raising an error. |
|
||||
| `type_` | *type* | Optional explicit type specification. Only allowed within `#[xml(extract(fields(..)))]`. |
|
||||
|
||||
If the `name` key contains a namespace prefix, it must be one of the prefixes
|
||||
defined as built-in in the XML specifications. That prefix will then be
|
||||
|
|
@ -183,6 +184,10 @@ is generated using [`std::default::Default`], requiring the field type to
|
|||
implement the `Default` trait for a `FromXml` derivation. `default` has no
|
||||
influence on `AsXml`.
|
||||
|
||||
If `type_` is specified and the `text` meta is used within an
|
||||
`#[xml(extract(fields(..)))]` meta, the specified type is used instead of the
|
||||
field type on which the `extract` is declared.
|
||||
|
||||
##### Example
|
||||
|
||||
```rust
|
||||
|
|
|
|||
Loading…
Reference in a new issue