Skip to content

Commit fcdcdf2

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 fcdcdf2

File tree

3 files changed

+175
-10
lines changed

3 files changed

+175
-10
lines changed

Changelog.md

+9-3
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

+6
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,12 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> {
533533
self
534534
}
535535

536+
/// Set the indent object for a serializer
537+
pub(crate) fn set_indent(&mut self, indent: Indent<'r>) -> &mut Self {
538+
self.ser.indent = indent;
539+
self
540+
}
541+
536542
/// Creates actual serializer or returns an error if root tag is not defined.
537543
/// In that case `err` contains the name of type that cannot be serialized.
538544
fn ser(self, err: &str) -> Result<ElementSerializer<'w, 'r, W>, DeError> {

src/writer.rs

+160-7
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,72 @@ 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::{Indent, Serializer};
321+
322+
self.write_indent()?;
323+
let mut fmt = ToFmtWrite(&mut self.writer);
324+
let mut serializer = Serializer::with_root(&mut fmt, Some(tag_name))?;
325+
326+
if let Some(indent) = &mut self.indent {
327+
serializer.set_indent(Indent::Borrow(indent));
328+
}
329+
330+
content.serialize(serializer)?;
331+
332+
Ok(())
333+
}
264334
}
265335

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

345427
#[derive(Clone)]
346428
pub(crate) struct Indentation {
429+
/// todo: does this even belong here? It has no impact on indentation logic.
347430
should_line_break: bool,
431+
/// The character code to be used for indentations (e.g. ` ` or `\t`)
348432
indent_char: u8,
433+
/// How many instances of the indent character ought to be used for each level of indentation
349434
indent_size: usize,
435+
/// Used as a cache for the bytes used for indentation
350436
indents: Vec<u8>,
351-
indents_len: usize,
437+
/// The current amount of indentation
438+
current_indent_len: usize,
352439
}
353440

354441
impl Indentation {
@@ -358,26 +445,27 @@ impl Indentation {
358445
indent_char,
359446
indent_size,
360447
indents: vec![indent_char; 128],
361-
indents_len: 0,
448+
current_indent_len: 0, // invariant - needs to remain less than indents.len()
362449
}
363450
}
364451

365452
/// Increase indentation by one level
366453
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);
454+
self.current_indent_len += self.indent_size;
455+
if self.current_indent_len > self.indents.len() {
456+
self.indents
457+
.resize(self.current_indent_len, self.indent_char);
370458
}
371459
}
372460

373461
/// Decrease indentation by one level. Do nothing, if level already zero
374462
pub fn shrink(&mut self) {
375-
self.indents_len = self.indents_len.saturating_sub(self.indent_size);
463+
self.current_indent_len = self.current_indent_len.saturating_sub(self.indent_size);
376464
}
377465

378466
/// Returns indent string for current level
379467
pub fn current(&self) -> &[u8] {
380-
&self.indents[..self.indents_len]
468+
&self.indents[..self.current_indent_len]
381469
}
382470
}
383471

@@ -547,6 +635,71 @@ mod indentation {
547635
);
548636
}
549637

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

0 commit comments

Comments
 (0)