Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions src/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ impl<W> Writer<W> {
}
}

/// Creates a `Writer` with configured indents from a generic writer.
pub fn new_with_indent(inner: W, indent_char: u8, indent_size: usize) -> Writer<W> {
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
Expand All @@ -88,14 +96,6 @@ impl<W> Writer<W> {
}

impl<W: Write> Writer<W> {
/// 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<W> {
Writer {
writer: inner,
indent: Some(Indentation::new(indent_char, indent_size)),
}
}

/// Write a [Byte-Order-Mark] character to the document.
///
/// # Example
Expand Down
231 changes: 227 additions & 4 deletions src/writer/async_tokio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,29 @@ use crate::Writer;
impl<W: AsyncWrite + Unpin> Writer<W> {
/// Writes the given event to the underlying writer. Async version of [`Writer::write_event`].
pub async fn write_event_async<'a, E: AsRef<Event<'a>>>(&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"</", e, 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"</", e, 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"<!--", e, b"-->").await,
Event::CData(ref e) => {
next_should_line_break = false;
self.write_async(b"<![CDATA[").await?;
self.write_async(e).await?;
self.write_async(b"]]>").await
Expand All @@ -22,7 +38,23 @@ impl<W: AsyncWrite + Unpin> Writer<W> {
Event::PI(ref e) => self.write_wrapped_async(b"<?", e, b"?>").await,
Event::DocType(ref e) => self.write_wrapped_async(b"<!DOCTYPE ", e, 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]
Expand All @@ -37,6 +69,12 @@ impl<W: AsyncWrite + Unpin> Writer<W> {
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?;
Expand Down Expand Up @@ -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#"<self-closed attr1="value1" attr2="value2"/>"#
);
}

#[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#"<paired attr1="value1" attr2="value2">
</paired>"#
);
}

#[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#"<paired attr1="value1" attr2="value2">
<inner/>
</paired>"#
);
}

#[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#"<paired attr1="value1" attr2="value2">text</paired>"#
);
}

#[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#"<paired attr1="value1" attr2="value2">text<inner/>
</paired>"#
);
}

#[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#"<paired attr1="value1" attr2="value2">
<paired attr1="value1" attr2="value2">
<inner/>
</paired>
</paired>"#
);
}
}