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