From 7d8ffe45a7f1cd9fcc14a28c4765a396def3c5cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Thu, 3 Oct 2024 12:56:04 +0200 Subject: [PATCH] xso: add support for ignoring unknown stuff in extracts --- parsers/src/util/macro_tests.rs | 36 +++++++++++++++++++++++++++++++++ xso-proc/src/field/mod.rs | 5 ++++- xso-proc/src/meta.rs | 28 +++++++++++++++++++++++++ xso/src/from_xml_doc.md | 2 ++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/parsers/src/util/macro_tests.rs b/parsers/src/util/macro_tests.rs index 4a5fd523..36544c22 100644 --- a/parsers/src/util/macro_tests.rs +++ b/parsers/src/util/macro_tests.rs @@ -1863,3 +1863,39 @@ fn ignore_unknown_children_negative_unexpected_attribute() { other => panic!("unexpected result: {:?}", other), } } + +#[derive(FromXml, AsXml, PartialEq, Debug, Clone)] +#[xml(namespace = NS1, name = "parent")] +struct ExtractIgnoreUnknownStuff { + #[xml(extract(namespace = NS1, name = "child", on_unknown_attribute = Discard, on_unknown_child = Discard, fields( + extract(namespace = NS1, name = "grandchild", fields(text)) + )))] + contents: String, +} + +#[test] +fn extract_ignore_unknown_stuff_positive() { + #[allow(unused_imports)] + use std::{ + option::Option::{None, Some}, + result::Result::{Err, Ok}, + }; + match parse_str::( + "hello world", + ) { + Ok(ExtractIgnoreUnknownStuff { contents }) => { + assert_eq!(contents, "hello world"); + } + other => panic!("unexpected result: {:?}", other), + } +} + +#[test] +fn extract_ignore_unknown_stuff_roundtrip() { + #[allow(unused_imports)] + use std::{ + option::Option::{None, Some}, + result::Result::{Err, Ok}, + }; + roundtrip_full::("hello world") +} diff --git a/xso-proc/src/field/mod.rs b/xso-proc/src/field/mod.rs index 744366de..572f556a 100644 --- a/xso-proc/src/field/mod.rs +++ b/xso-proc/src/field/mod.rs @@ -319,6 +319,8 @@ fn new_field( qname: QNameRef { namespace, name }, amount, fields, + on_unknown_attribute, + on_unknown_child, } => { let xml_namespace = namespace.unwrap_or_else(|| container_namespace.clone()); let xml_name = default_name(span, name, field_ident)?; @@ -366,7 +368,8 @@ fn new_field( &xml_namespace, )); } - let parts = Compound::from_field_defs(field_defs, None, None)?; + let parts = + Compound::from_field_defs(field_defs, on_unknown_attribute, on_unknown_child)?; Ok(Box::new(ChildField { default_, diff --git a/xso-proc/src/meta.rs b/xso-proc/src/meta.rs index ecb3ef00..fc033b43 100644 --- a/xso-proc/src/meta.rs +++ b/xso-proc/src/meta.rs @@ -737,6 +737,12 @@ pub(crate) enum XmlFieldMeta { /// The `fields` nested meta. fields: Vec, + + /// The `on_unknown_attribute` value. + on_unknown_attribute: Option, + + /// The `on_unknown_child` value. + on_unknown_child: Option, }, /// `#[xml(element)]` @@ -925,6 +931,8 @@ impl XmlFieldMeta { let mut qname = QNameRef::default(); let mut fields = None; let mut amount = None; + let mut on_unknown_attribute = None; + let mut on_unknown_child = None; let mut default_ = Flag::Absent; meta.parse_nested_meta(|meta| { if meta.path.is_ident("default") { @@ -952,6 +960,24 @@ impl XmlFieldMeta { } amount = Some(meta.value()?.parse()?); Ok(()) + } else if meta.path.is_ident("on_unknown_attribute") { + if on_unknown_attribute.is_some() { + return Err(Error::new_spanned( + meta.path, + "duplicate `on_unknown_attribute` key", + )); + } + on_unknown_attribute = Some(meta.value()?.parse()?); + Ok(()) + } else if meta.path.is_ident("on_unknown_child") { + if on_unknown_child.is_some() { + return Err(Error::new_spanned( + meta.path, + "duplicate `on_unknown_child` key", + )); + } + on_unknown_child = Some(meta.value()?.parse()?); + Ok(()) } else { match qname.parse_incremental_from_meta(meta)? { None => Ok(()), @@ -966,6 +992,8 @@ impl XmlFieldMeta { qname, fields, amount, + on_unknown_attribute, + on_unknown_child, }) } diff --git a/xso/src/from_xml_doc.md b/xso/src/from_xml_doc.md index 9b56a408..ecf0a1f3 100644 --- a/xso/src/from_xml_doc.md +++ b/xso/src/from_xml_doc.md @@ -474,6 +474,8 @@ The following keys can be used inside the `#[xml(extract(..))]` meta: | `default` | flag | If present, an absent child will substitute the default value instead of raising an error. | | `n` | `1` or `..` | If `1`, a single element is parsed. If `..`, a collection is parsed. Defaults to `1`. | | `fields` | *nested* | A list of [field meta](#field-meta) which describe the contents of the child element. | +| `on_unknown_attribute` | *identifier* | Name of an [`UnknownAttributePolicy`] member, controlling how unknown attributes are handled. | +| `on_unknown_child` | *identifier* | Name of an [`UnknownChildPolicy`] member, controlling how unknown children are handled. | 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