diff --git a/Changelog.md b/Changelog.md index 49892f20..e0a01192 100644 --- a/Changelog.md +++ b/Changelog.md @@ -14,12 +14,14 @@ - [#601]: Add `serde_helper` module to the crate root with some useful utility functions and document using of enum's unit variants as a text content of element. +- [#606]: Implement indentation for `AsyncWrite` trait implementations. ### Bug Fixes ### Misc Changes [#601]: https://github.com/tafia/quick-xml/pull/601 +[#606]: https://github.com/tafia/quick-xml/pull/606 ## 0.28.2 -- 2023-04-12 diff --git a/src/writer.rs b/src/writer.rs index f9ad7988..570c85cb 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -71,6 +71,14 @@ impl Writer { } } + /// Creates a `Writer` with configured indents from a generic writer. + pub fn new_with_indent(inner: W, indent_char: u8, indent_size: usize) -> Writer { + Writer { + writer: inner, + indent: Some(Indentation::new(indent_char, indent_size)), + } + } + /// Consumes this `Writer`, returning the underlying writer. pub fn into_inner(self) -> W { self.writer @@ -88,14 +96,6 @@ impl Writer { } impl Writer { - /// Creates a `Writer` with configured whitespace indents from a generic writer. - pub fn new_with_indent(inner: W, indent_char: u8, indent_size: usize) -> Writer { - Writer { - writer: inner, - indent: Some(Indentation::new(indent_char, indent_size)), - } - } - /// Write a [Byte-Order-Mark] character to the document. /// /// # Example diff --git a/src/writer/async_tokio.rs b/src/writer/async_tokio.rs index 9865fb4a..392e75c1 100644 --- a/src/writer/async_tokio.rs +++ b/src/writer/async_tokio.rs @@ -7,13 +7,29 @@ use crate::Writer; impl Writer { /// Writes the given event to the underlying writer. Async version of [`Writer::write_event`]. pub async fn write_event_async<'a, E: AsRef>>(&mut self, event: E) -> Result<()> { - match *event.as_ref() { - Event::Start(ref e) => self.write_wrapped_async(b"<", e, b">").await, - Event::End(ref e) => self.write_wrapped_async(b"").await, + let mut next_should_line_break = true; + let result = match *event.as_ref() { + Event::Start(ref e) => { + let result = self.write_wrapped_async(b"<", e, b">").await; + if let Some(i) = self.indent.as_mut() { + i.grow(); + } + result + } + Event::End(ref e) => { + if let Some(i) = self.indent.as_mut() { + i.shrink(); + } + self.write_wrapped_async(b"").await + } Event::Empty(ref e) => self.write_wrapped_async(b"<", e, b"/>").await, - Event::Text(ref e) => self.write_async(e).await, + Event::Text(ref e) => { + next_should_line_break = false; + self.write_async(e).await + } Event::Comment(ref e) => self.write_wrapped_async(b"").await, Event::CData(ref e) => { + next_should_line_break = false; self.write_async(b"").await @@ -22,7 +38,23 @@ impl Writer { Event::PI(ref e) => self.write_wrapped_async(b"").await, Event::DocType(ref e) => self.write_wrapped_async(b"").await, Event::Eof => Ok(()), + }; + if let Some(i) = self.indent.as_mut() { + i.should_line_break = next_should_line_break; + } + result + } + + /// Manually write a newline and indentation at the proper level. Async version of + /// [`Writer::write_indent`]. + /// + /// This method will do nothing if `Writer` was not constructed with [`Writer::new_with_indent`]. + pub async fn write_indent_async(&mut self) -> Result<()> { + if let Some(ref i) = self.indent { + self.writer.write_all(b"\n").await?; + self.writer.write_all(i.current()).await?; } + Ok(()) } #[inline] @@ -37,6 +69,12 @@ impl Writer { value: &[u8], after: &[u8], ) -> Result<()> { + if let Some(ref i) = self.indent { + if i.should_line_break { + self.writer.write_all(b"\n").await?; + self.writer.write_all(i.current()).await?; + } + } self.write_async(before).await?; self.write_async(value).await?; self.write_async(after).await?; @@ -117,3 +155,188 @@ mod tests { ); } } + +#[cfg(test)] +mod indentation_async { + use super::*; + use crate::events::*; + use pretty_assertions::assert_eq; + + #[tokio::test] + async fn self_closed() { + let mut buffer = Vec::new(); + let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); + + let tag = BytesStart::new("self-closed") + .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()); + writer + .write_event_async(Event::Empty(tag)) + .await + .expect("write tag failed"); + + assert_eq!( + std::str::from_utf8(&buffer).unwrap(), + r#""# + ); + } + + #[tokio::test] + async fn empty_paired() { + let mut buffer = Vec::new(); + let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); + + let start = BytesStart::new("paired") + .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()); + let end = start.to_end(); + writer + .write_event_async(Event::Start(start.clone())) + .await + .expect("write start tag failed"); + writer + .write_event_async(Event::End(end)) + .await + .expect("write end tag failed"); + + assert_eq!( + std::str::from_utf8(&buffer).unwrap(), + r#" +"# + ); + } + + #[tokio::test] + async fn paired_with_inner() { + let mut buffer = Vec::new(); + let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); + + let start = BytesStart::new("paired") + .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()); + let end = start.to_end(); + let inner = BytesStart::new("inner"); + + writer + .write_event_async(Event::Start(start.clone())) + .await + .expect("write start tag failed"); + writer + .write_event_async(Event::Empty(inner)) + .await + .expect("write inner tag failed"); + writer + .write_event_async(Event::End(end)) + .await + .expect("write end tag failed"); + + assert_eq!( + std::str::from_utf8(&buffer).unwrap(), + r#" + +"# + ); + } + + #[tokio::test] + async fn paired_with_text() { + let mut buffer = Vec::new(); + let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); + + let start = BytesStart::new("paired") + .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()); + let end = start.to_end(); + let text = BytesText::new("text"); + + writer + .write_event_async(Event::Start(start.clone())) + .await + .expect("write start tag failed"); + writer + .write_event_async(Event::Text(text)) + .await + .expect("write text failed"); + writer + .write_event_async(Event::End(end)) + .await + .expect("write end tag failed"); + + assert_eq!( + std::str::from_utf8(&buffer).unwrap(), + r#"text"# + ); + } + + #[tokio::test] + async fn mixed_content() { + let mut buffer = Vec::new(); + let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); + + let start = BytesStart::new("paired") + .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()); + let end = start.to_end(); + let text = BytesText::new("text"); + let inner = BytesStart::new("inner"); + + writer + .write_event_async(Event::Start(start.clone())) + .await + .expect("write start tag failed"); + writer + .write_event_async(Event::Text(text)) + .await + .expect("write text failed"); + writer + .write_event_async(Event::Empty(inner)) + .await + .expect("write inner tag failed"); + writer + .write_event_async(Event::End(end)) + .await + .expect("write end tag failed"); + + assert_eq!( + std::str::from_utf8(&buffer).unwrap(), + r#"text +"# + ); + } + + #[tokio::test] + async fn nested() { + let mut buffer = Vec::new(); + let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); + + let start = BytesStart::new("paired") + .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()); + let end = start.to_end(); + let inner = BytesStart::new("inner"); + + writer + .write_event_async(Event::Start(start.clone())) + .await + .expect("write start 1 tag failed"); + writer + .write_event_async(Event::Start(start.clone())) + .await + .expect("write start 2 tag failed"); + writer + .write_event_async(Event::Empty(inner)) + .await + .expect("write inner tag failed"); + writer + .write_event_async(Event::End(end.clone())) + .await + .expect("write end tag 2 failed"); + writer + .write_event_async(Event::End(end)) + .await + .expect("write end tag 1 failed"); + + assert_eq!( + std::str::from_utf8(&buffer).unwrap(), + r#" + + + +"# + ); + } +}