Skip to content

Commit

Permalink
Add Writer::write_serializable
Browse files Browse the repository at this point in the history
Allow serializing individual objects using serde with the raw Writer API

closes #610
  • Loading branch information
Rémi Labeyrie authored and dralley committed Jun 21, 2023
1 parent 5a536d0 commit fcdcdf2
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 10 deletions.
12 changes: 9 additions & 3 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,17 @@

### New Features

- [#609]: Added `Writer::write_serializable` to provide the capability to serialize
arbitrary types using serde when using the lower-level `Writer` API.

### Bug Fixes

### Misc Changes


[#609]: https://github.com/tafia/quick-xml/issues/609


## 0.29.0 -- 2023-06-13

### New Features
Expand All @@ -28,7 +34,7 @@
### Bug Fixes

- [#603]: Fix a regression from [#581] that an XML comment or a processing
instruction between a <!DOCTYPE> and the root element in the file brokes
instruction between a <!DOCTYPE> and the root element in the file broke
deserialization of structs by returning `DeError::ExpectedStart`
- [#608]: Return a new error `Error::EmptyDocType` on empty doctype instead
of crashing because of a debug assertion.
Expand Down Expand Up @@ -86,8 +92,8 @@
to trim leading and trailing spaces from text events
- [#565]: Allow deserialize special field names `$value` and `$text` into borrowed
fields when use serde deserializer
- [#568]: Rename `Writter::inner` into `Writter::get_mut`
- [#568]: Add method `Writter::get_ref`
- [#568]: Rename `Writer::inner` into `Writer::get_mut`
- [#568]: Add method `Writer::get_ref`
- [#569]: Rewrite the `Reader::read_event_into_async` as an async fn, making the future `Send` if possible.
- [#571]: Borrow element names (`<element>`) when deserialize with serde.
This change allow to deserialize into `HashMap<&str, T>`, for example
Expand Down
6 changes: 6 additions & 0 deletions src/se/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,12 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> {
self
}

/// Set the indent object for a serializer
pub(crate) fn set_indent(&mut self, indent: Indent<'r>) -> &mut Self {
self.ser.indent = indent;
self
}

/// Creates actual serializer or returns an error if root tag is not defined.
/// In that case `err` contains the name of type that cannot be serialized.
fn ser(self, err: &str) -> Result<ElementSerializer<'w, 'r, W>, DeError> {
Expand Down
167 changes: 160 additions & 7 deletions src/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ use crate::events::{attributes::Attribute, BytesCData, BytesStart, BytesText, Ev
mod async_tokio;

/// XML writer. Writes XML [`Event`]s to a [`std::io::Write`] or [`tokio::io::AsyncWrite`] implementor.
#[cfg(feature = "serialize")]
use {crate::de::DeError, serde::Serialize};

/// XML writer. Writes XML [`Event`]s to a [`std::io::Write`] implementor.
///
/// # Examples
///
Expand Down Expand Up @@ -261,6 +265,72 @@ impl<W: Write> Writer<W> {
start_tag: BytesStart::new(name.as_ref()),
}
}

/// Write an arbitrary serializable type
///
/// Note: If you are attempting to write XML in a non-UTF-8 encoding, this may not
/// be safe to use. Rust basic types assume UTF-8 encodings.
///
/// ```rust
/// # use pretty_assertions::assert_eq;
/// # use serde::Serialize;
/// # use quick_xml::events::{BytesStart, Event};
/// # use quick_xml::writer::Writer;
/// # use quick_xml::DeError;
/// # fn main() -> Result<(), DeError> {
/// #[derive(Debug, PartialEq, Serialize)]
/// struct MyData {
/// question: String,
/// answer: u32,
/// }
///
/// let data = MyData {
/// question: "The Ultimate Question of Life, the Universe, and Everything".into(),
/// answer: 42,
/// };
///
/// let mut buffer = Vec::new();
/// let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);
///
/// let start = BytesStart::new("root");
/// let end = start.to_end();
///
/// writer.write_event(Event::Start(start.clone()))?;
/// writer.write_serializable("my_data", &data)?;
/// writer.write_event(Event::End(end))?;
///
/// assert_eq!(
/// std::str::from_utf8(&buffer)?,
/// r#"<root>
/// <my_data>
/// <question>The Ultimate Question of Life, the Universe, and Everything</question>
/// <answer>42</answer>
/// </my_data>
/// </root>"#
/// );
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "serialize")]
pub fn write_serializable<T: Serialize>(
&mut self,
tag_name: &str,
content: &T,
) -> std::result::Result<(), DeError> {
use crate::se::{Indent, Serializer};

self.write_indent()?;
let mut fmt = ToFmtWrite(&mut self.writer);
let mut serializer = Serializer::with_root(&mut fmt, Some(tag_name))?;

if let Some(indent) = &mut self.indent {
serializer.set_indent(Indent::Borrow(indent));
}

content.serialize(serializer)?;

Ok(())
}
}

/// A struct to write an element. Contains methods to add attributes and inner
Expand Down Expand Up @@ -341,14 +411,31 @@ impl<'a, W: Write> ElementWriter<'a, W> {
Ok(self.writer)
}
}
#[cfg(feature = "serialize")]
struct ToFmtWrite<T>(pub T);

#[cfg(feature = "serialize")]
impl<T> std::fmt::Write for ToFmtWrite<T>
where
T: std::io::Write,
{
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.0.write_all(s.as_bytes()).map_err(|_| std::fmt::Error)
}
}

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

impl Indentation {
Expand All @@ -358,26 +445,27 @@ impl Indentation {
indent_char,
indent_size,
indents: vec![indent_char; 128],
indents_len: 0,
current_indent_len: 0, // invariant - needs to remain less than indents.len()
}
}

/// Increase indentation by one level
pub fn grow(&mut self) {
self.indents_len += self.indent_size;
if self.indents_len > self.indents.len() {
self.indents.resize(self.indents_len, self.indent_char);
self.current_indent_len += self.indent_size;
if self.current_indent_len > self.indents.len() {
self.indents
.resize(self.current_indent_len, self.indent_char);
}
}

/// Decrease indentation by one level. Do nothing, if level already zero
pub fn shrink(&mut self) {
self.indents_len = self.indents_len.saturating_sub(self.indent_size);
self.current_indent_len = self.current_indent_len.saturating_sub(self.indent_size);
}

/// Returns indent string for current level
pub fn current(&self) -> &[u8] {
&self.indents[..self.indents_len]
&self.indents[..self.current_indent_len]
}
}

Expand Down Expand Up @@ -547,6 +635,71 @@ mod indentation {
);
}

#[cfg(feature = "serialize")]
#[test]
fn serializable() {
#[derive(Serialize)]
struct Foo {
#[serde(rename = "@attribute")]
attribute: &'static str,

element: Bar,
list: Vec<&'static str>,

#[serde(rename = "$text")]
text: &'static str,

val: String,
}

#[derive(Serialize)]
struct Bar {
baz: usize,
bat: usize,
}

let mut buffer = Vec::new();
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);

let content = Foo {
attribute: "attribute",
element: Bar { baz: 42, bat: 43 },
list: vec!["first element", "second element"],
text: "text",
val: "foo".to_owned(),
};

let start = BytesStart::new("paired")
.with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter());
let end = start.to_end();

writer
.write_event(Event::Start(start.clone()))
.expect("write start tag failed");
writer
.write_serializable("foo_element", &content)
.expect("write serializable inner contents failed");
writer
.write_event(Event::End(end))
.expect("write end tag failed");

assert_eq!(
std::str::from_utf8(&buffer).unwrap(),
r#"<paired attr1="value1" attr2="value2">
<foo_element attribute="attribute">
<element>
<baz>42</baz>
<bat>43</bat>
</element>
<list>first element</list>
<list>second element</list>
text
<val>foo</val>
</foo_element>
</paired>"#
);
}

#[test]
fn element_writer_empty() {
let mut buffer = Vec::new();
Expand Down

0 comments on commit fcdcdf2

Please sign in to comment.