xso: only fail on non-whitespace unknown text

Jonas Schäfer created

We hereby ignore whitespace-only unexpected text, because that's
generally harmless.

Change summary

parsers/src/util/macro_tests.rs | 50 +++++++++++++++++++++++++++++++++++
xso-proc/src/compound.rs        | 10 ++++++
2 files changed, 59 insertions(+), 1 deletion(-)

Detailed changes

parsers/src/util/macro_tests.rs 🔗

@@ -398,6 +398,17 @@ fn text_string_roundtrip() {
     roundtrip_full::<TextString>("<text xmlns='urn:example:ns1'>hello world!</text>");
 }
 
+#[test]
+fn text_string_positive_preserves_whitespace() {
+    #[allow(unused_imports)]
+    use std::{
+        option::Option::{None, Some},
+        result::Result::{Err, Ok},
+    };
+    let el = parse_str::<TextString>("<text xmlns='urn:example:ns1'> \t\n</text>").unwrap();
+    assert_eq!(el.text, " \t\n");
+}
+
 #[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "text")]
 struct TextNonString {
@@ -414,3 +425,42 @@ fn text_non_string_roundtrip() {
     };
     roundtrip_full::<TextNonString>("<text xmlns='urn:example:ns1'>123456</text>");
 }
+
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = NS1, name = "elem")]
+struct IgnoresWhitespaceWithoutTextConsumer;
+
+#[test]
+fn ignores_whitespace_without_text_consumer_positive() {
+    #[allow(unused_imports)]
+    use std::{
+        option::Option::{None, Some},
+        result::Result::{Err, Ok},
+    };
+    let _ = parse_str::<IgnoresWhitespaceWithoutTextConsumer>(
+        "<elem xmlns='urn:example:ns1'> \t\r\n</elem>",
+    )
+    .unwrap();
+}
+
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = NS1, name = "elem")]
+struct FailsTextWithoutTextConsumer;
+
+#[test]
+fn fails_text_without_text_consumer_positive() {
+    #[allow(unused_imports)]
+    use std::{
+        option::Option::{None, Some},
+        result::Result::{Err, Ok},
+    };
+    match parse_str::<FailsTextWithoutTextConsumer>("<elem xmlns='urn:example:ns1'>  quak  </elem>")
+    {
+        Err(::xso::error::FromElementError::Invalid(::xso::error::Error::Other(e)))
+            if e.contains("Unexpected text") =>
+        {
+            ()
+        }
+        other => panic!("unexpected result: {:?}", other),
+    }
+}

xso-proc/src/compound.rs 🔗

@@ -132,7 +132,15 @@ impl Compound {
         let text_handler = match text_handler {
             Some(v) => v,
             None => quote! {
-                ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
+                // note: u8::is_ascii_whitespace includes U+000C, which is not
+                // part of XML's white space definition.'
+                if #text.as_bytes().iter().any(|b| *b != b' ' && *b != b'\t' && *b != b'\r' && *b != b'\n') {
+                    ::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
+                } else {
+                    ::core::result::Result::Ok(::std::ops::ControlFlow::Break(
+                        Self::#default_state_ident { #builder_data_ident }
+                    ))
+                }
             },
         };