xso: add support for ignoring unknown stuff in extracts

Jonas SchΓ€fer created

Change summary

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(-)

Detailed changes

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::<ExtractIgnoreUnknownStuff>(
+        "<parent xmlns='urn:example:ns1'><child foo='bar'><quak/><grandchild>hello world</grandchild></child></parent>",
+    ) {
+        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::<ExtractIgnoreUnknownStuff>("<parent xmlns='urn:example:ns1'><child><grandchild>hello world</grandchild></child></parent>")
+}

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_,

xso-proc/src/meta.rs πŸ”—

@@ -737,6 +737,12 @@ pub(crate) enum XmlFieldMeta {
 
         /// The `fields` nested meta.
         fields: Vec<XmlFieldMeta>,
+
+        /// The `on_unknown_attribute` value.
+        on_unknown_attribute: Option<Ident>,
+
+        /// The `on_unknown_child` value.
+        on_unknown_child: Option<Ident>,
     },
 
     /// `#[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,
         })
     }
 

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