Skip to content

Commit 909cd12

Browse files
Rémi Labeyriedralley
Rémi Labeyrie
authored andcommitted
Add Writer::write_serializable
Allow serializing individual objects using serde with the raw Writer API closes #610
1 parent 5a536d0 commit 909cd12

File tree

3 files changed

+176
-11
lines changed

3 files changed

+176
-11
lines changed

Changelog.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,17 @@
1212

1313
### New Features
1414

15+
- [#609]: Added `Writer::write_serializable` to provide the capability to serialize
16+
arbitrary types using serde when using the lower-level `Writer` API.
17+
1518
### Bug Fixes
1619

1720
### Misc Changes
1821

1922

23+
[#609]: https://github.com/tafia/quick-xml/issues/609
24+
25+
2026
## 0.29.0 -- 2023-06-13
2127

2228
### New Features
@@ -28,7 +34,7 @@
2834
### Bug Fixes
2935

3036
- [#603]: Fix a regression from [#581] that an XML comment or a processing
31-
instruction between a <!DOCTYPE> and the root element in the file brokes
37+
instruction between a <!DOCTYPE> and the root element in the file broke
3238
deserialization of structs by returning `DeError::ExpectedStart`
3339
- [#608]: Return a new error `Error::EmptyDocType` on empty doctype instead
3440
of crashing because of a debug assertion.
@@ -86,8 +92,8 @@
8692
to trim leading and trailing spaces from text events
8793
- [#565]: Allow deserialize special field names `$value` and `$text` into borrowed
8894
fields when use serde deserializer
89-
- [#568]: Rename `Writter::inner` into `Writter::get_mut`
90-
- [#568]: Add method `Writter::get_ref`
95+
- [#568]: Rename `Writer::inner` into `Writer::get_mut`
96+
- [#568]: Add method `Writer::get_ref`
9197
- [#569]: Rewrite the `Reader::read_event_into_async` as an async fn, making the future `Send` if possible.
9298
- [#571]: Borrow element names (`<element>`) when deserialize with serde.
9399
This change allow to deserialize into `HashMap<&str, T>`, for example

src/se/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,12 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> {
529529

530530
/// Configure indent for a serializer
531531
pub fn indent(&mut self, indent_char: char, indent_size: usize) -> &mut Self {
532-
self.ser.indent = Indent::Owned(Indentation::new(indent_char as u8, indent_size));
532+
self.set_indent(Indentation::new(indent_char as u8, indent_size))
533+
}
534+
535+
/// Configure indent object for a serializer
536+
pub(crate) fn set_indent(&mut self, indent: Indentation) -> &mut Self {
537+
self.ser.indent = Indent::Owned(indent);
533538
self
534539
}
535540

src/writer.rs

Lines changed: 161 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ use crate::events::{attributes::Attribute, BytesCData, BytesStart, BytesText, Ev
1010
mod async_tokio;
1111

1212
/// XML writer. Writes XML [`Event`]s to a [`std::io::Write`] or [`tokio::io::AsyncWrite`] implementor.
13+
#[cfg(feature = "serialize")]
14+
use {crate::de::DeError, serde::Serialize};
15+
16+
/// XML writer. Writes XML [`Event`]s to a [`std::io::Write`] implementor.
1317
///
1418
/// # Examples
1519
///
@@ -261,6 +265,73 @@ impl<W: Write> Writer<W> {
261265
start_tag: BytesStart::new(name.as_ref()),
262266
}
263267
}
268+
269+
/// Write an arbitrary serializable type
270+
///
271+
/// Note: If you are attempting to write XML in a non-UTF-8 encoding, this may not
272+
/// be safe to use. Rust basic types assume UTF-8 encodings.
273+
///
274+
/// ```rust
275+
/// # use pretty_assertions::assert_eq;
276+
/// # use serde::Serialize;
277+
/// # use quick_xml::events::{BytesStart, Event};
278+
/// # use quick_xml::writer::Writer;
279+
/// # use quick_xml::DeError;
280+
/// # fn main() -> Result<(), DeError> {
281+
/// #[derive(Debug, PartialEq, Serialize)]
282+
/// struct MyData {
283+
/// question: String,
284+
/// answer: u32,
285+
/// }
286+
///
287+
/// let data = MyData {
288+
/// question: "The Ultimate Question of Life, the Universe, and Everything".into(),
289+
/// answer: 42,
290+
/// };
291+
///
292+
/// let mut buffer = Vec::new();
293+
/// let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);
294+
///
295+
/// let start = BytesStart::new("root");
296+
/// let end = start.to_end();
297+
///
298+
/// writer.write_event(Event::Start(start.clone()))?;
299+
/// writer.write_serializable("my_data", &data)?;
300+
/// writer.write_event(Event::End(end))?;
301+
///
302+
/// assert_eq!(
303+
/// std::str::from_utf8(&buffer)?,
304+
/// r#"<root>
305+
/// <my_data>
306+
/// <question>The Ultimate Question of Life, the Universe, and Everything</question>
307+
/// <answer>42</answer>
308+
/// </my_data>
309+
/// </root>"#
310+
/// );
311+
/// # Ok(())
312+
/// # }
313+
/// ```
314+
#[cfg(feature = "serialize")]
315+
pub fn write_serializable<T: Serialize>(
316+
&mut self,
317+
tag_name: &str,
318+
content: &T,
319+
) -> std::result::Result<(), DeError> {
320+
use crate::se::Serializer;
321+
322+
self.write_indent()?;
323+
let indent = self.indent.clone();
324+
let mut fmt = ToFmtWrite(self.get_mut());
325+
let mut serializer = Serializer::with_root(&mut fmt, Some(tag_name))?;
326+
327+
if let Some(indent) = indent {
328+
serializer.set_indent(indent);
329+
}
330+
331+
content.serialize(serializer)?;
332+
333+
Ok(())
334+
}
264335
}
265336

266337
/// A struct to write an element. Contains methods to add attributes and inner
@@ -341,14 +412,31 @@ impl<'a, W: Write> ElementWriter<'a, W> {
341412
Ok(self.writer)
342413
}
343414
}
415+
#[cfg(feature = "serialize")]
416+
struct ToFmtWrite<T>(pub T);
417+
418+
#[cfg(feature = "serialize")]
419+
impl<T> std::fmt::Write for ToFmtWrite<T>
420+
where
421+
T: std::io::Write,
422+
{
423+
fn write_str(&mut self, s: &str) -> std::fmt::Result {
424+
self.0.write_all(s.as_bytes()).map_err(|_| std::fmt::Error)
425+
}
426+
}
344427

345428
#[derive(Clone)]
346429
pub(crate) struct Indentation {
430+
/// todo: does this even belong here? It has no impact on indentation logic.
347431
should_line_break: bool,
432+
/// The character code to be used for indentations (e.g. ` ` or `\t`)
348433
indent_char: u8,
434+
/// How many instances of the indent character ought to be used for each level of indentation
349435
indent_size: usize,
436+
/// Used as a cache for the bytes used for indentation
350437
indents: Vec<u8>,
351-
indents_len: usize,
438+
/// The current amount of indentation
439+
current_indent_len: usize,
352440
}
353441

354442
impl Indentation {
@@ -358,26 +446,27 @@ impl Indentation {
358446
indent_char,
359447
indent_size,
360448
indents: vec![indent_char; 128],
361-
indents_len: 0,
449+
current_indent_len: 0, // invariant - needs to remain less than indents.len()
362450
}
363451
}
364452

365453
/// Increase indentation by one level
366454
pub fn grow(&mut self) {
367-
self.indents_len += self.indent_size;
368-
if self.indents_len > self.indents.len() {
369-
self.indents.resize(self.indents_len, self.indent_char);
455+
self.current_indent_len += self.indent_size;
456+
if self.current_indent_len > self.indents.len() {
457+
self.indents
458+
.resize(self.current_indent_len, self.indent_char);
370459
}
371460
}
372461

373462
/// Decrease indentation by one level. Do nothing, if level already zero
374463
pub fn shrink(&mut self) {
375-
self.indents_len = self.indents_len.saturating_sub(self.indent_size);
464+
self.current_indent_len = self.current_indent_len.saturating_sub(self.indent_size);
376465
}
377466

378467
/// Returns indent string for current level
379468
pub fn current(&self) -> &[u8] {
380-
&self.indents[..self.indents_len]
469+
&self.indents[..self.current_indent_len]
381470
}
382471
}
383472

@@ -547,6 +636,71 @@ mod indentation {
547636
);
548637
}
549638

639+
#[cfg(feature = "serialize")]
640+
#[test]
641+
fn serializable() {
642+
#[derive(Serialize)]
643+
struct Foo {
644+
#[serde(rename = "@attribute")]
645+
attribute: &'static str,
646+
647+
element: Bar,
648+
list: Vec<&'static str>,
649+
650+
#[serde(rename = "$text")]
651+
text: &'static str,
652+
653+
val: String,
654+
}
655+
656+
#[derive(Serialize)]
657+
struct Bar {
658+
baz: usize,
659+
bat: usize,
660+
}
661+
662+
let mut buffer = Vec::new();
663+
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);
664+
665+
let content = Foo {
666+
attribute: "attribute",
667+
element: Bar { baz: 42, bat: 43 },
668+
list: vec!["first element", "second element"],
669+
text: "text",
670+
val: "foo".to_owned(),
671+
};
672+
673+
let start = BytesStart::new("paired")
674+
.with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter());
675+
let end = start.to_end();
676+
677+
writer
678+
.write_event(Event::Start(start.clone()))
679+
.expect("write start tag failed");
680+
writer
681+
.write_serializable("foo_element", &content)
682+
.expect("write serializable inner contents failed");
683+
writer
684+
.write_event(Event::End(end))
685+
.expect("write end tag failed");
686+
687+
assert_eq!(
688+
std::str::from_utf8(&buffer).unwrap(),
689+
r#"<paired attr1="value1" attr2="value2">
690+
<foo_element attribute="attribute">
691+
<element>
692+
<baz>42</baz>
693+
<bat>43</bat>
694+
</element>
695+
<list>first element</list>
696+
<list>second element</list>
697+
text
698+
<val>foo</val>
699+
</foo_element>
700+
</paired>"#
701+
);
702+
}
703+
550704
#[test]
551705
fn element_writer_empty() {
552706
let mut buffer = Vec::new();

0 commit comments

Comments
 (0)