Skip to content

Commit f1903a1

Browse files
authored
Merge pull request #922 from zrneely/space-before-slash
Optionally add a space before the trailing slash in unexpanded empty elements
2 parents bc269d6 + b90476e commit f1903a1

File tree

5 files changed

+226
-41
lines changed

5 files changed

+226
-41
lines changed

Changelog.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,17 @@
1414

1515
## Unreleased
1616

17+
Added a way to configure `Writer`. Now all configuration is contained in the `writer::Config`
18+
struct and can be applied at once. When `serde-types` feature is enabled, configuration is serializable.
19+
1720
### New Features
1821

22+
- [#846]: Add methods `config()` and `config_mut()` to inspect and change the writer configuration.
23+
- [#846]: Add ability to write space before `/>` in self-closed tags for maximum compatibility with
24+
XHTML.
25+
- [#846]: Add method `empty_element_handling()` as a more powerful alternative to `expand_empty_elements()`
26+
in `Serializer`.
27+
1928
### Bug Fixes
2029

2130
### Misc Changes
@@ -25,6 +34,7 @@
2534
of `NsReader`. Use `.resolver().bindings()` and `.resolver().resolve()` methods instead.
2635
- [#913]: `Attributes::has_nil` now accepts `NamespaceResolver` instead of `Reader<R>`.
2736

37+
[#846]: https://github.com/tafia/quick-xml/issues/846
2838
[#908]: https://github.com/tafia/quick-xml/pull/908
2939
[#913]: https://github.com/tafia/quick-xml/pull/913
3040

src/se/content.rs

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
use crate::de::TEXT_KEY;
44
use crate::se::element::{ElementSerializer, Struct, Tuple};
55
use crate::se::simple_type::{QuoteTarget, SimpleTypeSerializer};
6-
use crate::se::{Indent, QuoteLevel, SeError, TextFormat, WriteResult, XmlName};
6+
use crate::se::{
7+
EmptyElementHandling, Indent, QuoteLevel, SeError, TextFormat, WriteResult, XmlName,
8+
};
79
use serde::ser::{
810
Impossible, Serialize, SerializeSeq, SerializeTuple, SerializeTupleStruct, Serializer,
911
};
@@ -80,9 +82,8 @@ pub struct ContentSerializer<'w, 'i, W: Write> {
8082
/// as a text that makes it impossible to distinguish between them during
8183
/// deserialization. Instead of ambiguous serialization the error is returned.
8284
pub allow_primitive: bool,
83-
// If `true`, then empty elements will be serialized as `<element></element>`
84-
// instead of `<element/>`.
85-
pub expand_empty_elements: bool,
85+
/// Specifies how empty elements are written.
86+
pub empty_element_handling: EmptyElementHandling,
8687
}
8788

8889
impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> {
@@ -124,25 +125,35 @@ impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> {
124125
write_indent: self.write_indent,
125126
text_format: self.text_format,
126127
allow_primitive,
127-
expand_empty_elements: self.expand_empty_elements,
128+
empty_element_handling: self.empty_element_handling,
128129
}
129130
}
130131

131132
/// Writes `name` as self-closed tag
132133
#[inline]
133134
pub(super) fn write_empty(mut self, name: XmlName) -> Result<WriteResult, SeError> {
134135
self.write_indent()?;
135-
if self.expand_empty_elements {
136-
self.writer.write_char('<')?;
137-
self.writer.write_str(name.0)?;
138-
self.writer.write_str("></")?;
139-
self.writer.write_str(name.0)?;
140-
self.writer.write_char('>')?;
141-
} else {
142-
self.writer.write_str("<")?;
143-
self.writer.write_str(name.0)?;
144-
self.writer.write_str("/>")?;
136+
137+
match self.empty_element_handling {
138+
EmptyElementHandling::SelfClosed => {
139+
self.writer.write_char('<')?;
140+
self.writer.write_str(name.0)?;
141+
self.writer.write_str("/>")?;
142+
}
143+
EmptyElementHandling::SelfClosedWithSpace => {
144+
self.writer.write_char('<')?;
145+
self.writer.write_str(name.0)?;
146+
self.writer.write_str(" />")?;
147+
}
148+
EmptyElementHandling::Expanded => {
149+
self.writer.write_char('<')?;
150+
self.writer.write_str(name.0)?;
151+
self.writer.write_str("></")?;
152+
self.writer.write_str(name.0)?;
153+
self.writer.write_char('>')?;
154+
}
145155
}
156+
146157
Ok(WriteResult::Element)
147158
}
148159

@@ -604,7 +615,7 @@ pub(super) mod tests {
604615
write_indent: false,
605616
text_format: TextFormat::Text,
606617
allow_primitive: true,
607-
expand_empty_elements: false,
618+
empty_element_handling: EmptyElementHandling::SelfClosed,
608619
};
609620

610621
let result = $data.serialize(ser).unwrap();
@@ -628,7 +639,7 @@ pub(super) mod tests {
628639
write_indent: false,
629640
text_format: TextFormat::Text,
630641
allow_primitive: true,
631-
expand_empty_elements: false,
642+
empty_element_handling: EmptyElementHandling::SelfClosed,
632643
};
633644

634645
match $data.serialize(ser).unwrap_err() {
@@ -1070,7 +1081,7 @@ pub(super) mod tests {
10701081
write_indent: false,
10711082
text_format: TextFormat::Text,
10721083
allow_primitive: true,
1073-
expand_empty_elements: false,
1084+
empty_element_handling: EmptyElementHandling::SelfClosed,
10741085
};
10751086

10761087
let result = $data.serialize(ser).unwrap();
@@ -1094,7 +1105,7 @@ pub(super) mod tests {
10941105
write_indent: false,
10951106
text_format: TextFormat::Text,
10961107
allow_primitive: true,
1097-
expand_empty_elements: false,
1108+
empty_element_handling: EmptyElementHandling::SelfClosed,
10981109
};
10991110

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

src/se/element.rs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::se::content::ContentSerializer;
55
use crate::se::key::QNameSerializer;
66
use crate::se::simple_type::{QuoteTarget, SimpleSeq, SimpleTypeSerializer};
77
use crate::se::text::TextSerializer;
8-
use crate::se::{SeError, WriteResult, XmlName};
8+
use crate::se::{EmptyElementHandling, SeError, WriteResult, XmlName};
99
use serde::ser::{
1010
Impossible, Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant,
1111
SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, Serializer,
@@ -442,7 +442,7 @@ impl<'w, 'k, W: Write> Struct<'w, 'k, W> {
442442
write_indent: self.write_indent,
443443
text_format: self.ser.ser.text_format,
444444
allow_primitive: true,
445-
expand_empty_elements: self.ser.ser.expand_empty_elements,
445+
empty_element_handling: self.ser.ser.empty_element_handling,
446446
};
447447

448448
if key == TEXT_KEY {
@@ -479,12 +479,18 @@ impl<'w, 'k, W: Write> SerializeStruct for Struct<'w, 'k, W> {
479479
self.ser.ser.indent.decrease();
480480

481481
if self.children.is_empty() {
482-
if self.ser.ser.expand_empty_elements {
483-
self.ser.ser.writer.write_str("></")?;
484-
self.ser.ser.writer.write_str(self.ser.key.0)?;
485-
self.ser.ser.writer.write_char('>')?;
486-
} else {
487-
self.ser.ser.writer.write_str("/>")?;
482+
match self.ser.ser.empty_element_handling {
483+
EmptyElementHandling::SelfClosed => {
484+
self.ser.ser.writer.write_str("/>")?;
485+
}
486+
EmptyElementHandling::SelfClosedWithSpace => {
487+
self.ser.ser.writer.write_str(" />")?;
488+
}
489+
EmptyElementHandling::Expanded => {
490+
self.ser.ser.writer.write_str("></")?;
491+
self.ser.ser.writer.write_str(self.ser.key.0)?;
492+
self.ser.ser.writer.write_char('>')?;
493+
}
488494
}
489495
} else {
490496
self.ser.ser.writer.write_char('>')?;
@@ -635,7 +641,7 @@ mod tests {
635641
write_indent: false,
636642
text_format: TextFormat::Text,
637643
allow_primitive: true,
638-
expand_empty_elements: false,
644+
empty_element_handling: EmptyElementHandling::SelfClosed,
639645
},
640646
key: XmlName("root"),
641647
};
@@ -662,7 +668,7 @@ mod tests {
662668
write_indent: false,
663669
text_format: TextFormat::Text,
664670
allow_primitive: true,
665-
expand_empty_elements: false,
671+
empty_element_handling: EmptyElementHandling::SelfClosed,
666672
},
667673
key: XmlName("root"),
668674
};
@@ -1348,7 +1354,7 @@ mod tests {
13481354
write_indent: false,
13491355
text_format: TextFormat::Text,
13501356
allow_primitive: true,
1351-
expand_empty_elements: false,
1357+
empty_element_handling: EmptyElementHandling::SelfClosed,
13521358
},
13531359
key: XmlName("root"),
13541360
};
@@ -1375,7 +1381,7 @@ mod tests {
13751381
write_indent: false,
13761382
text_format: TextFormat::Text,
13771383
allow_primitive: true,
1378-
expand_empty_elements: false,
1384+
empty_element_handling: EmptyElementHandling::SelfClosed,
13791385
},
13801386
key: XmlName("root"),
13811387
};
@@ -2083,7 +2089,7 @@ mod tests {
20832089
write_indent: false,
20842090
text_format: TextFormat::Text,
20852091
allow_primitive: true,
2086-
expand_empty_elements: true,
2092+
empty_element_handling: EmptyElementHandling::Expanded,
20872093
},
20882094
key: XmlName("root"),
20892095
};

src/se/mod.rs

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,26 @@ pub enum QuoteLevel {
373373
Minimal,
374374
}
375375

376+
/// Specifies how empty elements are serialized.
377+
/// The default is self-closed with no space before the slash.
378+
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
379+
pub enum EmptyElementHandling {
380+
/// Empty elements will be written as `<element/>`. This is the default behavior.
381+
#[default]
382+
SelfClosed,
383+
384+
/// The same, as [`SelfClosed`], but an extra space will be written before
385+
/// the slash, so empty elements will be written as `<element />`.
386+
/// This is recommended by the [W3C guidelines] for XHTML.
387+
///
388+
/// [`SelfClosed`]: Self::SelfClosed
389+
/// [W3C guidelines]: https://www.w3.org/TR/xhtml1/#guidelines
390+
SelfClosedWithSpace,
391+
392+
/// Empty elements will be expanded, as in: `<element></element>`.
393+
Expanded,
394+
}
395+
376396
/// Classification of the type written by the serializer.
377397
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
378398
pub enum WriteResult {
@@ -568,7 +588,7 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> {
568588
write_indent: false,
569589
text_format: TextFormat::Text,
570590
allow_primitive: true,
571-
expand_empty_elements: false,
591+
empty_element_handling: EmptyElementHandling::SelfClosed,
572592
},
573593
root_tag: None,
574594
}
@@ -635,21 +655,80 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> {
635655
write_indent: false,
636656
text_format: TextFormat::Text,
637657
allow_primitive: true,
638-
expand_empty_elements: false,
658+
empty_element_handling: EmptyElementHandling::SelfClosed,
639659
},
640660
root_tag: root_tag.map(XmlName::try_from).transpose()?,
641661
})
642662
}
643663

644-
/// Enable or disable expansion of empty elements. Defaults to `false`.
664+
/// Enable or disable expansion of empty elements. Defaults to [`EmptyElementHandling::SelfClosed`].
645665
///
646666
/// # Examples
647667
///
648668
/// ```
649669
/// # use pretty_assertions::assert_eq;
650670
/// # use serde::Serialize;
651-
/// # use quick_xml::se::Serializer;
671+
/// # use quick_xml::se::{Serializer, EmptyElementHandling};
672+
/// #
673+
/// #[derive(Debug, PartialEq, Serialize)]
674+
/// struct Struct {
675+
/// question: Option<String>,
676+
/// }
677+
///
678+
/// let data = Struct {
679+
/// question: None,
680+
/// };
681+
///
682+
/// {
683+
/// let mut buffer = String::new();
684+
/// let mut ser = Serializer::new(&mut buffer);
685+
/// ser.empty_element_handling(EmptyElementHandling::SelfClosed);
686+
/// data.serialize(ser).unwrap();
687+
/// assert_eq!(
688+
/// buffer,
689+
/// "<Struct><question/></Struct>"
690+
/// );
691+
/// }
692+
///
693+
/// {
694+
/// let mut buffer = String::new();
695+
/// let mut ser = Serializer::new(&mut buffer);
696+
/// ser.empty_element_handling(EmptyElementHandling::SelfClosedWithSpace);
697+
/// data.serialize(ser).unwrap();
698+
/// assert_eq!(
699+
/// buffer,
700+
/// "<Struct><question /></Struct>"
701+
/// );
702+
/// }
652703
///
704+
/// {
705+
/// let mut buffer = String::new();
706+
/// let mut ser = Serializer::new(&mut buffer);
707+
/// ser.empty_element_handling(EmptyElementHandling::Expanded);
708+
/// data.serialize(ser).unwrap();
709+
/// assert_eq!(
710+
/// buffer,
711+
/// "<Struct><question></question></Struct>"
712+
/// );
713+
/// }
714+
/// ```
715+
pub fn empty_element_handling(&mut self, handling: EmptyElementHandling) -> &mut Self {
716+
self.ser.empty_element_handling = handling;
717+
self
718+
}
719+
720+
/// Enable or disable expansion of empty elements (without adding space before `/>`).
721+
///
722+
/// This is the historycally first way to configure empty element handling. You can use
723+
/// [`empty_element_handling`](Self::empty_element_handling) for more control.
724+
///
725+
/// # Examples
726+
///
727+
/// ```
728+
/// # use pretty_assertions::assert_eq;
729+
/// # use serde::Serialize;
730+
/// # use quick_xml::se::Serializer;
731+
/// #
653732
/// #[derive(Debug, PartialEq, Serialize)]
654733
/// struct Struct {
655734
/// question: Option<String>,
@@ -660,7 +739,7 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> {
660739
/// ser.expand_empty_elements(true);
661740
///
662741
/// let data = Struct {
663-
/// question: None,
742+
/// question: None,
664743
/// };
665744
///
666745
/// data.serialize(ser).unwrap();
@@ -670,8 +749,11 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> {
670749
/// );
671750
/// ```
672751
pub fn expand_empty_elements(&mut self, expand: bool) -> &mut Self {
673-
self.ser.expand_empty_elements = expand;
674-
self
752+
self.empty_element_handling(if expand {
753+
EmptyElementHandling::Expanded
754+
} else {
755+
EmptyElementHandling::SelfClosed
756+
})
675757
}
676758

677759
/// Set the text format used for serializing text content.

0 commit comments

Comments
 (0)