diff --git a/parsers/src/util/macro_tests.rs b/parsers/src/util/macro_tests.rs
index 46310815a3bc03f373ef66139ecf9301ede6689d..173e77b9960c19a75b0d41a71cd69af5ee16381d 100644
--- a/parsers/src/util/macro_tests.rs
+++ b/parsers/src/util/macro_tests.rs
@@ -1234,3 +1234,56 @@ fn text_extract_vec_roundtrip() {
"helloworld",
)
}
+
+#[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,
+}
+
+#[test]
+fn text_extract_attribute_vec_positive_nonempty() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ match parse_str::(
+ "",
+ ) {
+ 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::("") {
+ 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::(
+ "",
+ )
+}
diff --git a/xso-proc/src/field.rs b/xso-proc/src/field.rs
index 5a33253626c03fa86e67eff44c6c64609f758df7..06fa5346a14b4e650f13610773a0acf9cc1defda 100644
--- a/xso-proc/src/field.rs
+++ b/xso-proc/src/field.rs
@@ -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,
diff --git a/xso-proc/src/meta.rs b/xso-proc/src/meta.rs
index 05867197fa89cca79959514b18295ad8f390c425..7b07c2c5ae66aa8bca56f28cab422d05d12f9ee6 100644
--- a/xso-proc/src/meta.rs
+++ b/xso-proc/src/meta.rs
@@ -634,6 +634,9 @@ pub(crate) enum XmlFieldMeta {
/// The `default` flag.
default_: Flag,
+
+ /// An explicit type override, only usable within extracts.
+ type_: Option,
},
/// `#[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 {
match self {
+ Self::Attribute { ref mut type_, .. } => type_.take(),
Self::Text { ref mut type_, .. } => type_.take(),
_ => None,
}
diff --git a/xso/src/from_xml_doc.md b/xso/src/from_xml_doc.md
index 3a3e90e317eb1716c70099bae1e091b2301e8511..f9085ced0e1883a9aa7ce2daad6600e751019aa7 100644
--- a/xso/src/from_xml_doc.md
+++ b/xso/src/from_xml_doc.md
@@ -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