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>",
|
"<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,
|
span,
|
||||||
qname: QNameRef { namespace, name },
|
qname: QNameRef { namespace, name },
|
||||||
default_,
|
default_,
|
||||||
|
type_,
|
||||||
} => {
|
} => {
|
||||||
let xml_name = default_name(span, name, field_ident)?;
|
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 {
|
Ok(Self::Attribute {
|
||||||
xml_name,
|
xml_name,
|
||||||
xml_namespace: namespace,
|
xml_namespace: namespace,
|
||||||
|
|
|
||||||
|
|
@ -634,6 +634,9 @@ pub(crate) enum XmlFieldMeta {
|
||||||
|
|
||||||
/// The `default` flag.
|
/// The `default` flag.
|
||||||
default_: Flag,
|
default_: Flag,
|
||||||
|
|
||||||
|
/// An explicit type override, only usable within extracts.
|
||||||
|
type_: Option<Type>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// `#[xml(text)]`
|
/// `#[xml(text)]`
|
||||||
|
|
@ -700,11 +703,13 @@ impl XmlFieldMeta {
|
||||||
namespace,
|
namespace,
|
||||||
},
|
},
|
||||||
default_: Flag::Absent,
|
default_: Flag::Absent,
|
||||||
|
type_: None,
|
||||||
})
|
})
|
||||||
} else if meta.input.peek(syn::token::Paren) {
|
} else if meta.input.peek(syn::token::Paren) {
|
||||||
// full syntax
|
// full syntax
|
||||||
let mut qname = QNameRef::default();
|
let mut qname = QNameRef::default();
|
||||||
let mut default_ = Flag::Absent;
|
let mut default_ = Flag::Absent;
|
||||||
|
let mut type_ = None;
|
||||||
meta.parse_nested_meta(|meta| {
|
meta.parse_nested_meta(|meta| {
|
||||||
if meta.path.is_ident("default") {
|
if meta.path.is_ident("default") {
|
||||||
if default_.is_set() {
|
if default_.is_set() {
|
||||||
|
|
@ -712,6 +717,12 @@ impl XmlFieldMeta {
|
||||||
}
|
}
|
||||||
default_ = (&meta.path).into();
|
default_ = (&meta.path).into();
|
||||||
Ok(())
|
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 {
|
} else {
|
||||||
match qname.parse_incremental_from_meta(meta)? {
|
match qname.parse_incremental_from_meta(meta)? {
|
||||||
None => Ok(()),
|
None => Ok(()),
|
||||||
|
|
@ -723,6 +734,7 @@ impl XmlFieldMeta {
|
||||||
span: meta.path.span(),
|
span: meta.path.span(),
|
||||||
qname,
|
qname,
|
||||||
default_,
|
default_,
|
||||||
|
type_,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// argument-less syntax
|
// argument-less syntax
|
||||||
|
|
@ -730,6 +742,7 @@ impl XmlFieldMeta {
|
||||||
span: meta.path.span(),
|
span: meta.path.span(),
|
||||||
qname: QNameRef::default(),
|
qname: QNameRef::default(),
|
||||||
default_: Flag::Absent,
|
default_: Flag::Absent,
|
||||||
|
type_: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -980,6 +993,7 @@ impl XmlFieldMeta {
|
||||||
/// Extract an explicit type specification if it exists.
|
/// Extract an explicit type specification if it exists.
|
||||||
pub(crate) fn take_type(&mut self) -> Option<Type> {
|
pub(crate) fn take_type(&mut self) -> Option<Type> {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Attribute { ref mut type_, .. } => type_.take(),
|
||||||
Self::Text { ref mut type_, .. } => type_.take(),
|
Self::Text { ref mut type_, .. } => type_.take(),
|
||||||
_ => None,
|
_ => 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. |
|
| `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`. |
|
||||||
| `default` | flag | If present, an absent attribute will substitute the default value instead of raising an error. |
|
| `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
|
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
|
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
|
implement the `Default` trait for a `FromXml` derivation. `default` has no
|
||||||
influence on `AsXml`.
|
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
|
##### Example
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue