diff --git a/Changelog.md b/Changelog.md index 013aade8..b029b633 100644 --- a/Changelog.md +++ b/Changelog.md @@ -22,6 +22,10 @@ ### Bug Fixes +- [#912]: Fix deserialization of numbers, booleans and characters that is space-wrapped, for example + ` 42 `. That space characters are usually indent added during serialization and + other XML serialization libraries trims them + ### Misc Changes - [#901]: Fix running tests on 32-bit architecture @@ -30,6 +34,7 @@ [#353]: https://github.com/tafia/quick-xml/issues/353 [#901]: https://github.com/tafia/quick-xml/pull/901 [#909]: https://github.com/tafia/quick-xml/pull/909 +[#912]: https://github.com/tafia/quick-xml/pull/912 [`Serializer::text_format()`]: https://docs.rs/quick-xml/0.38.4/quick_xml/se/struct.Serializer.html#method.text_format diff --git a/src/de/map.rs b/src/de/map.rs index b3ddfa20..1b2879bf 100644 --- a/src/de/map.rs +++ b/src/de/map.rs @@ -11,7 +11,6 @@ use crate::{ events::attributes::IterState, events::BytesStart, name::QName, - utils::CowRef, }; use serde::de::value::BorrowedStrDeserializer; use serde::de::{self, DeserializeSeed, Deserializer as _, MapAccess, SeqAccess, Visitor}; diff --git a/src/de/mod.rs b/src/de/mod.rs index fab4915d..35ba64c6 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -1977,21 +1977,14 @@ // Also, macros should be imported before using them use serde::serde_if_integer128; -macro_rules! deserialize_num { - ($deserialize:ident => $visit:ident, $($mut:tt)?) => { +macro_rules! forward_to_simple_type { + ($deserialize:ident, $($mut:tt)?) => { + #[inline] fn $deserialize($($mut)? self, visitor: V) -> Result where V: Visitor<'de>, { - // No need to unescape because valid integer representations cannot be escaped - let text = self.read_string()?; - match text.parse() { - Ok(number) => visitor.$visit(number), - Err(_) => match text { - Cow::Borrowed(t) => visitor.visit_str(t), - Cow::Owned(t) => visitor.visit_string(t), - } - } + SimpleTypeDeserializer::from_text(self.read_string()?).$deserialize(visitor) } }; } @@ -2000,63 +1993,29 @@ macro_rules! deserialize_num { /// byte arrays, booleans and identifiers. macro_rules! deserialize_primitives { ($($mut:tt)?) => { - deserialize_num!(deserialize_i8 => visit_i8, $($mut)?); - deserialize_num!(deserialize_i16 => visit_i16, $($mut)?); - deserialize_num!(deserialize_i32 => visit_i32, $($mut)?); - deserialize_num!(deserialize_i64 => visit_i64, $($mut)?); + forward_to_simple_type!(deserialize_i8, $($mut)?); + forward_to_simple_type!(deserialize_i16, $($mut)?); + forward_to_simple_type!(deserialize_i32, $($mut)?); + forward_to_simple_type!(deserialize_i64, $($mut)?); - deserialize_num!(deserialize_u8 => visit_u8, $($mut)?); - deserialize_num!(deserialize_u16 => visit_u16, $($mut)?); - deserialize_num!(deserialize_u32 => visit_u32, $($mut)?); - deserialize_num!(deserialize_u64 => visit_u64, $($mut)?); + forward_to_simple_type!(deserialize_u8, $($mut)?); + forward_to_simple_type!(deserialize_u16, $($mut)?); + forward_to_simple_type!(deserialize_u32, $($mut)?); + forward_to_simple_type!(deserialize_u64, $($mut)?); serde_if_integer128! { - deserialize_num!(deserialize_i128 => visit_i128, $($mut)?); - deserialize_num!(deserialize_u128 => visit_u128, $($mut)?); + forward_to_simple_type!(deserialize_i128, $($mut)?); + forward_to_simple_type!(deserialize_u128, $($mut)?); } - deserialize_num!(deserialize_f32 => visit_f32, $($mut)?); - deserialize_num!(deserialize_f64 => visit_f64, $($mut)?); + forward_to_simple_type!(deserialize_f32, $($mut)?); + forward_to_simple_type!(deserialize_f64, $($mut)?); - fn deserialize_bool($($mut)? self, visitor: V) -> Result - where - V: Visitor<'de>, - { - let text = match self.read_string()? { - Cow::Borrowed(s) => CowRef::Input(s), - Cow::Owned(s) => CowRef::Owned(s), - }; - text.deserialize_bool(visitor) - } + forward_to_simple_type!(deserialize_bool, $($mut)?); + forward_to_simple_type!(deserialize_char, $($mut)?); - /// Character represented as [strings](#method.deserialize_str). - #[inline] - fn deserialize_char(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - self.deserialize_str(visitor) - } - - fn deserialize_str($($mut)? self, visitor: V) -> Result - where - V: Visitor<'de>, - { - let text = self.read_string()?; - match text { - Cow::Borrowed(string) => visitor.visit_borrowed_str(string), - Cow::Owned(string) => visitor.visit_string(string), - } - } - - /// Representation of owned strings the same as [non-owned](#method.deserialize_str). - #[inline] - fn deserialize_string(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - self.deserialize_str(visitor) - } + forward_to_simple_type!(deserialize_str, $($mut)?); + forward_to_simple_type!(deserialize_string, $($mut)?); /// Forwards deserialization to the [`deserialize_any`](#method.deserialize_any). #[inline] @@ -2163,7 +2122,6 @@ use crate::{ events::{BytesCData, BytesEnd, BytesRef, BytesStart, BytesText, Event}, name::QName, reader::NsReader, - utils::CowRef, }; use serde::de::{ self, Deserialize, DeserializeOwned, DeserializeSeed, IntoDeserializer, SeqAccess, Visitor, @@ -2921,13 +2879,17 @@ where /// [`CData`]: Event::CData fn read_string_impl(&mut self, allow_start: bool) -> Result, DeError> { match self.next()? { + // Reached by doc tests only: this file, lines 979 and 996 DeEvent::Text(e) => Ok(e.text), // allow one nested level + // Reached by trivial::{...}::{field, field_nested, field_tag_after, field_tag_before, nested, tag_after, tag_before, wrapped} DeEvent::Start(e) if allow_start => self.read_text(e.name()), + // TODO: not reached by any tests DeEvent::Start(e) => Err(DeError::UnexpectedStart(e.name().as_ref().to_owned())), // SAFETY: The reader is guaranteed that we don't have unmatched tags // If we here, then our deserializer has a bug DeEvent::End(e) => unreachable!("{:?}", e), + // Reached by trivial::{empty_doc, only_comment} DeEvent::Eof => Err(DeError::UnexpectedEof), } } @@ -2941,17 +2903,23 @@ where match self.next()? { DeEvent::Text(e) => match self.next()? { // The matching tag name is guaranteed by the reader + // Reached by trivial::{...}::{field, wrapped} DeEvent::End(_) => Ok(e.text), // SAFETY: Cannot be two consequent Text events, they would be merged into one DeEvent::Text(_) => unreachable!(), + // Reached by trivial::{...}::{field_tag_after, tag_after} DeEvent::Start(e) => Err(DeError::UnexpectedStart(e.name().as_ref().to_owned())), + // Reached by struct_::non_closed::elements_child DeEvent::Eof => Err(Error::missed_end(name, self.reader.decoder()).into()), }, // We can get End event in case of `` or `` input // Return empty text in that case // The matching tag name is guaranteed by the reader + // Reached by {...}::xs_list::empty DeEvent::End(_) => Ok("".into()), + // Reached by trivial::{...}::{field_nested, field_tag_before, nested, tag_before} DeEvent::Start(s) => Err(DeError::UnexpectedStart(s.name().as_ref().to_owned())), + // Reached by struct_::non_closed::elements_child DeEvent::Eof => Err(Error::missed_end(name, self.reader.decoder()).into()), } } diff --git a/src/de/simple_type.rs b/src/de/simple_type.rs index 41db1273..db0ab04a 100644 --- a/src/de/simple_type.rs +++ b/src/de/simple_type.rs @@ -7,7 +7,7 @@ use crate::de::Text; use crate::encoding::Decoder; use crate::errors::serialize::DeError; use crate::escape::unescape; -use crate::utils::CowRef; +use crate::utils::{trim_xml_spaces, CowRef}; use memchr::memchr; use serde::de::value::UnitDeserializer; use serde::de::{ @@ -25,9 +25,9 @@ macro_rules! deserialize_num { V: Visitor<'de>, { let text: &str = self.content.as_ref(); - match text.parse() { + match trim_xml_spaces(text).parse() { Ok(number) => visitor.$visit(number), - Err(_) => self.content.deserialize_str(visitor), + Err(_) => self.deserialize_str(visitor), } } }; @@ -146,7 +146,20 @@ impl<'de, 'a> Deserializer<'de> for AtomicDeserializer<'de, 'a> { where V: Visitor<'de>, { - self.content.deserialize_bool(visitor) + let text = self.content.as_ref(); + let text = if self.escaped { + unescape(text)? + } else { + Cow::Borrowed(text) + }; + match trim_xml_spaces(&text) { + "1" | "true" => visitor.visit_bool(true), + "0" | "false" => visitor.visit_bool(false), + _ => match text { + Cow::Borrowed(_) => self.content.deserialize_str(visitor), + Cow::Owned(s) => visitor.visit_string(s), + }, + } } deserialize_num!(deserialize_i8 => visit_i8); @@ -172,7 +185,24 @@ impl<'de, 'a> Deserializer<'de> for AtomicDeserializer<'de, 'a> { where V: Visitor<'de>, { - self.deserialize_str(visitor) + let text: &str = self.content.as_ref(); + let text = if self.escaped { + unescape(text)? + } else { + Cow::Borrowed(text) + }; + let trimmed = trim_xml_spaces(&text); + // If string is empty or contains only XML space characters (probably only one), + // deserialize as usual string and allow visitor to accept or reject it. + // Otherwise trim spaces and allow visitor to accept or reject the rest. + if trimmed.is_empty() { + match text { + Cow::Borrowed(_) => self.content.deserialize_str(visitor), + Cow::Owned(s) => visitor.visit_string(s), + } + } else { + visitor.visit_str(trimmed) + } } /// Supply to the visitor borrowed string, string slice, or owned string @@ -611,43 +641,11 @@ impl<'de, 'a> Deserializer<'de> for SimpleTypeDeserializer<'de, 'a> { deserialize_primitive!(deserialize_f32); deserialize_primitive!(deserialize_f64); + deserialize_primitive!(deserialize_char); deserialize_primitive!(deserialize_str); - - /// Forwards deserialization to the [`Self::deserialize_str`] - #[inline] - fn deserialize_char(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - self.deserialize_str(visitor) - } - - /// Forwards deserialization to the [`Self::deserialize_str`] - #[inline] - fn deserialize_string(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - self.deserialize_str(visitor) - } - - /// Forwards deserialization to the [`Self::deserialize_str`] - #[inline] - fn deserialize_bytes(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - self.deserialize_str(visitor) - } - - /// Forwards deserialization to the [`Self::deserialize_str`] - #[inline] - fn deserialize_byte_buf(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - self.deserialize_bytes(visitor) - } + deserialize_primitive!(deserialize_string); + deserialize_primitive!(deserialize_bytes); + deserialize_primitive!(deserialize_byte_buf); fn deserialize_option(self, visitor: V) -> Result where diff --git a/src/de/text.rs b/src/de/text.rs index a72f3147..e8a0ad51 100644 --- a/src/de/text.rs +++ b/src/de/text.rs @@ -2,7 +2,6 @@ use crate::{ de::simple_type::SimpleTypeDeserializer, de::{Text, TEXT_KEY}, errors::serialize::DeError, - utils::CowRef, }; use serde::de::value::BorrowedStrDeserializer; use serde::de::{DeserializeSeed, Deserializer, EnumAccess, VariantAccess, Visitor}; diff --git a/src/utils.rs b/src/utils.rs index bb4dc90e..6e91823d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -375,6 +375,19 @@ pub const fn trim_xml_end(mut bytes: &[u8]) -> &[u8] { bytes } +/// Returns a string slice with XML whitespace characters removed from both sides. +/// +/// 'Whitespace' refers to the definition used by [`is_whitespace`]. +#[inline] +pub fn trim_xml_spaces(text: &str) -> &str { + let bytes = trim_xml_end(trim_xml_start(text.as_bytes())); + match core::str::from_utf8(bytes) { + Ok(s) => s, + // SAFETY: Removing XML space characters (subset of ASCII) from a `&str` does not invalidate UTF-8. + _ => unreachable!(), + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////// /// Splits string into pieces which can be part of a single `CDATA` section. diff --git a/tests/serde-de.rs b/tests/serde-de.rs index b882b415..ff51ab11 100644 --- a/tests/serde-de.rs +++ b/tests/serde-de.rs @@ -178,17 +178,160 @@ mod trivial { } macro_rules! in_struct { + // string, char and bool have some specifics in some tests + (string: $type:ty = $value:expr, $expected:literal) => { + in_struct!( + string: $type = $value, $expected.into(), concat!(" \n\t", $expected, " \n\t").into(); + + /// Try to deserialize type from a root tag without data or with only spaces + #[test] + fn wrapped_empty() { + let item: $type = from_str("").unwrap(); + let expected: $type = "".into(); + assert_eq!(item, expected); + + let item: $type = from_str(" \r\n\t").unwrap(); + // \r\n normalized to \n + let expected: $type = " \n\t".into(); + assert_eq!(item, expected); + } + + #[test] + fn field_empty() { + let item: Field<$type> = from_str("").unwrap(); + assert_eq!(item, Field { value: "".into() }); + + let item: Field<$type> = from_str(" \r\n\t").unwrap(); + // \r\n normalized to \n + assert_eq!(item, Field { value: " \n\t".into() }); + } + ); + }; + (char_: $type:ty = $value:expr, $expected:expr) => { + in_struct!( + char_: $type = $value, $expected, $expected; + + /// Try to deserialize type from a root tag without data or with only spaces + #[test] + fn wrapped_empty() { + match from_str::<$type>("") { + Err(DeError::Custom(msg)) => assert_eq!(msg, r#"invalid value: string "", expected a character"#), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } + + match from_str::<$type>(" \r\n\t") { + // \r\n normalized to \n + Err(DeError::Custom(msg)) => assert_eq!(msg, r#"invalid value: string " \n\t", expected a character"#), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } + } + + #[test] + fn field_empty() { + match from_str::>("") { + Err(DeError::Custom(msg)) => assert_eq!(msg, r#"invalid value: string "", expected a character"#), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } + + match from_str::>(" \r\n\t") { + // \r\n normalized to \n + Err(DeError::Custom(msg)) => assert_eq!(msg, r#"invalid value: string " \n\t", expected a character"#), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } + } + ); + }; + ($name:ident: bool = $value:expr, $expected:expr) => { + in_struct!( + $name: bool = $value, $expected, $expected; + + /// Try to deserialize type from a root tag without data or with only spaces + #[test] + fn wrapped_empty() { + match from_str::("") { + Err(DeError::Custom(msg)) => assert_eq!(msg, r#"invalid type: string "", expected a boolean"#), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } + + match from_str::(" \r\n\t") { + // \r\n normalized to \n + Err(DeError::Custom(msg)) => assert_eq!(msg, r#"invalid type: string " \n\t", expected a boolean"#), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } + } + + #[test] + fn field_empty() { + match from_str::>("") { + Err(DeError::Custom(msg)) => assert_eq!(msg, r#"invalid type: string "", expected a boolean"#), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } + + match from_str::>(" \r\n\t") { + // \r\n normalized to \n + Err(DeError::Custom(msg)) => assert_eq!(msg, r#"invalid type: string " \n\t", expected a boolean"#), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } + } + ); + }; ($name:ident: $type:ty = $value:expr, $expected:expr) => { + in_struct!( + $name: $type = $value, $expected, $expected; + + /// Try to deserialize type from a root tag without data or with only spaces + #[test] + fn wrapped_empty() { + match from_str::<$type>("") { + Err(DeError::Custom(msg)) => assert_eq!(msg, concat!(r#"invalid type: string "", expected "#, stringify!($type))), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } + + match from_str::<$type>(" \r\n\t") { + // \r\n normalized to \n + Err(DeError::Custom(msg)) => assert_eq!(msg, concat!(r#"invalid type: string " \n\t", expected "#, stringify!($type))), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } + } + + #[test] + fn field_empty() { + match from_str::>("") { + Err(DeError::Custom(msg)) => assert_eq!(msg, concat!(r#"invalid type: string "", expected "#, stringify!($type))), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } + + match from_str::>(" \r\n\t") { + // \r\n normalized to \n + Err(DeError::Custom(msg)) => assert_eq!(msg, concat!(r#"invalid type: string " \n\t", expected "#, stringify!($type))), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } + } + ); + }; + ($name:ident: $type:ty = $value:expr, $expected:expr, $expected_with_indent:expr; $($specific_tests:item)*) => { mod $name { use super::*; use pretty_assertions::assert_eq; + $($specific_tests)* + + /// Try to deserialize type wrapped in a tag, optionally surround with spaces #[test] - fn naked() { + fn wrapped() { let item: $type = from_str(&format!("{}", $value)).unwrap(); let expected: $type = $expected; assert_eq!(item, expected); + let item: $type = + from_str(&format!(" \r\n\t{} \r\n\t", $value)).unwrap(); + let expected: $type = $expected_with_indent; + assert_eq!(item, expected); + } + + /// Try to deserialize type wrapped in two tags + #[test] + fn nested() { match from_str::<$type>(&format!("{}", $value)) { // Expected unexpected start element `` Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"nested"), @@ -198,6 +341,22 @@ mod trivial { ), } + match from_str::<$type>(&format!( + " \r\n\t{} \r\n\t", + $value + )) { + // Expected unexpected start element `` + Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"nested"), + x => panic!( + r#"Expected `Err(UnexpectedStart("nested"))`, but got `{:?}`"#, + x + ), + } + } + + /// Try to deserialize type wrapped in a tag with another tag after content + #[test] + fn tag_after() { match from_str::<$type>(&format!("{}", $value)) { // Expected unexpected start element `` Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"something-else"), @@ -207,6 +366,22 @@ mod trivial { ), } + match from_str::<$type>(&format!( + " \r\n\t{} \r\n\t", + $value + )) { + // Expected unexpected start element `` + Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"something-else"), + x => panic!( + r#"Expected `Err(UnexpectedStart("something-else"))`, but got `{:?}`"#, + x + ), + } + } + + /// Try to deserialize type wrapped in a tag with another tag before content + #[test] + fn tag_before() { match from_str::<$type>(&format!("{}", $value)) { // Expected unexpected start element `` Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"something-else"), @@ -215,14 +390,34 @@ mod trivial { x ), } + + match from_str::<$type>(&format!( + " \r\n\t{} \r\n\t", + $value + )) { + // Expected unexpected start element `` + Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"something-else"), + x => panic!( + r#"Expected `Err(UnexpectedStart("something-else"))`, but got `{:?}`"#, + x + ), + } } + /// Try to deserialize type which is a field of a struct #[test] fn field() { let item: Field<$type> = from_str(&format!("{}", $value)).unwrap(); assert_eq!(item, Field { value: $expected }); + let item: Field<$type> = + from_str(&format!(" \r\n\t{} \r\n\t", $value)).unwrap(); + assert_eq!(item, Field { value: $expected_with_indent }); + } + + #[test] + fn field_nested() { match from_str::>(&format!( "{}", $value @@ -235,6 +430,21 @@ mod trivial { ), } + match from_str::>(&format!( + " \r\n\t{} \r\n\t", + $value + )) { + // Expected unexpected start element `` + Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"nested"), + x => panic!( + r#"Expected `Err(UnexpectedStart("nested"))`, but got `{:?}`"#, + x + ), + } + } + + #[test] + fn field_tag_after() { match from_str::>(&format!( "{}", $value @@ -247,6 +457,21 @@ mod trivial { ), } + match from_str::>(&format!( + " \r\n\t{} \r\n\t", + $value + )) { + // Expected unexpected start element `` + Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"something-else"), + x => panic!( + r#"Expected `Err(UnexpectedStart("something-else"))`, but got `{:?}`"#, + x + ), + } + } + + #[test] + fn field_tag_before() { match from_str::>(&format!( "{}", $value @@ -258,6 +483,31 @@ mod trivial { x ), } + + match from_str::>(&format!( + " \r\n\t{} \r\n\t", + $value + )) { + // Expected unexpected start element `` + Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"something-else"), + x => panic!( + r#"Expected `Err(UnexpectedStart("something-else"))`, but got `{:?}`"#, + x + ), + } + } + + #[test] + fn text_empty() { + match from_str::>("") { + Err(DeError::Custom(msg)) => assert_eq!(msg, "missing field `$text`"), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } + + match from_str::>(" \r\n\t") { + Err(DeError::Custom(msg)) => assert_eq!(msg, "missing field `$text`"), + x => panic!("Expected `Err(Custom(_))`, but got `{:?}`", x), + } } /// Tests deserialization from top-level tag content: `...content...` @@ -267,18 +517,13 @@ mod trivial { from_str(&format!("{}", $value)).unwrap(); assert_eq!(item, Trivial { value: $expected }); - // Unlike `naked` test, here we have a struct that is serialized to XML with - // an implicit field `$text` and some other field "something-else" which not interested - // for us in the Trivial structure. If you want the same behavior as for naked primitive, - // use `$value` field which would consume all data, unless a dedicated field would present - let item: Trivial<$type> = - from_str(&format!("{}", $value)).unwrap(); - assert_eq!(item, Trivial { value: $expected }); - let item: Trivial<$type> = - from_str(&format!("{}", $value)).unwrap(); - assert_eq!(item, Trivial { value: $expected }); + from_str(&format!(" \r\n\t{} \r\n\t", $value)).unwrap(); + assert_eq!(item, Trivial { value: $expected_with_indent }); + } + #[test] + fn text_nested() { match from_str::>(&format!( "{}", $value @@ -290,6 +535,44 @@ mod trivial { x ), } + + match from_str::>(&format!( + " \r\n\t{} \r\n\t", + $value + )) { + // Expected unexpected start element `` + Err(DeError::Custom(reason)) => assert_eq!(reason, "missing field `$text`"), + x => panic!( + r#"Expected `Err(Custom("missing field `$text`"))`, but got `{:?}`"#, + x + ), + } + } + + #[test] + fn text_tag_after() { + // Unlike `wrapped` test, here we have a struct that is serialized to XML with + // an implicit field `$text` and some other field "something-else" which not interested + // for us in the Trivial structure. If you want the same behavior as for naked primitive, + // use `$value` field which would consume all data, unless a dedicated field would present + let item: Trivial<$type> = + from_str(&format!("{}", $value)).unwrap(); + assert_eq!(item, Trivial { value: $expected }); + + let item: Trivial<$type> = + from_str(&format!(" \r\n\t{} \r\n\t", $value)).unwrap(); + assert_eq!(item, Trivial { value: $expected_with_indent }); + } + + #[test] + fn text_tag_before() { + let item: Trivial<$type> = + from_str(&format!("{}", $value)).unwrap(); + assert_eq!(item, Trivial { value: $expected }); + + let item: Trivial<$type> = + from_str(&format!(" \r\n\t{} \r\n\t", $value)).unwrap(); + assert_eq!(item, Trivial { value: $expected_with_indent }); } } }; @@ -342,7 +625,7 @@ mod trivial { in_struct!(true_: bool = "true", true); in_struct!(char_: char = "r", 'r'); - in_struct!(string: String = "escaped string", "escaped string".into()); + in_struct!(string: String = "escaped string", "escaped string"); /// XML does not able to store binary data #[test] @@ -405,7 +688,7 @@ mod trivial { in_struct!(char_: char = "", 'r'); // Escape sequences does not processed inside CDATA section - in_struct!(string: String = "", "escaped string".into()); + in_struct!(string: String = "", "escaped string"); /// XML does not able to store binary data #[test]