From 4934d7b280d793ad5eb96b37be6d95d6d9ef9c69 Mon Sep 17 00:00:00 2001 From: Mingun Date: Tue, 8 Oct 2024 01:18:15 +0500 Subject: [PATCH 1/5] Classify the result of serialization --- Changelog.md | 2 + src/se/content.rs | 241 +++++++++++++++++++++++++++------------------- src/se/element.rs | 39 +++++--- src/se/mod.rs | 48 +++++++-- 4 files changed, 208 insertions(+), 122 deletions(-) diff --git a/Changelog.md b/Changelog.md index 0da34663..8d71aeb6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -26,10 +26,12 @@ - [#811]: Renamed `Error::EscapeError` to `Error::Escape` to match other variants. - [#811]: Narrow down error return type from `Error` where only one variant is ever returned: attribute related methods on `BytesStart` and `BytesDecl` returns `AttrError` +- [#820]: Classify output of the `Serializer` by returning an enumeration with kind of written data [#227]: https://github.com/tafia/quick-xml/issues/227 [#810]: https://github.com/tafia/quick-xml/pull/810 [#811]: https://github.com/tafia/quick-xml/pull/811 +[#820]: https://github.com/tafia/quick-xml/pull/820 ## 0.36.2 -- 2024-09-20 diff --git a/src/se/content.rs b/src/se/content.rs index 50f3b92d..30c7414f 100644 --- a/src/se/content.rs +++ b/src/se/content.rs @@ -3,7 +3,7 @@ use crate::de::TEXT_KEY; use crate::se::element::{ElementSerializer, Struct, Tuple}; use crate::se::simple_type::{QuoteTarget, SimpleTypeSerializer}; -use crate::se::{Indent, QuoteLevel, SeError, XmlName}; +use crate::se::{Indent, QuoteLevel, SeError, WriteResult, XmlName}; use serde::ser::{ Impossible, Serialize, SerializeSeq, SerializeTuple, SerializeTupleStruct, Serializer, }; @@ -15,7 +15,7 @@ macro_rules! write_primitive { #[inline] fn $method(self, value: $ty) -> Result { self.into_simple_type_serializer().$method(value)?; - Ok(()) + Ok(WriteResult::Text) } }; } @@ -24,7 +24,9 @@ macro_rules! write_primitive { /// A serializer used to serialize content of an element. It does not write /// surrounding tags. Unlike the [`ElementSerializer`], this serializer serializes -/// enums using variant names as tag names, i. e. as `...` +/// enums using variant names as tag names, i. e. as `...`. +/// +/// Returns the classification of the last written type. /// /// This serializer does the following: /// - numbers converted to a decimal representation and serialized as naked strings; @@ -107,7 +109,7 @@ impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> { /// Writes `name` as self-closed tag #[inline] - pub(super) fn write_empty(mut self, name: XmlName) -> Result<(), SeError> { + pub(super) fn write_empty(mut self, name: XmlName) -> Result { self.write_indent()?; if self.expand_empty_elements { self.writer.write_char('<')?; @@ -120,11 +122,15 @@ impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> { self.writer.write_str(name.0)?; self.writer.write_str("/>")?; } - Ok(()) + Ok(WriteResult::Element) } /// Writes simple type content between `name` tags - pub(super) fn write_wrapped(mut self, name: XmlName, serialize: S) -> Result<(), SeError> + pub(super) fn write_wrapped( + mut self, + name: XmlName, + serialize: S, + ) -> Result where S: for<'a> FnOnce(SimpleTypeSerializer<'i, &'a mut W>) -> Result<&'a mut W, SeError>, { @@ -138,7 +144,7 @@ impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> { writer.write_str("')?; - Ok(()) + Ok(WriteResult::Element) } pub(super) fn write_indent(&mut self) -> Result<(), SeError> { @@ -151,12 +157,12 @@ impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> { } impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { - type Ok = (); + type Ok = WriteResult; type Error = SeError; - type SerializeSeq = Self; - type SerializeTuple = Self; - type SerializeTupleStruct = Self; + type SerializeSeq = Seq<'w, 'i, W>; + type SerializeTuple = Seq<'w, 'i, W>; + type SerializeTupleStruct = Seq<'w, 'i, W>; type SerializeTupleVariant = Tuple<'w, 'i, W>; type SerializeMap = Impossible; type SerializeStruct = Impossible; @@ -182,21 +188,29 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { write_primitive!(serialize_f32(f32)); write_primitive!(serialize_f64(f64)); - write_primitive!(serialize_char(char)); write_primitive!(serialize_bytes(&[u8])); + #[inline] + fn serialize_char(self, value: char) -> Result { + self.into_simple_type_serializer().serialize_char(value)?; + Ok(WriteResult::SensitiveText) + } + #[inline] fn serialize_str(self, value: &str) -> Result { if !value.is_empty() { self.into_simple_type_serializer().serialize_str(value)?; } - Ok(()) + Ok(WriteResult::SensitiveText) } /// Does not write anything #[inline] fn serialize_none(self) -> Result { - Ok(()) + // Classify `None` as sensitive to whitespaces, because this can be `Option`. + // Unfortunately, we do not known what the type the option contains, so have no chance + // to adapt our behavior to it. The safe variant is assume sensitiviness + Ok(WriteResult::SensitiveNothing) } fn serialize_some(self, value: &T) -> Result { @@ -206,13 +220,13 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { /// Does not write anything #[inline] fn serialize_unit(self) -> Result { - Ok(()) + Ok(WriteResult::Nothing) } /// Does not write anything #[inline] fn serialize_unit_struct(self, _name: &'static str) -> Result { - Ok(()) + Ok(WriteResult::Nothing) } /// If `variant` is a special `$text` variant, then do nothing, otherwise @@ -224,7 +238,7 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { variant: &'static str, ) -> Result { if variant == TEXT_KEY { - Ok(()) + Ok(WriteResult::Nothing) } else { let name = XmlName::try_from(variant)?; self.write_empty(name) @@ -251,18 +265,24 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { ) -> Result { if variant == TEXT_KEY { value.serialize(self.into_simple_type_serializer())?; - Ok(()) + Ok(WriteResult::SensitiveText) } else { value.serialize(ElementSerializer { key: XmlName::try_from(variant)?, ser: self, - }) + })?; + Ok(WriteResult::Element) } } #[inline] fn serialize_seq(self, _len: Option) -> Result { - Ok(self) + Ok(Seq { + ser: self, + // If sequence if empty, nothing will be serialized. Because sequence can be of `Option`s + // we need to assume that writing indent may change the data and do not write anything + last: WriteResult::SensitiveNothing, + }) } #[inline] @@ -357,28 +377,38 @@ impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { } } -impl<'w, 'i, W: Write> SerializeSeq for ContentSerializer<'w, 'i, W> { - type Ok = (); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Helper struct which remembers the classification of the last serialized element +/// and reports it when the sequence ends +pub struct Seq<'w, 'k, W: Write> { + ser: ContentSerializer<'w, 'k, W>, + /// Classification of the result of the last serialized element. + last: WriteResult, +} + +impl<'w, 'i, W: Write> SerializeSeq for Seq<'w, 'i, W> { + type Ok = WriteResult; type Error = SeError; fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> where T: ?Sized + Serialize, { - value.serialize(self.new_seq_element_serializer())?; + self.last = value.serialize(self.ser.new_seq_element_serializer())?; // Write indent for next element - self.write_indent = true; + self.ser.write_indent = true; Ok(()) } #[inline] fn end(self) -> Result { - Ok(()) + Ok(self.last) } } -impl<'w, 'i, W: Write> SerializeTuple for ContentSerializer<'w, 'i, W> { - type Ok = (); +impl<'w, 'i, W: Write> SerializeTuple for Seq<'w, 'i, W> { + type Ok = WriteResult; type Error = SeError; #[inline] @@ -395,8 +425,8 @@ impl<'w, 'i, W: Write> SerializeTuple for ContentSerializer<'w, 'i, W> { } } -impl<'w, 'i, W: Write> SerializeTupleStruct for ContentSerializer<'w, 'i, W> { - type Ok = (); +impl<'w, 'i, W: Write> SerializeTupleStruct for Seq<'w, 'i, W> { + type Ok = WriteResult; type Error = SeError; #[inline] @@ -422,6 +452,7 @@ pub(super) mod tests { use crate::utils::Bytes; use serde::Serialize; use std::collections::BTreeMap; + use WriteResult::*; #[derive(Debug, Serialize, PartialEq)] pub struct Unit; @@ -539,6 +570,9 @@ pub(super) mod tests { /// Checks that given `$data` successfully serialized as `$expected` macro_rules! serialize_as { ($name:ident: $data:expr => $expected:expr) => { + serialize_as!($name: $data => $expected, WriteResult::Element); + }; + ($name:ident: $data:expr => $expected:expr, $result:expr) => { #[test] fn $name() { let mut buffer = String::new(); @@ -550,8 +584,9 @@ pub(super) mod tests { expand_empty_elements: false, }; - $data.serialize(ser).unwrap(); + let result = $data.serialize(ser).unwrap(); assert_eq!(buffer, $expected); + assert_eq!(result, $result); } }; } @@ -587,51 +622,51 @@ pub(super) mod tests { } // Primitives is serialized in the same way as for SimpleTypeSerializer - serialize_as!(false_: false => "false"); - serialize_as!(true_: true => "true"); + serialize_as!(false_: false => "false", Text); + serialize_as!(true_: true => "true", Text); - serialize_as!(i8_: -42i8 => "-42"); - serialize_as!(i16_: -4200i16 => "-4200"); - serialize_as!(i32_: -42000000i32 => "-42000000"); - serialize_as!(i64_: -42000000000000i64 => "-42000000000000"); - serialize_as!(isize_: -42000000000000isize => "-42000000000000"); + serialize_as!(i8_: -42i8 => "-42", Text); + serialize_as!(i16_: -4200i16 => "-4200", Text); + serialize_as!(i32_: -42000000i32 => "-42000000", Text); + serialize_as!(i64_: -42000000000000i64 => "-42000000000000", Text); + serialize_as!(isize_: -42000000000000isize => "-42000000000000", Text); - serialize_as!(u8_: 42u8 => "42"); - serialize_as!(u16_: 4200u16 => "4200"); - serialize_as!(u32_: 42000000u32 => "42000000"); - serialize_as!(u64_: 42000000000000u64 => "42000000000000"); - serialize_as!(usize_: 42000000000000usize => "42000000000000"); + serialize_as!(u8_: 42u8 => "42", Text); + serialize_as!(u16_: 4200u16 => "4200", Text); + serialize_as!(u32_: 42000000u32 => "42000000", Text); + serialize_as!(u64_: 42000000000000u64 => "42000000000000", Text); + serialize_as!(usize_: 42000000000000usize => "42000000000000", Text); serde_if_integer128! { - serialize_as!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); - serialize_as!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + serialize_as!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000", Text); + serialize_as!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000", Text); } - serialize_as!(f32_: 4.2f32 => "4.2"); - serialize_as!(f64_: 4.2f64 => "4.2"); + serialize_as!(f32_: 4.2f32 => "4.2", Text); + serialize_as!(f64_: 4.2f64 => "4.2", Text); - serialize_as!(char_non_escaped: 'h' => "h"); - serialize_as!(char_lt: '<' => "<"); - serialize_as!(char_gt: '>' => ">"); - serialize_as!(char_amp: '&' => "&"); - serialize_as!(char_apos: '\'' => "'"); - serialize_as!(char_quot: '"' => """); + serialize_as!(char_non_escaped: 'h' => "h", SensitiveText); + serialize_as!(char_lt: '<' => "<", SensitiveText); + serialize_as!(char_gt: '>' => ">", SensitiveText); + serialize_as!(char_amp: '&' => "&", SensitiveText); + serialize_as!(char_apos: '\'' => "'", SensitiveText); + serialize_as!(char_quot: '"' => """, SensitiveText); //TODO: add a setting to escape leading/trailing spaces, in order to // pretty-print does not change the content - serialize_as!(char_space: ' ' => " "); + serialize_as!(char_space: ' ' => " ", SensitiveText); - serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string"); - serialize_as!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string", SensitiveText); + serialize_as!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>", SensitiveText); err!(bytes: Bytes(b"<\"escaped & bytes'>") => Unsupported("`serialize_bytes` not supported yet")); - serialize_as!(option_none: Option::::None => ""); - serialize_as!(option_some: Some("non-escaped string") => "non-escaped string"); - serialize_as!(option_some_empty_str: Some("") => ""); + serialize_as!(option_none: Option::::None => "", SensitiveNothing); + serialize_as!(option_some: Some("non-escaped string") => "non-escaped string", SensitiveText); + serialize_as!(option_some_empty_str: Some("") => "", SensitiveText); - serialize_as!(unit: () => ""); - serialize_as!(unit_struct: Unit => ""); - serialize_as!(unit_struct_escaped: UnitEscaped => ""); + serialize_as!(unit: () => "", Nothing); + serialize_as!(unit_struct: Unit => "", Nothing); + serialize_as!(unit_struct_escaped: UnitEscaped => "", Nothing); // Unlike SimpleTypeSerializer, enumeration values serialized as tags serialize_as!(enum_unit: Enum::Unit => ""); @@ -639,19 +674,19 @@ pub(super) mod tests { => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); // Newtypes recursively applies ContentSerializer - serialize_as!(newtype: Newtype(42) => "42"); + serialize_as!(newtype: Newtype(42) => "42", Text); serialize_as!(enum_newtype: Enum::Newtype(42) => "42"); // Note that sequences of primitives serialized without delimiters! - serialize_as!(seq: vec![1, 2, 3] => "123"); - serialize_as!(seq_empty: Vec::::new() => ""); + serialize_as!(seq: vec![1, 2, 3] => "123", Text); + serialize_as!(seq_empty: Vec::::new() => "", SensitiveNothing); serialize_as!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) => "<"&'>\ with\t\r\n spaces\ - 3"); + 3", Text); serialize_as!(tuple_struct: Tuple("first", 42) => "first\ - 42"); + 42", Text); serialize_as!(enum_tuple: Enum::Tuple("first", 42) => "first\ 42"); @@ -979,6 +1014,9 @@ pub(super) mod tests { /// Checks that given `$data` successfully serialized as `$expected` macro_rules! serialize_as { ($name:ident: $data:expr => $expected:expr) => { + serialize_as!($name: $data => $expected, WriteResult::Element); + }; + ($name:ident: $data:expr => $expected:expr, $result:expr) => { #[test] fn $name() { let mut buffer = String::new(); @@ -990,8 +1028,9 @@ pub(super) mod tests { expand_empty_elements: false, }; - $data.serialize(ser).unwrap(); + let result = $data.serialize(ser).unwrap(); assert_eq!(buffer, $expected); + assert_eq!(result, $result); } }; } @@ -1026,50 +1065,50 @@ pub(super) mod tests { }; } - serialize_as!(false_: false => "false"); - serialize_as!(true_: true => "true"); + serialize_as!(false_: false => "false", Text); + serialize_as!(true_: true => "true", Text); - serialize_as!(i8_: -42i8 => "-42"); - serialize_as!(i16_: -4200i16 => "-4200"); - serialize_as!(i32_: -42000000i32 => "-42000000"); - serialize_as!(i64_: -42000000000000i64 => "-42000000000000"); - serialize_as!(isize_: -42000000000000isize => "-42000000000000"); + serialize_as!(i8_: -42i8 => "-42", Text); + serialize_as!(i16_: -4200i16 => "-4200", Text); + serialize_as!(i32_: -42000000i32 => "-42000000", Text); + serialize_as!(i64_: -42000000000000i64 => "-42000000000000", Text); + serialize_as!(isize_: -42000000000000isize => "-42000000000000", Text); - serialize_as!(u8_: 42u8 => "42"); - serialize_as!(u16_: 4200u16 => "4200"); - serialize_as!(u32_: 42000000u32 => "42000000"); - serialize_as!(u64_: 42000000000000u64 => "42000000000000"); - serialize_as!(usize_: 42000000000000usize => "42000000000000"); + serialize_as!(u8_: 42u8 => "42", Text); + serialize_as!(u16_: 4200u16 => "4200", Text); + serialize_as!(u32_: 42000000u32 => "42000000", Text); + serialize_as!(u64_: 42000000000000u64 => "42000000000000", Text); + serialize_as!(usize_: 42000000000000usize => "42000000000000", Text); serde_if_integer128! { - serialize_as!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); - serialize_as!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + serialize_as!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000", Text); + serialize_as!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000", Text); } - serialize_as!(f32_: 4.2f32 => "4.2"); - serialize_as!(f64_: 4.2f64 => "4.2"); + serialize_as!(f32_: 4.2f32 => "4.2", Text); + serialize_as!(f64_: 4.2f64 => "4.2", Text); - serialize_as!(char_non_escaped: 'h' => "h"); - serialize_as!(char_lt: '<' => "<"); - serialize_as!(char_gt: '>' => ">"); - serialize_as!(char_amp: '&' => "&"); - serialize_as!(char_apos: '\'' => "'"); - serialize_as!(char_quot: '"' => """); + serialize_as!(char_non_escaped: 'h' => "h", SensitiveText); + serialize_as!(char_lt: '<' => "<", SensitiveText); + serialize_as!(char_gt: '>' => ">", SensitiveText); + serialize_as!(char_amp: '&' => "&", SensitiveText); + serialize_as!(char_apos: '\'' => "'", SensitiveText); + serialize_as!(char_quot: '"' => """, SensitiveText); //TODO: add a setting to escape leading/trailing spaces, in order to // pretty-print does not change the content - serialize_as!(char_space: ' ' => " "); + serialize_as!(char_space: ' ' => " ", SensitiveText); - serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string"); - serialize_as!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string", SensitiveText); + serialize_as!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>", SensitiveText); err!(bytes: Bytes(b"<\"escaped & bytes'>") => Unsupported("`serialize_bytes` not supported yet")); - serialize_as!(option_none: Option::::None => ""); + serialize_as!(option_none: Option::::None => "", SensitiveNothing); serialize_as!(option_some: Some(Enum::Unit) => ""); - serialize_as!(unit: () => ""); - serialize_as!(unit_struct: Unit => ""); - serialize_as!(unit_struct_escaped: UnitEscaped => ""); + serialize_as!(unit: () => "", Nothing); + serialize_as!(unit_struct: Unit => "", Nothing); + serialize_as!(unit_struct_escaped: UnitEscaped => "", Nothing); // Unlike SimpleTypeSerializer, enumeration values serialized as tags serialize_as!(enum_unit: Enum::Unit => ""); @@ -1077,22 +1116,22 @@ pub(super) mod tests { => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); // Newtypes recursively applies ContentSerializer - serialize_as!(newtype: Newtype(42) => "42"); + serialize_as!(newtype: Newtype(42) => "42", Text); serialize_as!(enum_newtype: Enum::Newtype(42) => "42"); // Note that sequences of primitives serialized without delimiters other that indent! serialize_as!(seq: vec![1, 2, 3] => "1\n\ 2\n\ - 3"); - serialize_as!(seq_empty: Vec::::new() => ""); + 3", Text); + serialize_as!(seq_empty: Vec::::new() => "", SensitiveNothing); serialize_as!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) => "<"&'>\n\ with\t\r\n spaces\n\ - 3"); + 3", Text); serialize_as!(tuple_struct: Tuple("first", 42) => "first\n\ - 42"); + 42", Text); serialize_as!(enum_tuple: Enum::Tuple("first", 42) => "first\n\ 42"); diff --git a/src/se/element.rs b/src/se/element.rs index 4121817f..48a93707 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -5,7 +5,7 @@ use crate::se::content::ContentSerializer; use crate::se::key::QNameSerializer; use crate::se::simple_type::{QuoteTarget, SimpleSeq, SimpleTypeSerializer}; use crate::se::text::TextSerializer; -use crate::se::{Indent, SeError, XmlName}; +use crate::se::{Indent, SeError, WriteResult, XmlName}; use serde::ser::{ Impossible, Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, Serializer, @@ -28,6 +28,8 @@ macro_rules! write_primitive { /// this serializer never uses variant names of enum variants, and because of that /// it is unable to serialize any enum values, except unit variants. /// +/// Returns the classification of the last written type. +/// /// This serializer is used for an ordinary fields in structs, which are not special /// fields named `$text` ([`TEXT_KEY`]) or `$value` ([`VALUE_KEY`]). `$text` field /// should be serialized using [`SimpleTypeSerializer`] and `$value` field should be @@ -63,7 +65,7 @@ pub struct ElementSerializer<'w, 'k, W: Write> { } impl<'w, 'k, W: Write> Serializer for ElementSerializer<'w, 'k, W> { - type Ok = (); + type Ok = WriteResult; type Error = SeError; type SerializeSeq = Self; @@ -263,7 +265,7 @@ impl<'w, 'k, W: Write> Serializer for ElementSerializer<'w, 'k, W> { } impl<'w, 'k, W: Write> SerializeSeq for ElementSerializer<'w, 'k, W> { - type Ok = (); + type Ok = WriteResult; type Error = SeError; fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> @@ -281,12 +283,12 @@ impl<'w, 'k, W: Write> SerializeSeq for ElementSerializer<'w, 'k, W> { #[inline] fn end(self) -> Result { - Ok(()) + Ok(WriteResult::Element) } } impl<'w, 'k, W: Write> SerializeTuple for ElementSerializer<'w, 'k, W> { - type Ok = (); + type Ok = WriteResult; type Error = SeError; #[inline] @@ -304,7 +306,7 @@ impl<'w, 'k, W: Write> SerializeTuple for ElementSerializer<'w, 'k, W> { } impl<'w, 'k, W: Write> SerializeTupleStruct for ElementSerializer<'w, 'k, W> { - type Ok = (); + type Ok = WriteResult; type Error = SeError; #[inline] @@ -334,7 +336,7 @@ pub enum Tuple<'w, 'k, W: Write> { } impl<'w, 'k, W: Write> SerializeTupleVariant for Tuple<'w, 'k, W> { - type Ok = (); + type Ok = WriteResult; type Error = SeError; #[inline] @@ -352,7 +354,9 @@ impl<'w, 'k, W: Write> SerializeTupleVariant for Tuple<'w, 'k, W> { fn end(self) -> Result { match self { Self::Element(ser) => SerializeTuple::end(ser), - Self::Text(ser) => SerializeTuple::end(ser).map(|_| ()), + // Do not write indent after `$text` fields because it may be interpreted as + // part of content when deserialize + Self::Text(ser) => SerializeTuple::end(ser).map(|_| WriteResult::SensitiveText), } } } @@ -362,6 +366,8 @@ impl<'w, 'k, W: Write> SerializeTupleVariant for Tuple<'w, 'k, W> { /// A serializer for struct variants, which serializes the struct contents inside /// of wrapping tags (`<${tag}>...`). /// +/// Returns the classification of the last written type. +/// /// Serialization of each field depends on it representation: /// - attributes written directly to the higher serializer /// - elements buffered into internal buffer and at the end written into higher @@ -452,7 +458,7 @@ impl<'w, 'k, W: Write> Struct<'w, 'k, W> { } impl<'w, 'k, W: Write> SerializeStruct for Struct<'w, 'k, W> { - type Ok = (); + type Ok = WriteResult; type Error = SeError; fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> @@ -483,12 +489,12 @@ impl<'w, 'k, W: Write> SerializeStruct for Struct<'w, 'k, W> { self.ser.ser.writer.write_str(self.ser.key.0)?; self.ser.ser.writer.write_char('>')?; } - Ok(()) + Ok(WriteResult::Element) } } impl<'w, 'k, W: Write> SerializeStructVariant for Struct<'w, 'k, W> { - type Ok = (); + type Ok = WriteResult; type Error = SeError; #[inline] @@ -526,7 +532,7 @@ impl<'w, 'k, W: Write> Map<'w, 'k, W> { } impl<'w, 'k, W: Write> SerializeMap for Map<'w, 'k, W> { - type Ok = (); + type Ok = WriteResult; type Error = SeError; fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> @@ -623,8 +629,9 @@ mod tests { key: XmlName("root"), }; - $data.serialize(ser).unwrap(); + let result = $data.serialize(ser).unwrap(); assert_eq!(buffer, $expected); + assert_eq!(result, WriteResult::Element); } }; } @@ -1326,8 +1333,9 @@ mod tests { key: XmlName("root"), }; - $data.serialize(ser).unwrap(); + let result = $data.serialize(ser).unwrap(); assert_eq!(buffer, $expected); + assert_eq!(result, WriteResult::Element); } }; } @@ -2059,8 +2067,9 @@ mod tests { key: XmlName("root"), }; - $data.serialize(ser).unwrap(); + let result = $data.serialize(ser).unwrap(); assert_eq!(buffer, $expected); + assert_eq!(result, WriteResult::Element); } }; } diff --git a/src/se/mod.rs b/src/se/mod.rs index b8c09a09..3e84534d 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -91,6 +91,8 @@ use std::str::from_utf8; /// Serialize struct into a `Write`r. /// +/// Returns the classification of the last written type. +/// /// # Examples /// /// ``` @@ -124,7 +126,7 @@ use std::str::from_utf8; /// " /// ); /// ``` -pub fn to_writer(mut writer: W, value: &T) -> Result<(), SeError> +pub fn to_writer(mut writer: W, value: &T) -> Result where W: Write, T: ?Sized + Serialize, @@ -177,6 +179,8 @@ where /// Serialize struct into a `Write`r using specified root tag name. /// `root_tag` should be valid [XML name], otherwise error is returned. /// +/// Returns the classification of the last written type. +/// /// # Examples /// /// ``` @@ -210,7 +214,11 @@ where /// ``` /// /// [XML name]: https://www.w3.org/TR/xml11/#NT-Name -pub fn to_writer_with_root(mut writer: W, root_tag: &str, value: &T) -> Result<(), SeError> +pub fn to_writer_with_root( + mut writer: W, + root_tag: &str, + value: &T, +) -> Result where W: Write, T: ?Sized + Serialize, @@ -309,6 +317,26 @@ pub enum QuoteLevel { Minimal, } +/// Classification of the type written by the serializer. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WriteResult { + /// Text with insignificant spaces was written, for example a number. Adding indent to the + /// serialized data does not change meaning of the data. + Text, + /// The XML tag was written. Adding indent to the serialized data does not change meaning of the data. + Element, + /// Nothing was written (i. e. serialized type not represented in XML a all). Adding indent to the + /// serialized data does not change meaning of the data. This is returned for units, unit structs + /// and unit variants. + Nothing, + /// Text with significant spaces was written, for example a string. Adding indent to the + /// serialized data may change meaning of the data. + SensitiveText, + /// `None` was serialized and nothing was written. `None` does not represented in XML, + /// but adding indent after it may change meaning of the data. + SensitiveNothing, +} + //////////////////////////////////////////////////////////////////////////////////////////////////// /// Implements serialization method by forwarding it to the serializer created by @@ -444,7 +472,9 @@ impl<'i> Indent<'i> { //////////////////////////////////////////////////////////////////////////////////////////////////// -/// A Serializer +/// A Serializer. +/// +/// Returns the classification of the last written type. pub struct Serializer<'w, 'r, W: Write> { ser: ContentSerializer<'w, 'r, W>, /// Name of the root tag. If not specified, deduced from the structure name @@ -615,7 +645,7 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> { } impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { - type Ok = (); + type Ok = WriteResult; type Error = SeError; type SerializeSeq = ElementSerializer<'w, 'r, W>; @@ -651,7 +681,11 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { forward!(serialize_bytes(&[u8])); fn serialize_none(self) -> Result { - Ok(()) + // Do not write indent after `Option` field with `None` value, because + // this can be `Option`. Unfortunately, we do not known what the + // type the option contains, so have no chance to adapt our behavior to it. + // The safe variant is not to write indent + Ok(WriteResult::SensitiveNothing) } fn serialize_some(self, value: &T) -> Result { @@ -704,7 +738,9 @@ impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { ) -> Result { if variant == TEXT_KEY { value.serialize(self.ser.into_simple_type_serializer())?; - Ok(()) + // Do not write indent after `$text` variant because it may be interpreted as + // part of content when deserialize + Ok(WriteResult::SensitiveText) } else { let ser = ElementSerializer { ser: self.ser, From 65b9003ee73189d629941badaa22a6305d8dbe03 Mon Sep 17 00:00:00 2001 From: RedIODev Date: Tue, 1 Oct 2024 16:42:23 +0500 Subject: [PATCH 2/5] Change expectations about indentation - never indent text productions That means, never indent "$text" fields and do not indent "$value" fields that produces string without surrounding tags failures (208): se::content::tests::with_indent::enum_with_text_field::char_amp se::content::tests::with_indent::enum_with_text_field::char_apos se::content::tests::with_indent::enum_with_text_field::char_gt se::content::tests::with_indent::enum_with_text_field::char_lt se::content::tests::with_indent::enum_with_text_field::char_non_escaped se::content::tests::with_indent::enum_with_text_field::char_quot se::content::tests::with_indent::enum_with_text_field::char_space se::content::tests::with_indent::enum_with_text_field::enum_unit se::content::tests::with_indent::enum_with_text_field::enum_unit_escaped se::content::tests::with_indent::enum_with_text_field::f32_ se::content::tests::with_indent::enum_with_text_field::f64_ se::content::tests::with_indent::enum_with_text_field::false_ se::content::tests::with_indent::enum_with_text_field::i128_ se::content::tests::with_indent::enum_with_text_field::i16_ se::content::tests::with_indent::enum_with_text_field::i32_ se::content::tests::with_indent::enum_with_text_field::i64_ se::content::tests::with_indent::enum_with_text_field::i8_ se::content::tests::with_indent::enum_with_text_field::isize_ se::content::tests::with_indent::enum_with_text_field::newtype se::content::tests::with_indent::enum_with_text_field::option_none se::content::tests::with_indent::enum_with_text_field::option_some se::content::tests::with_indent::enum_with_text_field::option_some_empty_str se::content::tests::with_indent::enum_with_text_field::seq se::content::tests::with_indent::enum_with_text_field::seq_empty se::content::tests::with_indent::enum_with_text_field::str_escaped se::content::tests::with_indent::enum_with_text_field::str_non_escaped se::content::tests::with_indent::enum_with_text_field::true_ se::content::tests::with_indent::enum_with_text_field::tuple se::content::tests::with_indent::enum_with_text_field::tuple_struct se::content::tests::with_indent::enum_with_text_field::u128_ se::content::tests::with_indent::enum_with_text_field::u16_ se::content::tests::with_indent::enum_with_text_field::u32_ se::content::tests::with_indent::enum_with_text_field::u64_ se::content::tests::with_indent::enum_with_text_field::u8_ se::content::tests::with_indent::enum_with_text_field::unit se::content::tests::with_indent::enum_with_text_field::unit_struct se::content::tests::with_indent::enum_with_text_field::unit_struct_escaped se::content::tests::with_indent::enum_with_text_field::usize_ se::content::tests::with_indent::enum_with_value_field::char_amp se::content::tests::with_indent::enum_with_value_field::char_apos se::content::tests::with_indent::enum_with_value_field::char_gt se::content::tests::with_indent::enum_with_value_field::char_lt se::content::tests::with_indent::enum_with_value_field::char_non_escaped se::content::tests::with_indent::enum_with_value_field::char_quot se::content::tests::with_indent::enum_with_value_field::char_space se::content::tests::with_indent::enum_with_value_field::f32_ se::content::tests::with_indent::enum_with_value_field::f64_ se::content::tests::with_indent::enum_with_value_field::false_ se::content::tests::with_indent::enum_with_value_field::i128_ se::content::tests::with_indent::enum_with_value_field::i16_ se::content::tests::with_indent::enum_with_value_field::i32_ se::content::tests::with_indent::enum_with_value_field::i64_ se::content::tests::with_indent::enum_with_value_field::i8_ se::content::tests::with_indent::enum_with_value_field::isize_ se::content::tests::with_indent::enum_with_value_field::newtype se::content::tests::with_indent::enum_with_value_field::option_none se::content::tests::with_indent::enum_with_value_field::option_some se::content::tests::with_indent::enum_with_value_field::option_some_empty_str se::content::tests::with_indent::enum_with_value_field::seq se::content::tests::with_indent::enum_with_value_field::seq_empty se::content::tests::with_indent::enum_with_value_field::str_escaped se::content::tests::with_indent::enum_with_value_field::str_non_escaped se::content::tests::with_indent::enum_with_value_field::true_ se::content::tests::with_indent::enum_with_value_field::tuple se::content::tests::with_indent::enum_with_value_field::tuple_struct se::content::tests::with_indent::enum_with_value_field::u128_ se::content::tests::with_indent::enum_with_value_field::u16_ se::content::tests::with_indent::enum_with_value_field::u32_ se::content::tests::with_indent::enum_with_value_field::u64_ se::content::tests::with_indent::enum_with_value_field::u8_ se::content::tests::with_indent::enum_with_value_field::usize_ se::content::tests::with_indent::seq se::content::tests::with_indent::text_field::enum_struct se::content::tests::with_indent::tuple se::content::tests::with_indent::tuple_struct se::element::tests::with_indent::text_field::map::char_amp se::element::tests::with_indent::text_field::map::char_apos se::element::tests::with_indent::text_field::map::char_gt se::element::tests::with_indent::text_field::map::char_lt se::element::tests::with_indent::text_field::map::char_non_escaped se::element::tests::with_indent::text_field::map::char_quot se::element::tests::with_indent::text_field::map::char_space se::element::tests::with_indent::text_field::map::enum_unit se::element::tests::with_indent::text_field::map::enum_unit_escaped se::element::tests::with_indent::text_field::map::f32_ se::element::tests::with_indent::text_field::map::f64_ se::element::tests::with_indent::text_field::map::false_ se::element::tests::with_indent::text_field::map::i128_ se::element::tests::with_indent::text_field::map::i16_ se::element::tests::with_indent::text_field::map::i32_ se::element::tests::with_indent::text_field::map::i64_ se::element::tests::with_indent::text_field::map::i8_ se::element::tests::with_indent::text_field::map::isize_ se::element::tests::with_indent::text_field::map::newtype se::element::tests::with_indent::text_field::map::option_some se::element::tests::with_indent::text_field::map::seq se::element::tests::with_indent::text_field::map::str_escaped se::element::tests::with_indent::text_field::map::str_non_escaped se::element::tests::with_indent::text_field::map::true_ se::element::tests::with_indent::text_field::map::tuple se::element::tests::with_indent::text_field::map::tuple_struct se::element::tests::with_indent::text_field::map::u128_ se::element::tests::with_indent::text_field::map::u16_ se::element::tests::with_indent::text_field::map::u32_ se::element::tests::with_indent::text_field::map::u64_ se::element::tests::with_indent::text_field::map::u8_ se::element::tests::with_indent::text_field::map::usize_ se::element::tests::with_indent::text_field::struct_::char_amp se::element::tests::with_indent::text_field::struct_::char_apos se::element::tests::with_indent::text_field::struct_::char_gt se::element::tests::with_indent::text_field::struct_::char_lt se::element::tests::with_indent::text_field::struct_::char_non_escaped se::element::tests::with_indent::text_field::struct_::char_quot se::element::tests::with_indent::text_field::struct_::char_space se::element::tests::with_indent::text_field::struct_::enum_unit se::element::tests::with_indent::text_field::struct_::enum_unit_escaped se::element::tests::with_indent::text_field::struct_::f32_ se::element::tests::with_indent::text_field::struct_::f64_ se::element::tests::with_indent::text_field::struct_::false_ se::element::tests::with_indent::text_field::struct_::i128_ se::element::tests::with_indent::text_field::struct_::i16_ se::element::tests::with_indent::text_field::struct_::i32_ se::element::tests::with_indent::text_field::struct_::i64_ se::element::tests::with_indent::text_field::struct_::i8_ se::element::tests::with_indent::text_field::struct_::isize_ se::element::tests::with_indent::text_field::struct_::newtype se::element::tests::with_indent::text_field::struct_::option_none se::element::tests::with_indent::text_field::struct_::option_some se::element::tests::with_indent::text_field::struct_::option_some_empty_str se::element::tests::with_indent::text_field::struct_::seq se::element::tests::with_indent::text_field::struct_::seq_empty se::element::tests::with_indent::text_field::struct_::str_escaped se::element::tests::with_indent::text_field::struct_::str_non_escaped se::element::tests::with_indent::text_field::struct_::true_ se::element::tests::with_indent::text_field::struct_::tuple se::element::tests::with_indent::text_field::struct_::tuple_struct se::element::tests::with_indent::text_field::struct_::u128_ se::element::tests::with_indent::text_field::struct_::u16_ se::element::tests::with_indent::text_field::struct_::u32_ se::element::tests::with_indent::text_field::struct_::u64_ se::element::tests::with_indent::text_field::struct_::u8_ se::element::tests::with_indent::text_field::struct_::unit se::element::tests::with_indent::text_field::struct_::unit_struct se::element::tests::with_indent::text_field::struct_::unit_struct_escaped se::element::tests::with_indent::text_field::struct_::usize_ se::element::tests::with_indent::value_field::map::char_amp se::element::tests::with_indent::value_field::map::char_apos se::element::tests::with_indent::value_field::map::char_gt se::element::tests::with_indent::value_field::map::char_lt se::element::tests::with_indent::value_field::map::char_non_escaped se::element::tests::with_indent::value_field::map::char_quot se::element::tests::with_indent::value_field::map::char_space se::element::tests::with_indent::value_field::map::f32_ se::element::tests::with_indent::value_field::map::f64_ se::element::tests::with_indent::value_field::map::false_ se::element::tests::with_indent::value_field::map::i128_ se::element::tests::with_indent::value_field::map::i16_ se::element::tests::with_indent::value_field::map::i32_ se::element::tests::with_indent::value_field::map::i64_ se::element::tests::with_indent::value_field::map::i8_ se::element::tests::with_indent::value_field::map::isize_ se::element::tests::with_indent::value_field::map::newtype se::element::tests::with_indent::value_field::map::option_some se::element::tests::with_indent::value_field::map::seq se::element::tests::with_indent::value_field::map::str_escaped se::element::tests::with_indent::value_field::map::str_non_escaped se::element::tests::with_indent::value_field::map::true_ se::element::tests::with_indent::value_field::map::tuple se::element::tests::with_indent::value_field::map::tuple_struct se::element::tests::with_indent::value_field::map::u128_ se::element::tests::with_indent::value_field::map::u16_ se::element::tests::with_indent::value_field::map::u32_ se::element::tests::with_indent::value_field::map::u64_ se::element::tests::with_indent::value_field::map::u8_ se::element::tests::with_indent::value_field::map::usize_ se::element::tests::with_indent::value_field::struct_::char_amp se::element::tests::with_indent::value_field::struct_::char_apos se::element::tests::with_indent::value_field::struct_::char_gt se::element::tests::with_indent::value_field::struct_::char_lt se::element::tests::with_indent::value_field::struct_::char_non_escaped se::element::tests::with_indent::value_field::struct_::char_quot se::element::tests::with_indent::value_field::struct_::char_space se::element::tests::with_indent::value_field::struct_::f32_ se::element::tests::with_indent::value_field::struct_::f64_ se::element::tests::with_indent::value_field::struct_::false_ se::element::tests::with_indent::value_field::struct_::i128_ se::element::tests::with_indent::value_field::struct_::i16_ se::element::tests::with_indent::value_field::struct_::i32_ se::element::tests::with_indent::value_field::struct_::i64_ se::element::tests::with_indent::value_field::struct_::i8_ se::element::tests::with_indent::value_field::struct_::isize_ se::element::tests::with_indent::value_field::struct_::newtype se::element::tests::with_indent::value_field::struct_::option_none se::element::tests::with_indent::value_field::struct_::option_some se::element::tests::with_indent::value_field::struct_::option_some_empty_str se::element::tests::with_indent::value_field::struct_::seq se::element::tests::with_indent::value_field::struct_::seq_empty se::element::tests::with_indent::value_field::struct_::str_escaped se::element::tests::with_indent::value_field::struct_::str_non_escaped se::element::tests::with_indent::value_field::struct_::true_ se::element::tests::with_indent::value_field::struct_::tuple se::element::tests::with_indent::value_field::struct_::tuple_struct se::element::tests::with_indent::value_field::struct_::u128_ se::element::tests::with_indent::value_field::struct_::u16_ se::element::tests::with_indent::value_field::struct_::u32_ se::element::tests::with_indent::value_field::struct_::u64_ se::element::tests::with_indent::value_field::struct_::u8_ se::element::tests::with_indent::value_field::struct_::usize_ --- src/se/content.rs | 109 ++++++++++-------------------- src/se/element.rs | 131 ++++++++++++++---------------------- tests/serde-se.rs | 20 ++---- tests/writer-indentation.rs | 4 +- 4 files changed, 90 insertions(+), 174 deletions(-) diff --git a/src/se/content.rs b/src/se/content.rs index 30c7414f..e814f5b9 100644 --- a/src/se/content.rs +++ b/src/se/content.rs @@ -651,8 +651,6 @@ pub(super) mod tests { serialize_as!(char_amp: '&' => "&", SensitiveText); serialize_as!(char_apos: '\'' => "'", SensitiveText); serialize_as!(char_quot: '"' => """, SensitiveText); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content serialize_as!(char_space: ' ' => " ", SensitiveText); serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string", SensitiveText); @@ -781,8 +779,6 @@ pub(super) mod tests { text!(char_amp: '&' => "&"); text!(char_apos: '\'' => "'"); text!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content text!(char_space: ' ' => " "); text!(str_non_escaped: "non-escaped string" => "non-escaped string"); @@ -908,8 +904,6 @@ pub(super) mod tests { value!(char_amp: '&' => "&"); value!(char_apos: '\'' => "'"); value!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content value!(char_space: ' ' => " "); value!(str_non_escaped: "non-escaped string" => "non-escaped string"); @@ -1094,8 +1088,6 @@ pub(super) mod tests { serialize_as!(char_amp: '&' => "&", SensitiveText); serialize_as!(char_apos: '\'' => "'", SensitiveText); serialize_as!(char_quot: '"' => """, SensitiveText); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content serialize_as!(char_space: ' ' => " ", SensitiveText); serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string", SensitiveText); @@ -1119,19 +1111,14 @@ pub(super) mod tests { serialize_as!(newtype: Newtype(42) => "42", Text); serialize_as!(enum_newtype: Enum::Newtype(42) => "42"); - // Note that sequences of primitives serialized without delimiters other that indent! - serialize_as!(seq: vec![1, 2, 3] - => "1\n\ - 2\n\ - 3", Text); + // Note that sequences of primitives serialized without delimiters! + serialize_as!(seq: vec![1, 2, 3] => "123", Text); serialize_as!(seq_empty: Vec::::new() => "", SensitiveNothing); serialize_as!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) - => "<"&'>\n\ - with\t\r\n spaces\n\ + => "<"&'>\ + with\t\r\n spaces\ 3", Text); - serialize_as!(tuple_struct: Tuple("first", 42) - => "first\n\ - 42", Text); + serialize_as!(tuple_struct: Tuple("first", 42) => "first42", Text); serialize_as!(enum_tuple: Enum::Tuple("first", 42) => "first\n\ 42"); @@ -1170,9 +1157,7 @@ pub(super) mod tests { after: "answer", } => "\n \ - answer\n \ - 42 42\n \ - answer\n\ + answer42 42answer\n\ "); } @@ -1182,18 +1167,6 @@ pub(super) mod tests { use pretty_assertions::assert_eq; macro_rules! text { - ($name:ident: $data:expr) => { - serialize_as!($name: - SpecialEnum::Text { - before: "answer", - content: $data, - after: "answer", - } - => "\n \ - answer\n \ - answer\n\ - "); - }; ($name:ident: $data:expr => $expected:literal) => { serialize_as!($name: SpecialEnum::Text { @@ -1202,9 +1175,9 @@ pub(super) mod tests { after: "answer", } => concat!( - "\n answer\n ", + "\n answer", $expected, - "\n answer\n", + "answer\n", )); }; } @@ -1238,8 +1211,6 @@ pub(super) mod tests { text!(char_amp: '&' => "&"); text!(char_apos: '\'' => "'"); text!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content text!(char_space: ' ' => " "); text!(str_non_escaped: "non-escaped string" => "non-escaped string"); @@ -1253,13 +1224,13 @@ pub(super) mod tests { } => Unsupported("`serialize_bytes` not supported yet")); - text!(option_none: Option::<&str>::None); + text!(option_none: Option::<&str>::None => ""); text!(option_some: Some("non-escaped string") => "non-escaped string"); - text!(option_some_empty_str: Some("")); + text!(option_some_empty_str: Some("") => ""); - text!(unit: ()); - text!(unit_struct: Unit); - text!(unit_struct_escaped: UnitEscaped); + text!(unit: () => ""); + text!(unit_struct: Unit => ""); + text!(unit_struct_escaped: UnitEscaped => ""); text!(enum_unit: Enum::Unit => "Unit"); text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); @@ -1276,7 +1247,7 @@ pub(super) mod tests { // Sequences are serialized separated by spaces, all spaces inside are escaped text!(seq: vec![1, 2, 3] => "1 2 3"); - text!(seq_empty: Vec::::new()); + text!(seq_empty: Vec::::new() => ""); text!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) => "<"&'> \ with spaces \ @@ -1321,18 +1292,6 @@ pub(super) mod tests { use pretty_assertions::assert_eq; macro_rules! value { - ($name:ident: $data:expr) => { - serialize_as!($name: - SpecialEnum::Value { - before: "answer", - content: $data, - after: "answer", - } - => "\n \ - answer\n \ - answer\n\ - "); - }; ($name:ident: $data:expr => $expected:literal) => { serialize_as!($name: SpecialEnum::Value { @@ -1341,9 +1300,9 @@ pub(super) mod tests { after: "answer", } => concat!( - "\n answer\n ", + "\n answer", $expected, - "\n answer\n", + "answer\n", )); }; } @@ -1377,8 +1336,6 @@ pub(super) mod tests { value!(char_amp: '&' => "&"); value!(char_apos: '\'' => "'"); value!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content value!(char_space: ' ' => " "); value!(str_non_escaped: "non-escaped string" => "non-escaped string"); @@ -1392,15 +1349,15 @@ pub(super) mod tests { } => Unsupported("`serialize_bytes` not supported yet")); - value!(option_none: Option::<&str>::None); + value!(option_none: Option::<&str>::None => ""); value!(option_some: Some("non-escaped string") => "non-escaped string"); - value!(option_some_empty_str: Some("")); + value!(option_some_empty_str: Some("") => ""); - value!(unit: ()); - value!(unit_struct: Unit); - value!(unit_struct_escaped: UnitEscaped); + value!(unit: () => "\n "); + value!(unit_struct: Unit => "\n "); + value!(unit_struct_escaped: UnitEscaped => "\n "); - value!(enum_unit: Enum::Unit => ""); + value!(enum_unit: Enum::Unit => "\n \n "); err!(enum_unit_escaped: SpecialEnum::Value { before: "answer", @@ -1410,19 +1367,20 @@ pub(super) mod tests { => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); value!(newtype: Newtype(42) => "42"); - value!(enum_newtype: Enum::Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "\n 42\n "); // Note that sequences of primitives serialized without delimiters! - value!(seq: vec![1, 2, 3] => "1\n 2\n 3"); - value!(seq_empty: Vec::::new()); + value!(seq: vec![1, 2, 3] => "123"); + value!(seq_empty: Vec::::new() => ""); value!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) - => "<"&'>\n \ - with\t\n\r spaces\n \ + => "<"&'>\ + with\t\n\r spaces\ 3"); - value!(tuple_struct: Tuple("first", 42) => "first\n 42"); + value!(tuple_struct: Tuple("first", 42) => "first42"); value!(enum_tuple: Enum::Tuple("first", 42) - => "first\n \ - 42"); + => "\n \ + first\n \ + 42\n "); // We cannot wrap map or struct in any container and should not // flatten it, so it is impossible to serialize maps and structs @@ -1442,11 +1400,12 @@ pub(super) mod tests { => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); value!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } - => "\n \ + => "\n \ + \n \ answer\n \ 42\n \ 42\n \ - "); + \n "); } mod attributes { diff --git a/src/se/element.rs b/src/se/element.rs index 48a93707..10a62389 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -800,8 +800,6 @@ mod tests { text!(char_amp: '&' => "&"); text!(char_apos: '\'' => "'"); text!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content text!(char_space: ' ' => " "); text!(str_non_escaped: "non-escaped string" => "non-escaped string"); @@ -927,8 +925,6 @@ mod tests { text!(char_amp: '&' => "&"); text!(char_apos: '\'' => "'"); text!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content text!(char_space: ' ' => " "); text!(str_non_escaped: "non-escaped string" => "non-escaped string"); @@ -1058,8 +1054,6 @@ mod tests { value!(char_amp: '&' => "&"); value!(char_apos: '\'' => "'"); value!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content value!(char_space: ' ' => " "); value!(str_non_escaped: "non-escaped string" => "non-escaped string"); @@ -1164,8 +1158,6 @@ mod tests { value!(char_amp: '&' => "&"); value!(char_apos: '\'' => "'"); value!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content value!(char_space: ' ' => " "); value!(str_non_escaped: "non-escaped string" => "non-escaped string"); @@ -1316,7 +1308,8 @@ mod tests { use crate::writer::Indentation; use pretty_assertions::assert_eq; - /// Checks that given `$data` successfully serialized as `$expected` + /// Checks that given `$data` successfully serialized as `$expected`. + /// Writes `$data` using [`ElementSerializer`] with indent of two spaces. macro_rules! serialize_as { ($name:ident: $data:expr => $expected:expr) => { #[test] @@ -1402,8 +1395,6 @@ mod tests { serialize_as!(char_amp: '&' => "&"); serialize_as!(char_apos: '\'' => "'"); serialize_as!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content serialize_as!(char_space: ' ' => " "); serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string"); @@ -1468,13 +1459,15 @@ mod tests { macro_rules! text { ($name:ident: $data:expr) => { serialize_as!($name: + // Serialization started from ElementSerializer::serialize_map BTreeMap::from([("$text", $data)]) => ""); }; ($name:ident: $data:expr => $expected:literal) => { serialize_as!($name: + // Serialization started from ElementSerializer::serialize_map BTreeMap::from([("$text", $data)]) - => concat!("\n ", $expected,"\n")); + => concat!("", $expected,"")); }; } @@ -1507,8 +1500,6 @@ mod tests { text!(char_amp: '&' => "&"); text!(char_apos: '\'' => "'"); text!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content text!(char_space: ' ' => " "); text!(str_non_escaped: "non-escaped string" => "non-escaped string"); @@ -1590,29 +1581,18 @@ mod tests { use pretty_assertions::assert_eq; macro_rules! text { - ($name:ident: $data:expr) => { - serialize_as!($name: - Text { - before: "answer", - content: $data, - after: "answer", - } - => "\n \ - answer\n \ - answer\n\ - "); - }; ($name:ident: $data:expr => $expected:literal) => { serialize_as!($name: + // Serialization started from ElementSerializer::serialize_struct Text { before: "answer", content: $data, after: "answer", } => concat!( - "\n answer\n ", + "\n answer", $expected, - "\n answer\n", + "answer\n", )); }; } @@ -1646,8 +1626,6 @@ mod tests { text!(char_amp: '&' => "&"); text!(char_apos: '\'' => "'"); text!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content text!(char_space: ' ' => " "); text!(str_non_escaped: "non-escaped string" => "non-escaped string"); @@ -1661,13 +1639,13 @@ mod tests { } => Unsupported("`serialize_bytes` not supported yet")); - text!(option_none: Option::<&str>::None); + text!(option_none: Option::<&str>::None => ""); text!(option_some: Some("non-escaped string") => "non-escaped string"); - text!(option_some_empty_str: Some("")); + text!(option_some_empty_str: Some("") => ""); - text!(unit: ()); - text!(unit_struct: Unit); - text!(unit_struct_escaped: UnitEscaped); + text!(unit: () => ""); + text!(unit_struct: Unit => ""); + text!(unit_struct_escaped: UnitEscaped => ""); text!(enum_unit: Enum::Unit => "Unit"); text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); @@ -1684,7 +1662,7 @@ mod tests { // Sequences are serialized separated by spaces, all spaces inside are escaped text!(seq: vec![1, 2, 3] => "1 2 3"); - text!(seq_empty: Vec::::new()); + text!(seq_empty: Vec::::new() => ""); text!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) => "<"&'> \ with spaces \ @@ -1738,13 +1716,15 @@ mod tests { macro_rules! value { ($name:ident: $data:expr) => { serialize_as!($name: + // Serialization started from ElementSerializer::serialize_map BTreeMap::from([("$value", $data)]) => ""); }; ($name:ident: $data:expr => $expected:literal) => { serialize_as!($name: + // Serialization started from ElementSerializer::serialize_map BTreeMap::from([("$value", $data)]) - => concat!("\n ", $expected,"\n")); + => concat!("", $expected,"")); }; } @@ -1777,8 +1757,6 @@ mod tests { value!(char_amp: '&' => "&"); value!(char_apos: '\'' => "'"); value!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content value!(char_space: ' ' => " "); value!(str_non_escaped: "non-escaped string" => "non-escaped string"); @@ -1796,24 +1774,25 @@ mod tests { value!(unit_struct: Unit); value!(unit_struct_escaped: UnitEscaped); - value!(enum_unit: Enum::Unit => ""); + value!(enum_unit: Enum::Unit => "\n \n"); err!(enum_unit_escaped: BTreeMap::from([("$value", Enum::UnitEscaped)]) => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); value!(newtype: Newtype(42) => "42"); - value!(enum_newtype: Enum::Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "\n 42\n"); - value!(seq: vec![1, 2, 3] => "1\n 2\n 3"); + value!(seq: vec![1, 2, 3] => "123"); value!(seq_empty: Vec::::new()); value!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) - => "<"&'>\n \ - with\t\n\r spaces\n \ + => "<"&'>\ + with\t\n\r spaces\ 3"); - value!(tuple_struct: Tuple("first", 42) => "first\n 42"); + value!(tuple_struct: Tuple("first", 42) => "first42"); value!(enum_tuple: Enum::Tuple("first", 42) - => "first\n \ - 42"); + => "\n \ + first\n \ + 42\n"); // We cannot wrap map or struct in any container and should not // flatten it, so it is impossible to serialize maps and structs @@ -1825,11 +1804,12 @@ mod tests { => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); value!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } - => "\n \ + => "\n \ + \n \ answer\n \ 42\n \ 42\n \ - "); + \n"); } /// `$value` field inside a struct @@ -1838,29 +1818,18 @@ mod tests { use pretty_assertions::assert_eq; macro_rules! value { - ($name:ident: $data:expr) => { - serialize_as!($name: - Value { - before: "answer", - content: $data, - after: "answer", - } - => "\n \ - answer\n \ - answer\n\ - "); - }; ($name:ident: $data:expr => $expected:literal) => { serialize_as!($name: + // Serialization started from ElementSerializer::serialize_struct Value { before: "answer", content: $data, after: "answer", } => concat!( - "\n answer\n ", + "\n answer", $expected, - "\n answer\n", + "answer\n", )); }; } @@ -1894,8 +1863,6 @@ mod tests { value!(char_amp: '&' => "&"); value!(char_apos: '\'' => "'"); value!(char_quot: '"' => """); - //TODO: add a setting to escape leading/trailing spaces, in order to - // pretty-print does not change the content value!(char_space: ' ' => " "); value!(str_non_escaped: "non-escaped string" => "non-escaped string"); @@ -1909,15 +1876,15 @@ mod tests { } => Unsupported("`serialize_bytes` not supported yet")); - value!(option_none: Option::<&str>::None); + value!(option_none: Option::<&str>::None => ""); value!(option_some: Some("non-escaped string") => "non-escaped string"); - value!(option_some_empty_str: Some("")); + value!(option_some_empty_str: Some("") => ""); - value!(unit: ()); - value!(unit_struct: Unit); - value!(unit_struct_escaped: UnitEscaped); + value!(unit: () => "\n "); + value!(unit_struct: Unit => "\n "); + value!(unit_struct_escaped: UnitEscaped => "\n "); - value!(enum_unit: Enum::Unit => ""); + value!(enum_unit: Enum::Unit => "\n \n "); err!(enum_unit_escaped: Value { before: "answer", @@ -1927,19 +1894,20 @@ mod tests { => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); value!(newtype: Newtype(42) => "42"); - value!(enum_newtype: Enum::Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "\n 42\n "); // Note that sequences of primitives serialized without delimiters! - value!(seq: vec![1, 2, 3] => "1\n 2\n 3"); - value!(seq_empty: Vec::::new()); + value!(seq: vec![1, 2, 3] => "123"); + value!(seq_empty: Vec::::new() => ""); value!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) - => "<"&'>\n \ - with\t\n\r spaces\n \ + => "<"&'>\ + with\t\n\r spaces\ 3"); - value!(tuple_struct: Tuple("first", 42) => "first\n 42"); + value!(tuple_struct: Tuple("first", 42) => "first42"); value!(enum_tuple: Enum::Tuple("first", 42) - => "first\n \ - 42"); + => "\n \ + first\n \ + 42\n "); // We cannot wrap map or struct in any container and should not // flatten it, so it is impossible to serialize maps and structs @@ -1959,11 +1927,12 @@ mod tests { => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); value!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } - => "\n \ + => "\n \ + \n \ answer\n \ 42\n \ 42\n \ - "); + \n "); } } diff --git a/tests/serde-se.rs b/tests/serde-se.rs index fc560628..ec73b4ee 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -1380,9 +1380,7 @@ mod without_root { float: 42.0, string: "answer" } - => "\n \ - 42\n \ - answer\n\ + => "42answer\n\ "); mod enum_ { @@ -1439,9 +1437,7 @@ mod without_root { float: 42.0, string: "answer" } - => "\n \ - 42\n \ - answer\n\ + => "42answer\n\ "); /// Test serialization of the specially named variant `$text` @@ -1544,9 +1540,7 @@ mod without_root { string: "answer" } => "\n \ - Text\n \ - 42\n \ - answer\n\ + Text42answer\n\ "); } @@ -1624,9 +1618,7 @@ mod without_root { } => "\n \ Text\n \ - \n \ - 42\n \ - answer\n \ + 42answer\n \ \n\ "); } @@ -1676,9 +1668,7 @@ mod without_root { float: 42.0, string: "answer" } - => "\n \ - 42\n \ - answer\n\ + => "42answer\n\ "); } } diff --git a/tests/writer-indentation.rs b/tests/writer-indentation.rs index f96f6063..baabd967 100644 --- a/tests/writer-indentation.rs +++ b/tests/writer-indentation.rs @@ -222,9 +222,7 @@ fn serializable() { 43 first element - second element - text - foo + second elementtextfoo "# ); From bbef94a39f7c528d7a39d2b4693521bc0c01f50d Mon Sep 17 00:00:00 2001 From: Mingun Date: Tue, 8 Oct 2024 00:33:32 +0500 Subject: [PATCH 3/5] Do not write indent before and after `$text` fields Fixes all 208 tests --- Changelog.md | 4 ++++ src/se/content.rs | 12 ++++-------- src/se/element.rs | 17 ++++++++++++++--- src/se/mod.rs | 8 ++++++++ 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Changelog.md b/Changelog.md index 8d71aeb6..a0bfeeeb 100644 --- a/Changelog.md +++ b/Changelog.md @@ -17,6 +17,9 @@ ### Bug Fixes +- [#655]: Do not write indent before and after `$text` fields and those `$value` fields + that are serialized as a text (for example, `usize` or `String`). + ### Misc Changes - [#227]: Split `SeError` from `DeError` in the `serialize` feature. @@ -29,6 +32,7 @@ - [#820]: Classify output of the `Serializer` by returning an enumeration with kind of written data [#227]: https://github.com/tafia/quick-xml/issues/227 +[#655]: https://github.com/tafia/quick-xml/issues/655 [#810]: https://github.com/tafia/quick-xml/pull/810 [#811]: https://github.com/tafia/quick-xml/pull/811 [#820]: https://github.com/tafia/quick-xml/pull/820 diff --git a/src/se/content.rs b/src/se/content.rs index e814f5b9..6c867426 100644 --- a/src/se/content.rs +++ b/src/se/content.rs @@ -69,7 +69,7 @@ pub struct ContentSerializer<'w, 'i, W: Write> { /// child serializers should have access to the actual state of indentation. pub(super) indent: Indent<'i>, /// If `true`, then current indent will be written before writing the content, - /// but only if content is not empty. + /// but only if content is not empty. This flag is reset after writing indent. pub write_indent: bool, // If `true`, then empty elements will be serialized as `` // instead of ``. @@ -86,11 +86,7 @@ impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> { writer: self.writer, target: QuoteTarget::Text, level: self.level, - indent: if self.write_indent { - self.indent - } else { - Indent::None - }, + indent: Indent::None, } } @@ -396,8 +392,8 @@ impl<'w, 'i, W: Write> SerializeSeq for Seq<'w, 'i, W> { T: ?Sized + Serialize, { self.last = value.serialize(self.ser.new_seq_element_serializer())?; - // Write indent for next element - self.ser.write_indent = true; + // Write indent for next element if indents are used + self.ser.write_indent = self.last.allow_indent(); Ok(()) } diff --git a/src/se/element.rs b/src/se/element.rs index 10a62389..1ea17194 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -241,6 +241,7 @@ impl<'w, 'k, W: Write> Serializer for ElementSerializer<'w, 'k, W> { Ok(Struct { ser: self, children: String::new(), + write_indent: true, }) } @@ -379,6 +380,8 @@ pub struct Struct<'w, 'k, W: Write> { // attributes should be listed first. Fail, if attribute encountered after // element. Use feature to configure children: String, + /// Whether need to write indent after the last written field + write_indent: bool, } impl<'w, 'k, W: Write> Struct<'w, 'k, W> { @@ -439,19 +442,25 @@ impl<'w, 'k, W: Write> Struct<'w, 'k, W> { writer: &mut self.children, level: self.ser.ser.level, indent: self.ser.ser.indent.borrow(), - write_indent: true, + // If previous field does not require indent, do not write it + write_indent: self.write_indent, expand_empty_elements: self.ser.ser.expand_empty_elements, }; if key == TEXT_KEY { value.serialize(TextSerializer(ser.into_simple_type_serializer()))?; + // Text was written so we don't need to indent next field + self.write_indent = false; } else if key == VALUE_KEY { - value.serialize(ser)?; + // If element was written then we need to indent next field unless it is a text field + self.write_indent = value.serialize(ser)?.allow_indent(); } else { value.serialize(ElementSerializer { key: XmlName::try_from(key)?, ser, })?; + // Element was written so we need to indent next field unless it is a text field + self.write_indent = true; } Ok(()) } @@ -483,7 +492,9 @@ impl<'w, 'k, W: Write> SerializeStruct for Struct<'w, 'k, W> { self.ser.ser.writer.write_char('>')?; self.ser.ser.writer.write_str(&self.children)?; - self.ser.ser.indent.write_indent(&mut self.ser.ser.writer)?; + if self.write_indent { + self.ser.ser.indent.write_indent(&mut self.ser.ser.writer)?; + } self.ser.ser.writer.write_str(" bool { + matches!(self, Self::Element | Self::Nothing) + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////// /// Implements serialization method by forwarding it to the serializer created by From ac70ab5345fe2566472cf45dc247b71f3832a3a5 Mon Sep 17 00:00:00 2001 From: Mingun Date: Mon, 7 Oct 2024 22:37:55 +0500 Subject: [PATCH 4/5] Remove no longer used `indent` field from SimpleTypeSerializer --- src/de/simple_type.rs | 5 ++- src/se/content.rs | 5 ++- src/se/element.rs | 5 ++- src/se/simple_type.rs | 82 +++++++++++++------------------------------ src/se/text.rs | 12 +++---- 5 files changed, 36 insertions(+), 73 deletions(-) diff --git a/src/de/simple_type.rs b/src/de/simple_type.rs index 5c102674..d78db8fc 100644 --- a/src/de/simple_type.rs +++ b/src/de/simple_type.rs @@ -791,7 +791,7 @@ impl<'de, 'a> EnumAccess<'de> for SimpleTypeDeserializer<'de, 'a> { mod tests { use super::*; use crate::se::simple_type::{QuoteTarget, SimpleTypeSerializer}; - use crate::se::{Indent, QuoteLevel}; + use crate::se::QuoteLevel; use crate::utils::{ByteBuf, Bytes}; use serde::de::IgnoredAny; use serde::{Deserialize, Serialize}; @@ -828,7 +828,6 @@ mod tests { writer: String::new(), target: QuoteTarget::Text, level: QuoteLevel::Full, - indent: Indent::None, }) .unwrap(), xml @@ -943,7 +942,7 @@ mod tests { writer: &mut buffer, target: QuoteTarget::Text, level: QuoteLevel::Full, - indent: Some(Indent::None), + write_delimiter: false, }) .unwrap(); assert_eq!(buffer, $input); diff --git a/src/se/content.rs b/src/se/content.rs index 6c867426..c07a2d9d 100644 --- a/src/se/content.rs +++ b/src/se/content.rs @@ -80,13 +80,12 @@ pub struct ContentSerializer<'w, 'i, W: Write> { impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> { /// Turns this serializer into serializer of a text content #[inline] - pub fn into_simple_type_serializer(self) -> SimpleTypeSerializer<'i, &'w mut W> { + pub fn into_simple_type_serializer(self) -> SimpleTypeSerializer<&'w mut W> { //TODO: Customization point: choose between CDATA and Text representation SimpleTypeSerializer { writer: self.writer, target: QuoteTarget::Text, level: self.level, - indent: Indent::None, } } @@ -128,7 +127,7 @@ impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> { serialize: S, ) -> Result where - S: for<'a> FnOnce(SimpleTypeSerializer<'i, &'a mut W>) -> Result<&'a mut W, SeError>, + S: for<'a> FnOnce(SimpleTypeSerializer<&'a mut W>) -> Result<&'a mut W, SeError>, { self.write_indent()?; self.writer.write_char('<')?; diff --git a/src/se/element.rs b/src/se/element.rs index 1ea17194..5f9d3cf7 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -5,7 +5,7 @@ use crate::se::content::ContentSerializer; use crate::se::key::QNameSerializer; use crate::se::simple_type::{QuoteTarget, SimpleSeq, SimpleTypeSerializer}; use crate::se::text::TextSerializer; -use crate::se::{Indent, SeError, WriteResult, XmlName}; +use crate::se::{SeError, WriteResult, XmlName}; use serde::ser::{ Impossible, Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, Serializer, @@ -333,7 +333,7 @@ pub enum Tuple<'w, 'k, W: Write> { /// Serialize each tuple field as an element Element(ElementSerializer<'w, 'k, W>), /// Serialize tuple as an `xs:list`: space-delimited content of fields - Text(SimpleSeq<'k, &'w mut W>), + Text(SimpleSeq<&'w mut W>), } impl<'w, 'k, W: Write> SerializeTupleVariant for Tuple<'w, 'k, W> { @@ -416,7 +416,6 @@ impl<'w, 'k, W: Write> Struct<'w, 'k, W> { writer: &mut self.ser.ser.writer, target: QuoteTarget::DoubleQAttr, level: self.ser.ser.level, - indent: Indent::None, })?; self.ser.ser.writer.write_char('"')?; diff --git a/src/se/simple_type.rs b/src/se/simple_type.rs index dbdfe277..c7336ce6 100644 --- a/src/se/simple_type.rs +++ b/src/se/simple_type.rs @@ -4,7 +4,7 @@ //! [as defined]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition use crate::escape::_escape; -use crate::se::{Indent, QuoteLevel, SeError}; +use crate::se::{QuoteLevel, SeError}; use serde::ser::{ Impossible, Serialize, SerializeSeq, SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, Serializer, @@ -179,22 +179,18 @@ macro_rules! write_atomic { /// /// [item]: https://www.w3.org/TR/xmlschema11-1/#std-item_type_definition /// [simple type]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition -pub struct AtomicSerializer<'i, W: Write> { +pub struct AtomicSerializer { pub writer: W, pub target: QuoteTarget, /// Defines which XML characters need to be escaped pub level: QuoteLevel, - /// When `Some`, the indent that should be written before the content - /// if content is not an empty string. - /// When `None` an `xs:list` delimiter (a space) should be written - pub(crate) indent: Option>, + /// When `true` an `xs:list` delimiter (a space) should be written + pub(crate) write_delimiter: bool, } -impl<'i, W: Write> AtomicSerializer<'i, W> { +impl AtomicSerializer { fn write_str(&mut self, value: &str) -> Result<(), SeError> { - if let Some(indent) = self.indent.as_mut() { - indent.write_indent(&mut self.writer)?; - } else { + if self.write_delimiter { // TODO: Customization point -- possible non-XML compatible extension to specify delimiter char self.writer.write_char(' ')?; } @@ -202,7 +198,7 @@ impl<'i, W: Write> AtomicSerializer<'i, W> { } } -impl<'i, W: Write> Serializer for AtomicSerializer<'i, W> { +impl Serializer for AtomicSerializer { type Ok = bool; type Error = SeError; @@ -401,31 +397,28 @@ impl<'i, W: Write> Serializer for AtomicSerializer<'i, W> { /// - CDATA content (`<...>`) /// /// [simple types]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition -pub struct SimpleTypeSerializer<'i, W: Write> { +pub struct SimpleTypeSerializer { /// Writer to which this serializer writes content pub writer: W, /// Target for which element is serializing. Affects additional characters to escape. pub target: QuoteTarget, /// Defines which XML characters need to be escaped pub level: QuoteLevel, - /// Indent that should be written before the content if content is not an empty string - pub(crate) indent: Indent<'i>, } -impl<'i, W: Write> SimpleTypeSerializer<'i, W> { +impl SimpleTypeSerializer { fn write_str(&mut self, value: &str) -> Result<(), SeError> { - self.indent.write_indent(&mut self.writer)?; Ok(self.writer.write_str(value)?) } } -impl<'i, W: Write> Serializer for SimpleTypeSerializer<'i, W> { +impl Serializer for SimpleTypeSerializer { type Ok = W; type Error = SeError; - type SerializeSeq = SimpleSeq<'i, W>; - type SerializeTuple = SimpleSeq<'i, W>; - type SerializeTupleStruct = SimpleSeq<'i, W>; + type SerializeSeq = SimpleSeq; + type SerializeTuple = SimpleSeq; + type SerializeTupleStruct = SimpleSeq; type SerializeTupleVariant = Impossible; type SerializeMap = Impossible; type SerializeStruct = Impossible; @@ -470,7 +463,6 @@ impl<'i, W: Write> Serializer for SimpleTypeSerializer<'i, W> { writer: self.writer, target: self.target, level: self.level, - indent: self.indent, is_empty: true, }) } @@ -535,17 +527,15 @@ impl<'i, W: Write> Serializer for SimpleTypeSerializer<'i, W> { } /// Serializer for a sequence of atomic values delimited by space -pub struct SimpleSeq<'i, W: Write> { +pub struct SimpleSeq { writer: W, target: QuoteTarget, level: QuoteLevel, - /// Indent that should be written before the content if content is not an empty string - indent: Indent<'i>, /// If `true`, nothing was written yet to the `writer` is_empty: bool, } -impl<'i, W: Write> SerializeSeq for SimpleSeq<'i, W> { +impl SerializeSeq for SimpleSeq { type Ok = W; type Error = SeError; @@ -553,17 +543,11 @@ impl<'i, W: Write> SerializeSeq for SimpleSeq<'i, W> { where T: ?Sized + Serialize, { - // Write indent for the first element and delimiter for others - let indent = if self.is_empty { - Some(self.indent.borrow()) - } else { - None - }; if value.serialize(AtomicSerializer { writer: &mut self.writer, target: self.target, level: self.level, - indent, + write_delimiter: !self.is_empty, })? { self.is_empty = false; } @@ -576,7 +560,7 @@ impl<'i, W: Write> SerializeSeq for SimpleSeq<'i, W> { } } -impl<'i, W: Write> SerializeTuple for SimpleSeq<'i, W> { +impl SerializeTuple for SimpleSeq { type Ok = W; type Error = SeError; @@ -594,7 +578,7 @@ impl<'i, W: Write> SerializeTuple for SimpleSeq<'i, W> { } } -impl<'i, W: Write> SerializeTupleStruct for SimpleSeq<'i, W> { +impl SerializeTupleStruct for SimpleSeq { type Ok = W; type Error = SeError; @@ -612,7 +596,7 @@ impl<'i, W: Write> SerializeTupleStruct for SimpleSeq<'i, W> { } } -impl<'i, W: Write> SerializeTupleVariant for SimpleSeq<'i, W> { +impl SerializeTupleVariant for SimpleSeq { type Ok = W; type Error = SeError; @@ -928,7 +912,7 @@ mod tests { writer: &mut buffer, target: QuoteTarget::Text, level: QuoteLevel::Full, - indent: Some(Indent::None), + write_delimiter: false, }; let has_written = $data.serialize(ser).unwrap(); @@ -949,7 +933,7 @@ mod tests { writer: &mut buffer, target: QuoteTarget::Text, level: QuoteLevel::Full, - indent: Some(Indent::None), + write_delimiter: false, }; match $data.serialize(ser).unwrap_err() { @@ -1047,7 +1031,6 @@ mod tests { writer: String::new(), target: QuoteTarget::Text, level: QuoteLevel::Full, - indent: Indent::None, }; let buffer = $data.serialize(ser).unwrap(); @@ -1067,7 +1050,6 @@ mod tests { writer: &mut buffer, target: QuoteTarget::Text, level: QuoteLevel::Full, - indent: Indent::None, }; match $data.serialize(ser).unwrap_err() { @@ -1153,19 +1135,15 @@ mod tests { mod simple_seq { use super::*; - use crate::writer::Indentation; use pretty_assertions::assert_eq; #[test] fn empty_seq() { let mut buffer = String::new(); - let mut indent = Indentation::new(b'*', 2); - indent.grow(); let ser = SimpleSeq { writer: &mut buffer, target: QuoteTarget::Text, level: QuoteLevel::Full, - indent: Indent::Owned(indent), is_empty: true, }; @@ -1176,13 +1154,10 @@ mod tests { #[test] fn all_items_empty() { let mut buffer = String::new(); - let mut indent = Indentation::new(b'*', 2); - indent.grow(); let mut ser = SimpleSeq { writer: &mut buffer, target: QuoteTarget::Text, level: QuoteLevel::Full, - indent: Indent::Owned(indent), is_empty: true, }; @@ -1196,13 +1171,10 @@ mod tests { #[test] fn some_items_empty1() { let mut buffer = String::new(); - let mut indent = Indentation::new(b'*', 2); - indent.grow(); let mut ser = SimpleSeq { writer: &mut buffer, target: QuoteTarget::Text, level: QuoteLevel::Full, - indent: Indent::Owned(indent), is_empty: true, }; @@ -1210,19 +1182,16 @@ mod tests { SerializeSeq::serialize_element(&mut ser, &1).unwrap(); SerializeSeq::serialize_element(&mut ser, "").unwrap(); SerializeSeq::end(ser).unwrap(); - assert_eq!(buffer, "\n**1"); + assert_eq!(buffer, "1"); } #[test] fn some_items_empty2() { let mut buffer = String::new(); - let mut indent = Indentation::new(b'*', 2); - indent.grow(); let mut ser = SimpleSeq { writer: &mut buffer, target: QuoteTarget::Text, level: QuoteLevel::Full, - indent: Indent::Owned(indent), is_empty: true, }; @@ -1230,19 +1199,16 @@ mod tests { SerializeSeq::serialize_element(&mut ser, "").unwrap(); SerializeSeq::serialize_element(&mut ser, &2).unwrap(); SerializeSeq::end(ser).unwrap(); - assert_eq!(buffer, "\n**1 2"); + assert_eq!(buffer, "1 2"); } #[test] fn items() { let mut buffer = String::new(); - let mut indent = Indentation::new(b'*', 2); - indent.grow(); let mut ser = SimpleSeq { writer: &mut buffer, target: QuoteTarget::Text, level: QuoteLevel::Full, - indent: Indent::Owned(indent), is_empty: true, }; @@ -1250,7 +1216,7 @@ mod tests { SerializeSeq::serialize_element(&mut ser, &2).unwrap(); SerializeSeq::serialize_element(&mut ser, &3).unwrap(); SerializeSeq::end(ser).unwrap(); - assert_eq!(buffer, "\n**1 2 3"); + assert_eq!(buffer, "1 2 3"); } } } diff --git a/src/se/text.rs b/src/se/text.rs index 9dd30509..7094adba 100644 --- a/src/se/text.rs +++ b/src/se/text.rs @@ -23,16 +23,16 @@ macro_rules! write_primitive { /// This serializer a very similar to [`SimpleTypeSerializer`], but different /// from it in how it processes unit enum variants. Unlike [`SimpleTypeSerializer`] /// this serializer does not write anything for the unit variant. -pub struct TextSerializer<'i, W: Write>(pub SimpleTypeSerializer<'i, W>); +pub struct TextSerializer(pub SimpleTypeSerializer); -impl<'i, W: Write> Serializer for TextSerializer<'i, W> { +impl Serializer for TextSerializer { type Ok = W; type Error = SeError; - type SerializeSeq = SimpleSeq<'i, W>; - type SerializeTuple = SimpleSeq<'i, W>; - type SerializeTupleStruct = SimpleSeq<'i, W>; - type SerializeTupleVariant = SimpleSeq<'i, W>; + type SerializeSeq = SimpleSeq; + type SerializeTuple = SimpleSeq; + type SerializeTupleStruct = SimpleSeq; + type SerializeTupleVariant = SimpleSeq; type SerializeMap = Impossible; type SerializeStruct = Impossible; type SerializeStructVariant = Impossible; From da88af2c8d293c65ef9751a38cab6f1f27d2204a Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 10 Oct 2024 19:58:25 +0500 Subject: [PATCH 5/5] Add regression test for #655 - ok --- tests/serde-issues.rs | 49 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/tests/serde-issues.rs b/tests/serde-issues.rs index 9781ae72..bac7d416 100644 --- a/tests/serde-issues.rs +++ b/tests/serde-issues.rs @@ -4,7 +4,7 @@ use pretty_assertions::assert_eq; use quick_xml::de::{from_reader, from_str}; -use quick_xml::se::{to_string, to_string_with_root}; +use quick_xml::se::{to_string, to_string_with_root, Serializer}; use serde::de::{Deserializer, IgnoredAny}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -475,6 +475,53 @@ fn issue580() { ); } +/// Regression test for https://github.com/tafia/quick-xml/issues/655 +#[test] +fn issue655() { + #[derive(Deserialize, Serialize, Debug)] + pub struct TextureCoordinates { + #[serde(rename = "@dimension")] + pub dimension: String, + #[serde(rename = "@channel")] + pub channel: String, + #[serde(rename = "$value")] + pub elements: String, + } + #[derive(Deserialize, Serialize, Debug)] + #[serde(rename_all = "PascalCase")] + pub struct VertexBuffer { + pub positions: String, + pub normals: String, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub texture_coordinates: Option, + } + + let mut buffer = String::new(); + let mut ser = Serializer::with_root(&mut buffer, None).unwrap(); + ser.indent(' ', 2); + ser.expand_empty_elements(true); + + let obj = VertexBuffer { + positions: "319.066 -881.28705 7.71589".into(), + normals: "-0.0195154 -0.21420999 0.976593".into(), + texture_coordinates: Some(TextureCoordinates { + dimension: "2D".into(), + channel: "0".into(), + elements: "752494 0.201033,0.773967 0.201033".into(), + }), + }; + obj.serialize(ser).unwrap(); + assert_eq!( + buffer, + "\ + + 319.066 -881.28705 7.71589 + -0.0195154 -0.21420999 0.976593 + 752494 0.201033,0.773967 0.201033 +" + ); +} + /// Regression test for https://github.com/tafia/quick-xml/issues/683. #[test] fn issue683() {