Skip to content

Commit 05059fd

Browse files
authored
Add expand_empty_elements fn to Serializer (#620)
* Add expand_empty_elements fn to Serializer Setting this to true will serialize empty elements to <element></element> instead of <element/>. * Move the expand empty elements logic into write_empty * Add doctest example for expand_empty_elements * Add entry to CHANGELOG.md * Link to issue instead of PR in changelog
1 parent aca1f79 commit 05059fd

File tree

4 files changed

+133
-3
lines changed

4 files changed

+133
-3
lines changed

Changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- [#609]: Added `Writer::write_serializable` to provide the capability to serialize
1616
arbitrary types using serde when using the lower-level `Writer` API.
1717
- [#615]: Added ability to set entity resolver when deserialize using borrowing reader.
18+
- [#617]: Added ability to enforce the expansion of empty elements.
1819

1920
### Bug Fixes
2021

@@ -26,6 +27,7 @@
2627
[#604]: https://github.com/tafia/quick-xml/issue/604
2728
[#609]: https://github.com/tafia/quick-xml/pull/609
2829
[#615]: https://github.com/tafia/quick-xml/pull/615
30+
[#617]: https://github.com/tafia/quick-xml/pull/617
2931

3032

3133
## 0.29.0 -- 2023-06-13

src/se/content.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ pub struct ContentSerializer<'w, 'i, W: Write> {
5656
/// If `true`, then current indent will be written before writing the content,
5757
/// but only if content is not empty.
5858
pub write_indent: bool,
59+
// If `true`, then empty elements will be serialized as `<element></element>`
60+
// instead of `<element/>`.
61+
pub expand_empty_elements: bool,
5962
//TODO: add settings to disallow consequent serialization of primitives
6063
}
6164

@@ -85,16 +88,25 @@ impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> {
8588
level: self.level,
8689
indent: self.indent.borrow(),
8790
write_indent: self.write_indent,
91+
expand_empty_elements: false,
8892
}
8993
}
9094

9195
/// Writes `name` as self-closed tag
9296
#[inline]
9397
pub(super) fn write_empty(mut self, name: XmlName) -> Result<(), DeError> {
9498
self.write_indent()?;
95-
self.writer.write_char('<')?;
96-
self.writer.write_str(name.0)?;
97-
self.writer.write_str("/>")?;
99+
if self.expand_empty_elements {
100+
self.writer.write_char('<')?;
101+
self.writer.write_str(name.0)?;
102+
self.writer.write_str("></")?;
103+
self.writer.write_str(name.0)?;
104+
self.writer.write_char('>')?;
105+
} else {
106+
self.writer.write_str("<")?;
107+
self.writer.write_str(name.0)?;
108+
self.writer.write_str("/>")?;
109+
}
98110
Ok(())
99111
}
100112

@@ -483,6 +495,7 @@ pub(super) mod tests {
483495
level: QuoteLevel::Full,
484496
indent: Indent::None,
485497
write_indent: false,
498+
expand_empty_elements: false,
486499
};
487500

488501
$data.serialize(ser).unwrap();
@@ -503,6 +516,7 @@ pub(super) mod tests {
503516
level: QuoteLevel::Full,
504517
indent: Indent::None,
505518
write_indent: false,
519+
expand_empty_elements: false,
506520
};
507521

508522
match $data.serialize(ser).unwrap_err() {
@@ -672,6 +686,7 @@ pub(super) mod tests {
672686
level: QuoteLevel::Full,
673687
indent: Indent::Owned(Indentation::new(b' ', 2)),
674688
write_indent: false,
689+
expand_empty_elements: false,
675690
};
676691

677692
$data.serialize(ser).unwrap();
@@ -692,6 +707,7 @@ pub(super) mod tests {
692707
level: QuoteLevel::Full,
693708
indent: Indent::Owned(Indentation::new(b' ', 2)),
694709
write_indent: false,
710+
expand_empty_elements: false,
695711
};
696712

697713
match $data.serialize(ser).unwrap_err() {

src/se/element.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ impl<'w, 'k, W: Write> Struct<'w, 'k, W> {
397397
level: self.ser.ser.level,
398398
indent: self.ser.ser.indent.borrow(),
399399
write_indent: true,
400+
expand_empty_elements: false,
400401
};
401402

402403
if key == TEXT_KEY {
@@ -574,6 +575,7 @@ mod tests {
574575
level: QuoteLevel::Full,
575576
indent: Indent::None,
576577
write_indent: false,
578+
expand_empty_elements: false,
577579
},
578580
key: XmlName("root"),
579581
};
@@ -597,6 +599,7 @@ mod tests {
597599
level: QuoteLevel::Full,
598600
indent: Indent::None,
599601
write_indent: false,
602+
expand_empty_elements: false,
600603
},
601604
key: XmlName("root"),
602605
};
@@ -1533,6 +1536,7 @@ mod tests {
15331536
level: QuoteLevel::Full,
15341537
indent: Indent::Owned(Indentation::new(b' ', 2)),
15351538
write_indent: false,
1539+
expand_empty_elements: false,
15361540
},
15371541
key: XmlName("root"),
15381542
};
@@ -1556,6 +1560,7 @@ mod tests {
15561560
level: QuoteLevel::Full,
15571561
indent: Indent::Owned(Indentation::new(b' ', 2)),
15581562
write_indent: false,
1563+
expand_empty_elements: false,
15591564
},
15601565
key: XmlName("root"),
15611566
};
@@ -2533,4 +2538,76 @@ mod tests {
25332538
</root>");
25342539
}
25352540
}
2541+
2542+
mod expand_empty_elements {
2543+
use super::*;
2544+
use pretty_assertions::assert_eq;
2545+
2546+
/// Checks that given `$data` successfully serialized as `$expected`
2547+
macro_rules! serialize_as {
2548+
($name:ident: $data:expr => $expected:expr) => {
2549+
#[test]
2550+
fn $name() {
2551+
let mut buffer = String::new();
2552+
let ser = ElementSerializer {
2553+
ser: ContentSerializer {
2554+
writer: &mut buffer,
2555+
level: QuoteLevel::Full,
2556+
indent: Indent::None,
2557+
write_indent: false,
2558+
expand_empty_elements: true,
2559+
},
2560+
key: XmlName("root"),
2561+
};
2562+
2563+
$data.serialize(ser).unwrap();
2564+
assert_eq!(buffer, $expected);
2565+
}
2566+
};
2567+
}
2568+
2569+
/// Checks that attempt to serialize given `$data` results to a
2570+
/// serialization error `$kind` with `$reason`
2571+
macro_rules! err {
2572+
($name:ident: $data:expr => $kind:ident($reason:literal)) => {
2573+
#[test]
2574+
fn $name() {
2575+
let mut buffer = String::new();
2576+
let ser = ElementSerializer {
2577+
ser: ContentSerializer {
2578+
writer: &mut buffer,
2579+
level: QuoteLevel::Full,
2580+
indent: Indent::None,
2581+
write_indent: false,
2582+
expand_empty_elements: false,
2583+
},
2584+
key: XmlName("root"),
2585+
};
2586+
2587+
match $data.serialize(ser).unwrap_err() {
2588+
DeError::$kind(e) => assert_eq!(e, $reason),
2589+
e => panic!(
2590+
"Expected `{}({})`, found `{:?}`",
2591+
stringify!($kind),
2592+
$reason,
2593+
e
2594+
),
2595+
}
2596+
// We can write something before fail
2597+
// assert_eq!(buffer, "");
2598+
}
2599+
};
2600+
}
2601+
2602+
serialize_as!(option_some_empty: Some("") => "<root></root>");
2603+
serialize_as!(option_some_empty_str: Some("") => "<root></root>");
2604+
2605+
serialize_as!(unit: () => "<root></root>");
2606+
serialize_as!(unit_struct: Unit => "<root></root>");
2607+
serialize_as!(unit_struct_escaped: UnitEscaped => "<root></root>");
2608+
2609+
serialize_as!(enum_unit: Enum::Unit => "<Unit></Unit>");
2610+
err!(enum_unit_escaped: Enum::UnitEscaped
2611+
=> Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`"));
2612+
}
25362613
}

src/se/mod.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,7 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> {
458458
level: QuoteLevel::Full,
459459
indent: Indent::None,
460460
write_indent: false,
461+
expand_empty_elements: false,
461462
},
462463
root_tag: None,
463464
}
@@ -522,11 +523,45 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> {
522523
level: QuoteLevel::Full,
523524
indent: Indent::None,
524525
write_indent: false,
526+
expand_empty_elements: false,
525527
},
526528
root_tag: root_tag.map(|tag| XmlName::try_from(tag)).transpose()?,
527529
})
528530
}
529531

532+
/// Enable or disable expansion of empty elements. Defaults to `false`.
533+
///
534+
/// # Examples
535+
///
536+
/// ```
537+
/// # use pretty_assertions::assert_eq;
538+
/// # use serde::Serialize;
539+
/// # use quick_xml::se::Serializer;
540+
///
541+
/// #[derive(Debug, PartialEq, Serialize)]
542+
/// struct Struct {
543+
/// question: Option<String>,
544+
/// }
545+
///
546+
/// let mut buffer = String::new();
547+
/// let mut ser = Serializer::new(&mut buffer);
548+
/// ser.expand_empty_elements(true);
549+
///
550+
/// let data = Struct {
551+
/// question: None,
552+
/// };
553+
///
554+
/// data.serialize(ser).unwrap();
555+
/// assert_eq!(
556+
/// buffer,
557+
/// "<Struct><question></question></Struct>"
558+
/// );
559+
/// ```
560+
pub fn expand_empty_elements(&mut self, expand: bool) -> &mut Self {
561+
self.ser.expand_empty_elements = expand;
562+
self
563+
}
564+
530565
/// Configure indent for a serializer
531566
pub fn indent(&mut self, indent_char: char, indent_size: usize) -> &mut Self {
532567
self.ser.indent = Indent::Owned(Indentation::new(indent_char as u8, indent_size));

0 commit comments

Comments
 (0)