diff --git a/Changelog.md b/Changelog.md index 33e5e96b..ba808781 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. +- [#617]: 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 +[#617]: https://github.com/tafia/quick-xml/pull/617 ## 0.29.0 -- 2023-06-13 diff --git a/src/se/content.rs b/src/se/content.rs index 24c29ac7..bd5b4562 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, } } @@ -92,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(()) } @@ -483,6 +495,7 @@ pub(super) mod tests { level: QuoteLevel::Full, indent: Indent::None, write_indent: false, + expand_empty_elements: false, }; $data.serialize(ser).unwrap(); @@ -503,6 +516,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 +686,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 +707,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..4f076df5 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -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,76 @@ 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); + } + }; + } + + /// 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 `<\"&'>`")); + } } diff --git a/src/se/mod.rs b/src/se/mod.rs index cce81fd1..06b80044 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,45 @@ 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. 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 + } + /// 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));