Repository: tafia/quick-xml
Pull request #704 (2024-01-12 06:23:29)

Fixed expand empty not expanding tags with attributes
In PR #620 the expand_empty_elements function was added to the serializer. With the current implementation only unit variants are expanded, but if a tag has an attribute it would not expand the tag which seems inconsistent.


Patch:
diff --git a/Changelog.md b/Changelog.md
index 5c51eec..f652b3d 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -35,6 +35,7 @@ to get an offset of the error position. For `SyntaxError`s the range
 - [#622]: Fix wrong disregarding of not closed markup, such as lone `<`.
 - [#684]: Fix incorrect position reported for `Error::IllFormed(DoubleHyphenInComment)`.
 - [#684]: Fix incorrect position reported for `Error::IllFormed(MissingDoctypeName)`.
+- [#704]: Fix empty tags with attributes not being expanded when `expand_empty_elements` is set to true.
 
 ### Misc Changes
 
@@ -66,6 +67,7 @@ to get an offset of the error position. For `SyntaxError`s the range
 [#677]: https://github.com/tafia/quick-xml/pull/677
 [#684]: https://github.com/tafia/quick-xml/pull/684
 [#689]: https://github.com/tafia/quick-xml/pull/689
+[#704]: https://github.com/tafia/quick-xml/pull/704
 
 
 ## 0.31.0 -- 2023-10-22
diff --git a/src/se/element.rs b/src/se/element.rs
index 3d6056e..35aa28f 100644
--- a/src/se/element.rs
+++ b/src/se/element.rs
@@ -467,7 +467,13 @@ impl<'w, 'k, W: Write> SerializeStruct for Struct<'w, 'k, W> {
         self.ser.ser.indent.decrease();
 
         if self.children.is_empty() {
-            self.ser.ser.writer.write_str("/>")?;
+            if self.ser.ser.expand_empty_elements {
+                self.ser.ser.writer.write_str("></")?;
+                self.ser.ser.writer.write_str(self.ser.key.0)?;
+                self.ser.ser.writer.write_char('>')?;
+            } else {
+                self.ser.ser.writer.write_str("/>")?;
+            }
         } else {
             self.ser.ser.writer.write_char('>')?;
             self.ser.ser.writer.write_str(&self.children)?;


Test patch:
diff --git a/tests/serde-se.rs b/tests/serde-se.rs
index 5488ae0..39f6e66 100644
--- a/tests/serde-se.rs
+++ b/tests/serde-se.rs
@@ -42,6 +42,12 @@ struct Nested {
 #[derive(Debug, PartialEq, Deserialize, Serialize)]
 struct Empty {}
 
+#[derive(Debug, PartialEq, Deserialize, Serialize)]
+struct EmptyWithAttribute {
+    #[serde(rename = "@attr")]
+    attr: f64,
+}
+
 #[derive(Debug, PartialEq, Deserialize, Serialize)]
 struct Text {
     #[serde(rename = "$text")]
@@ -69,6 +75,10 @@ enum ExternallyTagged {
         string: &'static str,
     },
     Empty {},
+    EmptyWithAttribute {
+        #[serde(rename = "@attr")]
+        attr: f64,
+    },
 }
 
 /// Having both `#[serde(flatten)]` and `'static` fields in one struct leads to
@@ -109,6 +119,10 @@ enum InternallyTagged {
         string: &'static str,
     },
     Empty {},
+    EmptyWithAttribute {
+        #[serde(rename = "@attr")]
+        attr: f64,
+    },
 }
 
 /// Having both `#[serde(flatten)]` and `'static` fields in one struct leads to
@@ -149,6 +163,10 @@ enum AdjacentlyTagged {
         string: &'static str,
     },
     Empty {},
+    EmptyWithAttribute {
+        #[serde(rename = "@attr")]
+        attr: f64,
+    },
 }
 
 /// Having both `#[serde(flatten)]` and `'static` fields in one struct leads to
@@ -189,6 +207,10 @@ enum Untagged {
         string: &'static str,
     },
     Empty {},
+    EmptyWithAttribute {
+        #[serde(rename = "@attr")]
+        attr: f64,
+    },
 }
 
 /// Having both `#[serde(flatten)]` and `'static` fields in one struct leads to
@@ -356,6 +378,9 @@ mod without_root {
     serialize_as!(empty_struct:
         Empty {}
         => "<Empty/>");
+    serialize_as!(empty_attr_struct:
+        EmptyWithAttribute {attr: 42.0}
+        => "<EmptyWithAttribute attr=\"42\"/>");
     serialize_as!(text:
         Text {
             float: 42.0,
@@ -1706,6 +1731,98 @@ mod without_root {
             }
         }
     }
+
+    mod expand_empty {
+        use super::*;
+        use pretty_assertions::assert_eq;
+
+        macro_rules! serialize_as {
+            ($name:ident: $data:expr => $expected:literal) => {
+                #[test]
+                fn $name() {
+                    let mut buffer = String::new();
+                    let mut ser = Serializer::new(&mut buffer);
+                    ser.expand_empty_elements(true);
+                    ser.indent(' ', 2);
+
+                    $data.serialize(ser).unwrap();
+                    assert_eq!(buffer, $expected);
+                }
+            };
+        }
+
+        serialize_as!(option_none: Option::<Unit>::None => "");
+        serialize_as!(option_some: Some(Unit) => "<Unit></Unit>");
+
+        serialize_as!(unit_struct: Unit => "<Unit></Unit>");
+
+        serialize_as!(empty_struct:
+            Empty {}
+            => "<Empty></Empty>");
+        mod enum_ {
+            use super::*;
+
+            mod externally_tagged {
+                use super::*;
+                use pretty_assertions::assert_eq;
+
+                serialize_as!(unit:
+                    ExternallyTagged::Unit
+                    => "<Unit></Unit>");
+                serialize_as!(empty_struct:
+                    ExternallyTagged::Empty {}
+                    => "<Empty></Empty>");
+                serialize_as!(empty_attr_struct:
+                    ExternallyTagged::EmptyWithAttribute { attr: 42.0 }
+                    => "<EmptyWithAttribute attr=\"42\"></EmptyWithAttribute>");
+            }
+
+            mod adjacently_tagged {
+                use super::*;
+                use pretty_assertions::assert_eq;
+
+                serialize_as!(empty_struct:
+                    AdjacentlyTagged::Empty {}
+                    => "<AdjacentlyTagged>\n  \
+                            <tag>Empty</tag>\n  \
+                            <content></content>\n\
+                        </AdjacentlyTagged>");
+                serialize_as!(empty_attr_struct:
+                    AdjacentlyTagged::EmptyWithAttribute { attr: 42.0 }
+                    => "<AdjacentlyTagged>\n  \
+                            <tag>EmptyWithAttribute</tag>\n  \
+                            <content attr=\"42\"></content>\n\
+                        </AdjacentlyTagged>");
+            }
+
+            mod untagged {
+                use super::*;
+                use pretty_assertions::assert_eq;
+                serialize_as!(empty_struct:
+                    Untagged::Empty {}
+                    => "<Untagged></Untagged>");
+                serialize_as!(empty_attr_struct:
+                    Untagged::EmptyWithAttribute { attr: 42.0 }
+                    => "<Untagged attr=\"42\"></Untagged>");
+            }
+
+            mod internally_tagged {
+                use super::*;
+                use pretty_assertions::assert_eq;
+
+                serialize_as!(empty_struct:
+                    InternallyTagged::Empty {}
+                    => "<InternallyTagged>\n  \
+                            <tag>Empty</tag>\n\
+                        </InternallyTagged>");
+                serialize_as!(empty_attr_struct:
+                    InternallyTagged::EmptyWithAttribute { attr: 42.0 }
+                    => "<InternallyTagged attr=\"42\">\n  \
+                            <tag>EmptyWithAttribute</tag>\n\
+                        </InternallyTagged>");
+            }
+        }
+    }
 }
 
 mod with_root {