From ddd1800465f928738cf2ea8e5fb0144673f51b62 Mon Sep 17 00:00:00 2001 From: Mingun Date: Tue, 3 Oct 2023 22:42:33 +0500 Subject: [PATCH 1/4] Move definition of tag inside a macro --- tests/serde-de.rs | 78 +++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/tests/serde-de.rs b/tests/serde-de.rs index f31ed5eb..e754e8fb 100644 --- a/tests/serde-de.rs +++ b/tests/serde-de.rs @@ -203,12 +203,12 @@ mod trivial { ($name:ident: $type:ty = $value:expr, $expected:expr) => { #[test] fn $name() { - let item: Trivial<$type> = from_str($value).unwrap(); + let item: Trivial<$type> = from_str(&format!("{}", $value)).unwrap(); assert_eq!(item, Trivial { value: $expected }); - match from_str::>(&format!("{}", $value)) { - // Expected unexpected start element `` + match from_str::>(&format!("{}", $value)) { + // Expected unexpected start element `` Err(DeError::Custom(reason)) => assert_eq!(reason, "missing field `$text`"), x => panic!( r#"Expected `Err(DeError::Custom("missing field `$text`"))`, but got `{:?}`"#, @@ -225,31 +225,31 @@ mod trivial { use super::*; use pretty_assertions::assert_eq; - in_struct!(i8_: i8 = "-42", -42i8); - in_struct!(i16_: i16 = "-4200", -4200i16); - in_struct!(i32_: i32 = "-42000000", -42000000i32); - in_struct!(i64_: i64 = "-42000000000000", -42000000000000i64); - in_struct!(isize_: isize = "-42000000000000", -42000000000000isize); + in_struct!(i8_: i8 = "-42", -42i8); + in_struct!(i16_: i16 = "-4200", -4200i16); + in_struct!(i32_: i32 = "-42000000", -42000000i32); + in_struct!(i64_: i64 = "-42000000000000", -42000000000000i64); + in_struct!(isize_: isize = "-42000000000000", -42000000000000isize); - in_struct!(u8_: u8 = "42", 42u8); - in_struct!(u16_: u16 = "4200", 4200u16); - in_struct!(u32_: u32 = "42000000", 42000000u32); - in_struct!(u64_: u64 = "42000000000000", 42000000000000u64); - in_struct!(usize_: usize = "42000000000000", 42000000000000usize); + in_struct!(u8_: u8 = "42", 42u8); + in_struct!(u16_: u16 = "4200", 4200u16); + in_struct!(u32_: u32 = "42000000", 42000000u32); + in_struct!(u64_: u64 = "42000000000000", 42000000000000u64); + in_struct!(usize_: usize = "42000000000000", 42000000000000usize); serde_if_integer128! { - in_struct!(u128_: u128 = "420000000000000000000000000000", 420000000000000000000000000000u128); - in_struct!(i128_: i128 = "-420000000000000000000000000000", -420000000000000000000000000000i128); + in_struct!(u128_: u128 = "420000000000000000000000000000", 420000000000000000000000000000u128); + in_struct!(i128_: i128 = "-420000000000000000000000000000", -420000000000000000000000000000i128); } - in_struct!(f32_: f32 = "4.2", 4.2f32); - in_struct!(f64_: f64 = "4.2", 4.2f64); + in_struct!(f32_: f32 = "4.2", 4.2f32); + in_struct!(f64_: f64 = "4.2", 4.2f64); - in_struct!(false_: bool = "false", false); - in_struct!(true_: bool = "true", true); - in_struct!(char_: char = "r", 'r'); + in_struct!(false_: bool = "false", false); + 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".into()); /// XML does not able to store binary data #[test] @@ -287,32 +287,32 @@ mod trivial { use super::*; use pretty_assertions::assert_eq; - in_struct!(i8_: i8 = "", -42i8); - in_struct!(i16_: i16 = "", -4200i16); - in_struct!(i32_: i32 = "", -42000000i32); - in_struct!(i64_: i64 = "", -42000000000000i64); - in_struct!(isize_: isize = "", -42000000000000isize); + in_struct!(i8_: i8 = "", -42i8); + in_struct!(i16_: i16 = "", -4200i16); + in_struct!(i32_: i32 = "", -42000000i32); + in_struct!(i64_: i64 = "", -42000000000000i64); + in_struct!(isize_: isize = "", -42000000000000isize); - in_struct!(u8_: u8 = "", 42u8); - in_struct!(u16_: u16 = "", 4200u16); - in_struct!(u32_: u32 = "", 42000000u32); - in_struct!(u64_: u64 = "", 42000000000000u64); - in_struct!(usize_: usize = "", 42000000000000usize); + in_struct!(u8_: u8 = "", 42u8); + in_struct!(u16_: u16 = "", 4200u16); + in_struct!(u32_: u32 = "", 42000000u32); + in_struct!(u64_: u64 = "", 42000000000000u64); + in_struct!(usize_: usize = "", 42000000000000usize); serde_if_integer128! { - in_struct!(u128_: u128 = "", 420000000000000000000000000000u128); - in_struct!(i128_: i128 = "", -420000000000000000000000000000i128); + in_struct!(u128_: u128 = "", 420000000000000000000000000000u128); + in_struct!(i128_: i128 = "", -420000000000000000000000000000i128); } - in_struct!(f32_: f32 = "", 4.2f32); - in_struct!(f64_: f64 = "", 4.2f64); + in_struct!(f32_: f32 = "", 4.2f32); + in_struct!(f64_: f64 = "", 4.2f64); - in_struct!(false_: bool = "", false); - in_struct!(true_: bool = "", true); - in_struct!(char_: char = "", 'r'); + in_struct!(false_: bool = "", false); + in_struct!(true_: bool = "", true); + 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".into()); /// XML does not able to store binary data #[test] From 8987d1524a56526f063a8539957d4f5945dfdbc1 Mon Sep 17 00:00:00 2001 From: Mingun Date: Tue, 3 Oct 2023 22:51:32 +0500 Subject: [PATCH 2/4] Reorganize structure of `trivial` tests New tests will be added in the following commit (review with "ignore whitespace changes mode" on) --- tests/serde-de.rs | 233 +++++++++++++++++++++++----------------------- 1 file changed, 117 insertions(+), 116 deletions(-) diff --git a/tests/serde-de.rs b/tests/serde-de.rs index e754e8fb..070ab18f 100644 --- a/tests/serde-de.rs +++ b/tests/serde-de.rs @@ -183,26 +183,15 @@ mod trivial { eof!(""); } - /// Tests deserialization from top-level tag content: `...content...` - mod struct_ { - use super::*; - - /// Well-formed XML must have a single tag at the root level. - /// Any XML tag can be modeled as a struct, and content of this tag are modeled as - /// fields of this struct. - /// - /// Because we want to get access to unnamed content of the tag (usually, this internal - /// XML node called `$text`) we use a rename to a special name `$text` - #[derive(Debug, Deserialize, PartialEq)] - struct Trivial { - #[serde(rename = "$text")] - value: T, - } + macro_rules! in_struct { + ($name:ident: $type:ty = $value:expr, $expected:expr) => { + mod $name { + use super::*; + use pretty_assertions::assert_eq; - macro_rules! in_struct { - ($name:ident: $type:ty = $value:expr, $expected:expr) => { + /// Tests deserialization from top-level tag content: `...content...` #[test] - fn $name() { + fn struct_() { let item: Trivial<$type> = from_str(&format!("{}", $value)).unwrap(); assert_eq!(item, Trivial { value: $expected }); @@ -216,130 +205,142 @@ mod trivial { ), } } - }; - } + } + }; + } - /// Tests deserialization from text content in a tag - #[rustfmt::skip] // tests formatted in a table - mod text { - use super::*; - use pretty_assertions::assert_eq; + /// Well-formed XML must have a single tag at the root level. + /// Any XML tag can be modeled as a struct, and content of this tag are modeled as + /// fields of this struct. + /// + /// Because we want to get access to unnamed content of the tag (usually, this internal + /// XML node called `$text`) we use a rename to a special name `$text` + #[derive(Debug, Deserialize, PartialEq)] + struct Trivial { + #[serde(rename = "$text")] + value: T, + } - in_struct!(i8_: i8 = "-42", -42i8); - in_struct!(i16_: i16 = "-4200", -4200i16); - in_struct!(i32_: i32 = "-42000000", -42000000i32); - in_struct!(i64_: i64 = "-42000000000000", -42000000000000i64); - in_struct!(isize_: isize = "-42000000000000", -42000000000000isize); + /// Tests deserialization from text content in a tag + #[rustfmt::skip] // tests formatted in a table + mod text { + use super::*; + use pretty_assertions::assert_eq; - in_struct!(u8_: u8 = "42", 42u8); - in_struct!(u16_: u16 = "4200", 4200u16); - in_struct!(u32_: u32 = "42000000", 42000000u32); - in_struct!(u64_: u64 = "42000000000000", 42000000000000u64); - in_struct!(usize_: usize = "42000000000000", 42000000000000usize); + in_struct!(i8_: i8 = "-42", -42i8); + in_struct!(i16_: i16 = "-4200", -4200i16); + in_struct!(i32_: i32 = "-42000000", -42000000i32); + in_struct!(i64_: i64 = "-42000000000000", -42000000000000i64); + in_struct!(isize_: isize = "-42000000000000", -42000000000000isize); - serde_if_integer128! { - in_struct!(u128_: u128 = "420000000000000000000000000000", 420000000000000000000000000000u128); - in_struct!(i128_: i128 = "-420000000000000000000000000000", -420000000000000000000000000000i128); - } + in_struct!(u8_: u8 = "42", 42u8); + in_struct!(u16_: u16 = "4200", 4200u16); + in_struct!(u32_: u32 = "42000000", 42000000u32); + in_struct!(u64_: u64 = "42000000000000", 42000000000000u64); + in_struct!(usize_: usize = "42000000000000", 42000000000000usize); + + serde_if_integer128! { + in_struct!(u128_: u128 = "420000000000000000000000000000", 420000000000000000000000000000u128); + in_struct!(i128_: i128 = "-420000000000000000000000000000", -420000000000000000000000000000i128); + } - in_struct!(f32_: f32 = "4.2", 4.2f32); - in_struct!(f64_: f64 = "4.2", 4.2f64); + in_struct!(f32_: f32 = "4.2", 4.2f32); + in_struct!(f64_: f64 = "4.2", 4.2f64); - in_struct!(false_: bool = "false", false); - in_struct!(true_: bool = "true", true); - in_struct!(char_: char = "r", 'r'); + in_struct!(false_: bool = "false", false); + 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".into()); - /// XML does not able to store binary data - #[test] - fn byte_buf() { - match from_str::>("escaped byte_buf") { - Err(DeError::Unsupported(msg)) => { - assert_eq!(msg, "binary data content is not supported by XML format") - } - x => panic!( - r#"Expected `Err(DeError::Unsupported("binary data content is not supported by XML format"))`, but got `{:?}`"#, - x - ), + /// XML does not able to store binary data + #[test] + fn byte_buf() { + match from_str::>("escaped byte_buf") { + Err(DeError::Unsupported(msg)) => { + assert_eq!(msg, "binary data content is not supported by XML format") } + x => panic!( + r#"Expected `Err(DeError::Unsupported("binary data content is not supported by XML format"))`, but got `{:?}`"#, + x + ), } + } - /// XML does not able to store binary data - #[test] - fn bytes() { - match from_str::>("escaped byte_buf") { - Err(DeError::Unsupported(msg)) => { - assert_eq!(msg, "binary data content is not supported by XML format") - } - x => panic!( - r#"Expected `Err(DeError::Unsupported("binary data content is not supported by XML format"))`, but got `{:?}`"#, - x - ), + /// XML does not able to store binary data + #[test] + fn bytes() { + match from_str::>("escaped byte_buf") { + Err(DeError::Unsupported(msg)) => { + assert_eq!(msg, "binary data content is not supported by XML format") } + x => panic!( + r#"Expected `Err(DeError::Unsupported("binary data content is not supported by XML format"))`, but got `{:?}`"#, + x + ), } } + } - /// Tests deserialization from CDATA content in a tag. - /// CDATA handling similar to text handling except that strings does not unescapes - #[rustfmt::skip] // tests formatted in a table - mod cdata { - use super::*; - use pretty_assertions::assert_eq; + /// Tests deserialization from CDATA content in a tag. + /// CDATA handling similar to text handling except that strings does not unescapes + #[rustfmt::skip] // tests formatted in a table + mod cdata { + use super::*; + use pretty_assertions::assert_eq; - in_struct!(i8_: i8 = "", -42i8); - in_struct!(i16_: i16 = "", -4200i16); - in_struct!(i32_: i32 = "", -42000000i32); - in_struct!(i64_: i64 = "", -42000000000000i64); - in_struct!(isize_: isize = "", -42000000000000isize); + in_struct!(i8_: i8 = "", -42i8); + in_struct!(i16_: i16 = "", -4200i16); + in_struct!(i32_: i32 = "", -42000000i32); + in_struct!(i64_: i64 = "", -42000000000000i64); + in_struct!(isize_: isize = "", -42000000000000isize); - in_struct!(u8_: u8 = "", 42u8); - in_struct!(u16_: u16 = "", 4200u16); - in_struct!(u32_: u32 = "", 42000000u32); - in_struct!(u64_: u64 = "", 42000000000000u64); - in_struct!(usize_: usize = "", 42000000000000usize); + in_struct!(u8_: u8 = "", 42u8); + in_struct!(u16_: u16 = "", 4200u16); + in_struct!(u32_: u32 = "", 42000000u32); + in_struct!(u64_: u64 = "", 42000000000000u64); + in_struct!(usize_: usize = "", 42000000000000usize); - serde_if_integer128! { - in_struct!(u128_: u128 = "", 420000000000000000000000000000u128); - in_struct!(i128_: i128 = "", -420000000000000000000000000000i128); - } + serde_if_integer128! { + in_struct!(u128_: u128 = "", 420000000000000000000000000000u128); + in_struct!(i128_: i128 = "", -420000000000000000000000000000i128); + } - in_struct!(f32_: f32 = "", 4.2f32); - in_struct!(f64_: f64 = "", 4.2f64); + in_struct!(f32_: f32 = "", 4.2f32); + in_struct!(f64_: f64 = "", 4.2f64); - in_struct!(false_: bool = "", false); - in_struct!(true_: bool = "", true); - in_struct!(char_: char = "", 'r'); + in_struct!(false_: bool = "", false); + in_struct!(true_: bool = "", true); + in_struct!(char_: char = "", 'r'); - // Escape sequences does not processed inside CDATA section - in_struct!(string: String = "", "escaped string".into()); + // Escape sequences does not processed inside CDATA section + in_struct!(string: String = "", "escaped string".into()); - /// XML does not able to store binary data - #[test] - fn byte_buf() { - match from_str::>("") { - Err(DeError::Unsupported(msg)) => { - assert_eq!(msg, "binary data content is not supported by XML format") - } - x => panic!( - r#"Expected `Err(DeError::Unsupported("binary data content is not supported by XML format"))`, but got `{:?}`"#, - x - ), + /// XML does not able to store binary data + #[test] + fn byte_buf() { + match from_str::>("") { + Err(DeError::Unsupported(msg)) => { + assert_eq!(msg, "binary data content is not supported by XML format") } + x => panic!( + r#"Expected `Err(DeError::Unsupported("binary data content is not supported by XML format"))`, but got `{:?}`"#, + x + ), } + } - /// XML does not able to store binary data - #[test] - fn bytes() { - match from_str::>("") { - Err(DeError::Unsupported(msg)) => { - assert_eq!(msg, "binary data content is not supported by XML format") - } - x => panic!( - r#"Expected `Err(DeError::Unsupported("binary data content is not supported by XML format"))`, but got `{:?}`"#, - x - ), + /// XML does not able to store binary data + #[test] + fn bytes() { + match from_str::>("") { + Err(DeError::Unsupported(msg)) => { + assert_eq!(msg, "binary data content is not supported by XML format") } + x => panic!( + r#"Expected `Err(DeError::Unsupported("binary data content is not supported by XML format"))`, but got `{:?}`"#, + x + ), } } } From 14a63d76b7217d6c7a21942c565921efb5911292 Mon Sep 17 00:00:00 2001 From: Mingun Date: Tue, 3 Oct 2023 22:55:32 +0500 Subject: [PATCH 3/4] Add new tests for deserializing primitives failures (72): trivial::cdata::char_::field trivial::cdata::char_::primitive trivial::cdata::f32_::field trivial::cdata::f32_::primitive trivial::cdata::f64_::field trivial::cdata::f64_::primitive trivial::cdata::false_::field trivial::cdata::false_::primitive trivial::cdata::i128_::field trivial::cdata::i128_::primitive trivial::cdata::i16_::field trivial::cdata::i16_::primitive trivial::cdata::i32_::field trivial::cdata::i32_::primitive trivial::cdata::i64_::field trivial::cdata::i64_::primitive trivial::cdata::i8_::field trivial::cdata::i8_::primitive trivial::cdata::isize_::field trivial::cdata::isize_::primitive trivial::cdata::string::field trivial::cdata::string::primitive trivial::cdata::true_::field trivial::cdata::true_::primitive trivial::cdata::u128_::field trivial::cdata::u128_::primitive trivial::cdata::u16_::field trivial::cdata::u16_::primitive trivial::cdata::u32_::field trivial::cdata::u32_::primitive trivial::cdata::u64_::field trivial::cdata::u64_::primitive trivial::cdata::u8_::field trivial::cdata::u8_::primitive trivial::cdata::usize_::field trivial::cdata::usize_::primitive trivial::text::char_::field trivial::text::char_::primitive trivial::text::f32_::field trivial::text::f32_::primitive trivial::text::f64_::field trivial::text::f64_::primitive trivial::text::false_::field trivial::text::false_::primitive trivial::text::i128_::field trivial::text::i128_::primitive trivial::text::i16_::field trivial::text::i16_::primitive trivial::text::i32_::field trivial::text::i32_::primitive trivial::text::i64_::field trivial::text::i64_::primitive trivial::text::i8_::field trivial::text::i8_::primitive trivial::text::isize_::field trivial::text::isize_::primitive trivial::text::string::field trivial::text::string::primitive trivial::text::true_::field trivial::text::true_::primitive trivial::text::u128_::field trivial::text::u128_::primitive trivial::text::u16_::field trivial::text::u16_::primitive trivial::text::u32_::field trivial::text::u32_::primitive trivial::text::u64_::field trivial::text::u64_::primitive trivial::text::u8_::field trivial::text::u8_::primitive trivial::text::usize_::field trivial::text::usize_::primitive --- tests/serde-de.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/tests/serde-de.rs b/tests/serde-de.rs index 070ab18f..b20d9067 100644 --- a/tests/serde-de.rs +++ b/tests/serde-de.rs @@ -189,11 +189,87 @@ mod trivial { use super::*; use pretty_assertions::assert_eq; + #[test] + fn naked() { + let item: $type = from_str(&format!("{}", $value)).unwrap(); + let expected: $type = $expected; + assert_eq!(item, expected); + + match from_str::<$type>(&format!("{}", $value)) { + // Expected unexpected start element `` + Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"nested"), + x => panic!( + r#"Expected `Err(DeError::UnexpectedStart("nested"))`, but got `{:?}`"#, + x + ), + } + + match from_str::<$type>(&format!("{}", $value)) { + // Expected unexpected start element `` + Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"something-else"), + x => panic!( + r#"Expected `Err(DeError::UnexpectedStart("something-else"))`, but got `{:?}`"#, + x + ), + } + + match from_str::<$type>(&format!("{}", $value)) { + // Expected unexpected start element `` + Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"something-else"), + x => panic!( + r#"Expected `Err(DeError::UnexpectedStart("something-else"))`, but got `{:?}`"#, + x + ), + } + } + + #[test] + fn field() { + let item: Field<$type> = from_str(&format!("{}", $value)).unwrap(); + assert_eq!(item, Field { value: $expected }); + + match from_str::>(&format!("{}", $value)) { + // Expected unexpected start element `` + Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"nested"), + x => panic!( + r#"Expected `Err(DeError::UnexpectedStart("nested"))`, but got `{:?}`"#, + x + ), + } + + match from_str::>(&format!("{}", $value)) { + // Expected unexpected start element `` + Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"something-else"), + x => panic!( + r#"Expected `Err(DeError::UnexpectedStart("something-else"))`, but got `{:?}`"#, + x + ), + } + + match from_str::>(&format!("{}", $value)) { + // Expected unexpected start element `` + Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"something-else"), + x => panic!( + r#"Expected `Err(DeError::UnexpectedStart("something-else"))`, but got `{:?}`"#, + x + ), + } + } + /// Tests deserialization from top-level tag content: `...content...` #[test] - fn struct_() { + fn text() { let item: Trivial<$type> = 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 }); match from_str::>(&format!("{}", $value)) { @@ -209,6 +285,11 @@ mod trivial { }; } + #[derive(Debug, Deserialize, PartialEq)] + struct Field { + value: T, + } + /// Well-formed XML must have a single tag at the root level. /// Any XML tag can be modeled as a struct, and content of this tag are modeled as /// fields of this struct. From 9a354d70101a7202bad7114de4bb0967ee0cbe7e Mon Sep 17 00:00:00 2001 From: Mingun Date: Tue, 3 Oct 2023 22:04:17 +0500 Subject: [PATCH 4/4] Made read_string_impl more strict. Now `text` will return error instead of returning `text` when deserialize into primitive types (ex. `u32`, but not struct S { #[serde(rename = "$text")] field: u32 }) --- Changelog.md | 4 ++++ src/de/mod.rs | 48 ++++++++++++++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/Changelog.md b/Changelog.md index 45d4100a..4e05eef2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -23,6 +23,9 @@ MSRV bumped to 1.56! Crate now uses Rust 2021 edition. - [#660]: Fixed incorrect deserialization of `xs:list`s from empty tags (`` or ``). Previously an `DeError::UnexpectedEof")` was returned in that case - [#580]: Fixed incorrect deserialization of vectors of newtypes from sequences of tags. +- [#661]: More string handling of serialized primitive values (booleans, numbers, strings, + unit structs, unit variants). `123` is no longer valid + content. Previously all data after `123` up to closing tag would be silently skipped. ### Misc Changes @@ -43,6 +46,7 @@ MSRV bumped to 1.56! Crate now uses Rust 2021 edition. [#649]: https://github.com/tafia/quick-xml/pull/646 [#651]: https://github.com/tafia/quick-xml/pull/651 [#660]: https://github.com/tafia/quick-xml/pull/660 +[#661]: https://github.com/tafia/quick-xml/pull/661 ## 0.30.0 -- 2023-07-23 diff --git a/src/de/mod.rs b/src/de/mod.rs index eb60972d..e3e37171 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -2581,11 +2581,15 @@ where /// events, merge them into one string. If there are no such events, returns /// an empty string. /// - /// If `allow_start` is `false`, then only text events is consumed, for other + /// If `allow_start` is `false`, then only text events are consumed, for other /// events an error is returned (see table below). /// - /// If `allow_start` is `true`, then first [`DeEvent::Text`] event is returned - /// and all other content is skipped until corresponding end tag will be consumed. + /// If `allow_start` is `true`, then two or three events are expected: + /// - [`DeEvent::Start`]; + /// - _(optional)_ [`DeEvent::Text`] which content is returned; + /// - [`DeEvent::End`]. If text event was missed, an empty string is returned. + /// + /// Corresponding events are consumed. /// /// # Handling events /// @@ -2603,9 +2607,8 @@ where /// |Event |XML |Handling /// |------------------|---------------------------|---------------------------------------------------------------------------------- /// |[`DeEvent::Start`]|`...` |Emits [`UnexpectedStart("any-tag")`](DeError::UnexpectedStart) - /// |[`DeEvent::End`] |`` |Returns an empty slice, if close tag matched the open one - /// |[`DeEvent::End`] |`` |Emits [`UnexpectedEnd("any-tag")`](DeError::UnexpectedEnd) - /// |[`DeEvent::Text`] |`text content` or `` (probably mixed)|Returns event content unchanged, consumes events up to `` + /// |[`DeEvent::End`] |`` |Returns an empty slice. The reader guarantee that tag will match the open one + /// |[`DeEvent::Text`] |`text content` or `` (probably mixed)|Returns event content unchanged, expects the `` after that /// |[`DeEvent::Eof`] | |Emits [`UnexpectedEof`](DeError::UnexpectedEof) /// /// [`Text`]: Event::Text @@ -2614,23 +2617,32 @@ where match self.next()? { DeEvent::Text(e) => Ok(e.text), // allow one nested level - DeEvent::Start(e) if allow_start => match self.next()? { - DeEvent::Text(t) => { - self.read_to_end(e.name())?; - Ok(t.text) - } - DeEvent::Start(s) => Err(DeError::UnexpectedStart(s.name().as_ref().to_owned())), - // We can get End event in case of `` or `` input - // Return empty text in that case - DeEvent::End(end) if end.name() == e.name() => Ok("".into()), - DeEvent::End(end) => Err(DeError::UnexpectedEnd(end.name().as_ref().to_owned())), - DeEvent::Eof => Err(DeError::UnexpectedEof), - }, + DeEvent::Start(_) if allow_start => self.read_text(), DeEvent::Start(e) => Err(DeError::UnexpectedStart(e.name().as_ref().to_owned())), DeEvent::End(e) => Err(DeError::UnexpectedEnd(e.name().as_ref().to_owned())), DeEvent::Eof => Err(DeError::UnexpectedEof), } } + /// Consumes one [`DeEvent::Text`] event and ensures that it is followed by the + /// [`DeEvent::End`] event. + fn read_text(&mut self) -> Result, DeError> { + match self.next()? { + DeEvent::Text(e) => match self.next()? { + // The matching tag name is guaranteed by the reader + DeEvent::End(_) => Ok(e.text), + // SAFETY: Cannot be two consequent Text events, they would be merged into one + DeEvent::Text(_) => unreachable!(), + DeEvent::Start(e) => Err(DeError::UnexpectedStart(e.name().as_ref().to_owned())), + DeEvent::Eof => Err(DeError::UnexpectedEof), + }, + // 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 + DeEvent::End(_) => Ok("".into()), + DeEvent::Start(s) => Err(DeError::UnexpectedStart(s.name().as_ref().to_owned())), + DeEvent::Eof => Err(DeError::UnexpectedEof), + } + } /// Drops all events until event with [name](BytesEnd::name()) `name` won't be /// dropped. This method should be called after [`Self::next()`]