From 1f44bb41e4953ee5e015a420062a46ea6410052a Mon Sep 17 00:00:00 2001 From: Arne Bahlo Date: Tue, 27 Jun 2023 18:17:27 +0200 Subject: [PATCH 1/5] Add expand_empty_elements fn to Serializer Setting this to true will serialize empty elements to instead of . --- src/se/content.rs | 8 ++++++++ src/se/element.rs | 38 +++++++++++++++++++++++++++++++++++++- src/se/mod.rs | 8 ++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/se/content.rs b/src/se/content.rs index 24c29ac7..e816324b 100644 --- a/src/se/content.rs +++ b/src/se/content.rs @@ -56,6 +56,9 @@ pub struct ContentSerializer<'w, 'i, W: Write> { /// If `true`, then current indent will be written before writing the content, /// but only if content is not empty. pub write_indent: bool, + // If `true`, then empty elements will be serialized as `` + // instead of ``. + pub expand_empty_elements: bool, //TODO: add settings to disallow consequent serialization of primitives } @@ -85,6 +88,7 @@ impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> { level: self.level, indent: self.indent.borrow(), write_indent: self.write_indent, + expand_empty_elements: false, } } @@ -483,6 +487,7 @@ pub(super) mod tests { level: QuoteLevel::Full, indent: Indent::None, write_indent: false, + expand_empty_elements: false, }; $data.serialize(ser).unwrap(); @@ -503,6 +508,7 @@ pub(super) mod tests { level: QuoteLevel::Full, indent: Indent::None, write_indent: false, + expand_empty_elements: false, }; match $data.serialize(ser).unwrap_err() { @@ -672,6 +678,7 @@ pub(super) mod tests { level: QuoteLevel::Full, indent: Indent::Owned(Indentation::new(b' ', 2)), write_indent: false, + expand_empty_elements: false, }; $data.serialize(ser).unwrap(); @@ -692,6 +699,7 @@ pub(super) mod tests { level: QuoteLevel::Full, indent: Indent::Owned(Indentation::new(b' ', 2)), write_indent: false, + expand_empty_elements: false, }; match $data.serialize(ser).unwrap_err() { diff --git a/src/se/element.rs b/src/se/element.rs index 19a96571..f82921d8 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -66,7 +66,7 @@ impl<'w, 'k, W: Write> Serializer for ElementSerializer<'w, 'k, W> { write_primitive!(serialize_bytes(&[u8])); fn serialize_str(self, value: &str) -> Result { - if value.is_empty() { + if value.is_empty() && !self.ser.expand_empty_elements { self.ser.write_empty(self.key) } else { self.ser @@ -397,6 +397,7 @@ impl<'w, 'k, W: Write> Struct<'w, 'k, W> { level: self.ser.ser.level, indent: self.ser.ser.indent.borrow(), write_indent: true, + expand_empty_elements: false, }; if key == TEXT_KEY { @@ -574,6 +575,7 @@ mod tests { level: QuoteLevel::Full, indent: Indent::None, write_indent: false, + expand_empty_elements: false, }, key: XmlName("root"), }; @@ -597,6 +599,7 @@ mod tests { level: QuoteLevel::Full, indent: Indent::None, write_indent: false, + expand_empty_elements: false, }, key: XmlName("root"), }; @@ -1533,6 +1536,7 @@ mod tests { level: QuoteLevel::Full, indent: Indent::Owned(Indentation::new(b' ', 2)), write_indent: false, + expand_empty_elements: false, }, key: XmlName("root"), }; @@ -1556,6 +1560,7 @@ mod tests { level: QuoteLevel::Full, indent: Indent::Owned(Indentation::new(b' ', 2)), write_indent: false, + expand_empty_elements: false, }, key: XmlName("root"), }; @@ -2533,4 +2538,35 @@ mod tests { "); } } + + mod expand_empty_elements { + use super::*; + use pretty_assertions::assert_eq; + + /// Checks that given `$data` successfully serialized as `$expected` + macro_rules! serialize_as { + ($name:ident: $data:expr => $expected:expr) => { + #[test] + fn $name() { + let mut buffer = String::new(); + let ser = ElementSerializer { + ser: ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + indent: Indent::None, + write_indent: false, + expand_empty_elements: true, + }, + key: XmlName("root"), + }; + + $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected); + } + }; + } + + serialize_as!(option_some_empty: Some("") => ""); + serialize_as!(option_some_empty_str: Some("") => ""); + } } diff --git a/src/se/mod.rs b/src/se/mod.rs index cce81fd1..8d824692 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -458,6 +458,7 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> { level: QuoteLevel::Full, indent: Indent::None, write_indent: false, + expand_empty_elements: false, }, root_tag: None, } @@ -522,11 +523,18 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> { level: QuoteLevel::Full, indent: Indent::None, write_indent: false, + expand_empty_elements: false, }, root_tag: root_tag.map(|tag| XmlName::try_from(tag)).transpose()?, }) } + /// Enable or disable expansion of empty elements. + pub fn expand_empty_elements(&mut self, expand: bool) -> &mut Self { + self.ser.expand_empty_elements = expand; + self + } + /// Configure indent for a serializer pub fn indent(&mut self, indent_char: char, indent_size: usize) -> &mut Self { self.ser.indent = Indent::Owned(Indentation::new(indent_char as u8, indent_size)); From b2223f58299ba8cf805ca425607fa7b624782a59 Mon Sep 17 00:00:00 2001 From: Arne Bahlo Date: Wed, 28 Jun 2023 12:01:58 +0200 Subject: [PATCH 2/5] Move the expand empty elements logic into write_empty --- src/se/content.rs | 14 +++++++++++--- src/se/element.rs | 43 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/se/content.rs b/src/se/content.rs index e816324b..bd5b4562 100644 --- a/src/se/content.rs +++ b/src/se/content.rs @@ -96,9 +96,17 @@ impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> { #[inline] pub(super) fn write_empty(mut self, name: XmlName) -> Result<(), DeError> { self.write_indent()?; - self.writer.write_char('<')?; - self.writer.write_str(name.0)?; - self.writer.write_str("/>")?; + if self.expand_empty_elements { + self.writer.write_char('<')?; + self.writer.write_str(name.0)?; + self.writer.write_str(">')?; + } else { + self.writer.write_str("<")?; + self.writer.write_str(name.0)?; + self.writer.write_str("/>")?; + } Ok(()) } diff --git a/src/se/element.rs b/src/se/element.rs index f82921d8..4f076df5 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -66,7 +66,7 @@ impl<'w, 'k, W: Write> Serializer for ElementSerializer<'w, 'k, W> { write_primitive!(serialize_bytes(&[u8])); fn serialize_str(self, value: &str) -> Result { - if value.is_empty() && !self.ser.expand_empty_elements { + if value.is_empty() { self.ser.write_empty(self.key) } else { self.ser @@ -2566,7 +2566,48 @@ mod tests { }; } + /// Checks that attempt to serialize given `$data` results to a + /// serialization error `$kind` with `$reason` + macro_rules! err { + ($name:ident: $data:expr => $kind:ident($reason:literal)) => { + #[test] + fn $name() { + let mut buffer = String::new(); + let ser = ElementSerializer { + ser: ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + indent: Indent::None, + write_indent: false, + expand_empty_elements: false, + }, + key: XmlName("root"), + }; + + match $data.serialize(ser).unwrap_err() { + DeError::$kind(e) => assert_eq!(e, $reason), + e => panic!( + "Expected `{}({})`, found `{:?}`", + stringify!($kind), + $reason, + e + ), + } + // We can write something before fail + // assert_eq!(buffer, ""); + } + }; + } + serialize_as!(option_some_empty: Some("") => ""); serialize_as!(option_some_empty_str: Some("") => ""); + + serialize_as!(unit: () => ""); + serialize_as!(unit_struct: Unit => ""); + serialize_as!(unit_struct_escaped: UnitEscaped => ""); + + serialize_as!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: Enum::UnitEscaped + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); } } From 30da60edee2ecf75a3727fe56bc50310732e47f5 Mon Sep 17 00:00:00 2001 From: Arne Bahlo Date: Thu, 29 Jun 2023 21:04:01 +0200 Subject: [PATCH 3/5] Add doctest example for expand_empty_elements --- src/se/mod.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/se/mod.rs b/src/se/mod.rs index 8d824692..06b80044 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -529,7 +529,34 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> { }) } - /// Enable or disable expansion of empty elements. + /// Enable or disable expansion of empty elements. Defaults to `false`. + /// + /// # Examples + /// + /// ``` + /// # use pretty_assertions::assert_eq; + /// # use serde::Serialize; + /// # use quick_xml::se::Serializer; + /// + /// #[derive(Debug, PartialEq, Serialize)] + /// struct Struct { + /// question: Option, + /// } + /// + /// let mut buffer = String::new(); + /// let mut ser = Serializer::new(&mut buffer); + /// ser.expand_empty_elements(true); + /// + /// let data = Struct { + /// question: None, + /// }; + /// + /// data.serialize(ser).unwrap(); + /// assert_eq!( + /// buffer, + /// "" + /// ); + /// ``` pub fn expand_empty_elements(&mut self, expand: bool) -> &mut Self { self.ser.expand_empty_elements = expand; self From 6624dee6ef017e22e8f0000f0a6a04987a0f1c40 Mon Sep 17 00:00:00 2001 From: Arne Bahlo Date: Thu, 29 Jun 2023 21:05:48 +0200 Subject: [PATCH 4/5] Add entry to CHANGELOG.md --- Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog.md b/Changelog.md index 33e5e96b..18e890d5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -15,6 +15,7 @@ - [#609]: Added `Writer::write_serializable` to provide the capability to serialize arbitrary types using serde when using the lower-level `Writer` API. - [#615]: Added ability to set entity resolver when deserialize using borrowing reader. +- [#620]: Added ability to enforce the expansion of empty elements. ### Bug Fixes @@ -23,6 +24,7 @@ [#609]: https://github.com/tafia/quick-xml/pull/609 [#615]: https://github.com/tafia/quick-xml/pull/615 +[#620]: https://github.com/tafia/quick-xml/pull/620 ## 0.29.0 -- 2023-06-13 From 52508490f9c2712a9ae356991b5f8c765308d8fc Mon Sep 17 00:00:00 2001 From: Arne Bahlo Date: Fri, 30 Jun 2023 10:15:54 +0200 Subject: [PATCH 5/5] Link to issue instead of PR in changelog --- Changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 18e890d5..ba808781 100644 --- a/Changelog.md +++ b/Changelog.md @@ -15,7 +15,7 @@ - [#609]: Added `Writer::write_serializable` to provide the capability to serialize arbitrary types using serde when using the lower-level `Writer` API. - [#615]: Added ability to set entity resolver when deserialize using borrowing reader. -- [#620]: Added ability to enforce the expansion of empty elements. +- [#617]: Added ability to enforce the expansion of empty elements. ### Bug Fixes @@ -24,7 +24,7 @@ [#609]: https://github.com/tafia/quick-xml/pull/609 [#615]: https://github.com/tafia/quick-xml/pull/615 -[#620]: https://github.com/tafia/quick-xml/pull/620 +[#617]: https://github.com/tafia/quick-xml/pull/617 ## 0.29.0 -- 2023-06-13