From dbd107613c080e1378631ccfbbd09f8b1fd931a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Labeyrie?= Date: Sun, 13 Nov 2022 03:40:02 +0100 Subject: [PATCH] Add Writer::write_serializable Allow serializing individual objects using serde with the raw Writer API closes #610 --- Changelog.md | 13 ++-- src/se/mod.rs | 7 ++- src/writer.rs | 166 +++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 174 insertions(+), 12 deletions(-) diff --git a/Changelog.md b/Changelog.md index e0ebd96a..14bde1a5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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 @@ -28,7 +34,7 @@ ### Bug Fixes - [#603]: Fix a regression from [#581] that an XML comment or a processing - instruction between a and the root element in the file brokes + instruction between a 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. @@ -45,7 +51,6 @@ [#606]: https://github.com/tafia/quick-xml/pull/606 [#608]: https://github.com/tafia/quick-xml/issues/608 - ## 0.28.2 -- 2023-04-12 ### New Features @@ -86,8 +91,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 (``) when deserialize with serde. This change allow to deserialize into `HashMap<&str, T>`, for example diff --git a/src/se/mod.rs b/src/se/mod.rs index 9a03eec5..af1bb663 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -529,7 +529,12 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> { /// Configure indent for a serializer pub fn indent(&mut self, indent_char: char, indent_size: usize) -> &mut Self { - self.ser.indent = Indent::Owned(Indentation::new(indent_char as u8, indent_size)); + self.set_indent(Indentation::new(indent_char as u8, indent_size)) + } + + /// Configure indent object for a serializer + pub(crate) fn set_indent(&mut self, indent: Indentation) -> &mut Self { + self.ser.indent = Indent::Owned(indent); self } diff --git a/src/writer.rs b/src/writer.rs index 570c85cb..49d87b95 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -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 /// @@ -261,6 +265,71 @@ impl Writer { start_tag: BytesStart::new(name.as_ref()), } } + + /// Write an arbitrary serializable type + /// + /// ```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#" + /// + /// The Ultimate Question of Life, the Universe, and Everything + /// 42 + /// + /// "# + /// ); + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "serialize")] + pub fn write_serializable( + &mut self, + tag_name: &str, + content: &T, + ) -> std::result::Result<(), DeError> { + use crate::se::Serializer; + + self.write_indent()?; + let indent = self.indent.clone(); + let mut fmt = ToFmtWrite(self.get_mut()); + let mut serializer = Serializer::with_root(&mut fmt, Some(tag_name))?; + + if let Some(indent) = indent { + serializer.set_indent(indent); + } + + content.serialize(serializer)?; + + Ok(()) + } } /// A struct to write an element. Contains methods to add attributes and inner @@ -341,14 +410,31 @@ impl<'a, W: Write> ElementWriter<'a, W> { Ok(self.writer) } } +#[cfg(feature = "serialize")] +struct ToFmtWrite(pub T); + +#[cfg(feature = "serialize")] +impl std::fmt::Write for ToFmtWrite +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, - indents_len: usize, + /// The current amount of indentation + current_indent_len: usize, } impl Indentation { @@ -358,26 +444,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] } } @@ -547,6 +634,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#" + + + 42 + 43 + + first element + second element + text + foo + +"# + ); + } + #[test] fn element_writer_empty() { let mut buffer = Vec::new();