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(">")?;
+ self.writer.write_str(name.0)?;
+ self.writer.write_char('>')?;
+ } 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));