diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 91b96f9c..2a566f61 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,9 +16,13 @@ jobs: run: cargo build - name: Run tests (no features) run: cargo test + run: cargo test --no-default-features - name: Run tests (serialize) run: cargo test --features serialize - name: Run tests (encoding+serialize) run: cargo test --features encoding,serialize - name: Run tests (escape-html+serialize) run: cargo test --features escape-html,serialize + - name: Check fmt + run: cargo fmt -- --check + diff --git a/Cargo.toml b/Cargo.toml index 4dfe35ee..456d2a40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "quick-xml" version = "0.22.0" authors = ["Johann Tuffe "] description = "High performance xml reader and writer" +edition = "2018" documentation = "https://docs.rs/quick-xml" repository = "https://github.com/tafia/quick-xml" diff --git a/Changelog.md b/Changelog.md index 2c3ba716..587fdf42 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,12 @@ ## Unreleased +- style: convert to rust edition 2018 +- fix: don't encode multi byte escape characters as big endian +- feat: add `Writer::write_nested_event` +- feat: add `BytesStart::try_get_attribute` +- test: add more test on github actions + ## 0.22.0 - feat (breaking): Move html entity escape behind a `'escape-html'` feature to help with compilation diff --git a/compare/Cargo.toml b/compare/Cargo.toml index 4753b076..7654a74f 100644 --- a/compare/Cargo.toml +++ b/compare/Cargo.toml @@ -9,5 +9,5 @@ edition = "2018" [dev-dependencies] quick-xml = { path = "..", features = ["serialize"] } xml-rs = "0.8.0" -serde-xml-rs = "0.3.1" +serde-xml-rs = "0.4.1" serde = { version = "1.0.103", features = [ "derive" ] } diff --git a/src/errors.rs b/src/errors.rs index d5727eb0..ef7bacba 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -33,7 +33,7 @@ pub enum Error { /// Duplicate attribute DuplicatedAttribute(usize, usize), /// Escape error - EscapeError(::escape::EscapeError), + EscapeError(crate::escape::EscapeError), } impl From<::std::io::Error> for Error { @@ -52,10 +52,10 @@ impl From<::std::str::Utf8Error> for Error { } } -impl From<::escape::EscapeError> for Error { +impl From for Error { /// Creates a new `Error::EscapeError` from the given error #[inline] - fn from(error: ::escape::EscapeError) -> Error { + fn from(error: crate::escape::EscapeError) -> Error { Error::EscapeError(error) } } @@ -63,7 +63,7 @@ impl From<::escape::EscapeError> for Error { /// A specialized `Result` type where the error is hard-wired to [`Error`]. /// /// [`Error`]: enum.Error.html -pub type Result = ::std::result::Result; +pub type Result = std::result::Result; impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { diff --git a/src/escapei.rs b/src/escapei.rs index e4cf88a4..30236d6f 100644 --- a/src/escapei.rs +++ b/src/escapei.rs @@ -63,6 +63,7 @@ impl std::error::Error for EscapeError {} /// Escapes a `&[u8]` and replaces all xml special characters (<, >, &, ', ") with their /// corresponding xml escaped value. pub fn escape(raw: &[u8]) -> Cow<[u8]> { + #[inline] fn to_escape(b: u8) -> bool { match b { b'<' | b'>' | b'\'' | b'&' | b'"' => true, @@ -70,10 +71,32 @@ pub fn escape(raw: &[u8]) -> Cow<[u8]> { } } + _escape(raw, to_escape) +} + +/// Should only be used for escaping text content. In xml text content, it is allowed +/// (though not recommended) to leave the quote special characters " and ' unescaped. +/// This function escapes a `&[u8]` and replaces xml special characters (<, >, &) with +/// their corresponding xml escaped value, but does not escape quote characters. +pub fn partial_escape(raw: &[u8]) -> Cow<[u8]> { + #[inline] + fn to_escape(b: u8) -> bool { + match b { + b'<' | b'>' | b'&' => true, + _ => false, + } + } + + _escape(raw, to_escape) +} + +/// Escapes a `&[u8]` and replaces a subset of xml special characters (<, >, &, ', ") with their +/// corresponding xml escaped value. +fn _escape bool>(raw: &[u8], escape_chars: F) -> Cow<[u8]> { let mut escaped = None; let mut bytes = raw.iter(); let mut pos = 0; - while let Some(i) = bytes.position(|&b| to_escape(b)) { + while let Some(i) = bytes.position(|&b| escape_chars(b)) { if escaped.is_none() { escaped = Some(Vec::with_capacity(raw.len())); } @@ -121,7 +144,7 @@ pub fn unescape_with<'a>( } /// Unescape a `&[u8]` and replaces all xml escaped characters ('&...;') into their corresponding -/// value, using an optional dictionnary of custom entities. +/// value, using an optional dictionary of custom entities. /// /// # Pre-condition /// @@ -182,7 +205,7 @@ const fn named_entity(name: &[u8]) -> Option<&str> { b"amp" => "&", b"apos" => "'", b"quot" => "\"", - _ => return None + _ => return None, }; Some(s) } @@ -821,11 +844,9 @@ const fn named_entity(name: &[u8]) -> Option<&str> { b"mid" | b"VerticalBar" | b"smid" | b"shortmid" => "\u{2223}", b"nmid" | b"NotVerticalBar" | b"nsmid" | b"nshortmid" => "\u{2224}", b"par" | b"parallel" | b"DoubleVerticalBar" | b"spar" | b"shortparallel" => "\u{2225}", - b"npar" - | b"nparallel" - | b"NotDoubleVerticalBar" - | b"nspar" - | b"nshortparallel" => "\u{2226}", + b"npar" | b"nparallel" | b"NotDoubleVerticalBar" | b"nspar" | b"nshortparallel" => { + "\u{2226}" + } b"and" | b"wedge" => "\u{2227}", b"or" | b"vee" => "\u{2228}", b"cap" => "\u{2229}", @@ -1646,7 +1667,7 @@ const fn named_entity(name: &[u8]) -> Option<&str> { b"xopf" => "\u{1D56}", b"yopf" => "\u{1D56}", b"zopf" => "\u{1D56}", - _ => return None + _ => return None, }; Some(s) } @@ -1741,3 +1762,15 @@ fn test_escape() { "prefix_"a"b&<>c".as_bytes() ); } + +#[test] +fn test_partial_escape() { + assert_eq!(&*partial_escape(b"test"), b"test"); + assert_eq!(&*partial_escape(b""), b"<test>"); + assert_eq!(&*partial_escape(b"\"a\"bc"), b"\"a\"bc"); + assert_eq!(&*partial_escape(b"\"a\"b&c"), b"\"a\"b&c"); + assert_eq!( + &*partial_escape(b"prefix_\"a\"b&<>c"), + "prefix_\"a\"b&<>c".as_bytes() + ); +} diff --git a/src/events/attributes.rs b/src/events/attributes.rs index 15f05adc..cb4571e4 100644 --- a/src/events/attributes.rs +++ b/src/events/attributes.rs @@ -2,13 +2,10 @@ //! //! Provides an iterator over attributes key/value pairs -use errors::{Error, Result}; -use escape::{do_unescape, escape}; -use reader::{is_whitespace, Reader}; -use std::borrow::Cow; -use std::collections::HashMap; -use std::io::BufRead; -use std::ops::Range; +use crate::errors::{Error, Result}; +use crate::escape::{do_unescape, escape}; +use crate::reader::{is_whitespace, Reader}; +use std::{borrow::Cow, collections::HashMap, io::BufRead, ops::Range}; /// Iterator over XML attributes. /// diff --git a/src/events/mod.rs b/src/events/mod.rs index 1593a21f..cf8620ef 100644 --- a/src/events/mod.rs +++ b/src/events/mod.rs @@ -39,16 +39,11 @@ pub mod attributes; #[cfg(feature = "encoding_rs")] use encoding_rs::Encoding; -use std::borrow::Cow; -use std::collections::HashMap; -use std::io::BufRead; -use std::ops::Deref; -use std::str::from_utf8; +use std::{borrow::Cow, collections::HashMap, io::BufRead, ops::Deref, str::from_utf8}; -use self::attributes::{Attribute, Attributes}; -use errors::{Error, Result}; -use escape::{do_unescape, escape}; -use reader::Reader; +use crate::escape::{do_unescape, escape}; +use crate::{errors::Error, errors::Result, reader::Reader}; +use attributes::{Attribute, Attributes}; use memchr; @@ -132,7 +127,7 @@ impl<'a> BytesStart<'a> { /// /// # Example /// - /// ``` + /// ```rust /// # use quick_xml::{Error, Writer}; /// use quick_xml::events::{BytesStart, Event}; /// @@ -353,6 +348,20 @@ impl<'a> BytesStart<'a> { self.buf.to_mut().truncate(self.name_len); self } + + /// Try to get an attribute + pub fn try_get_attribute + Sized>( + &'a self, + attr_name: N, + ) -> Result>> { + for a in self.attributes() { + let a = a?; + if a.key == attr_name.as_ref() { + return Ok(Some(a)); + } + } + Ok(None) + } } impl<'a> std::fmt::Debug for BytesStart<'a> { @@ -831,7 +840,7 @@ pub enum Event<'a> { impl<'a> Event<'a> { /// Converts the event to an owned version, untied to the lifetime of - /// buffer used when reading but incurring a new, seperate allocation. + /// buffer used when reading but incurring a new, separate allocation. pub fn into_owned(self) -> Event<'static> { match self { Event::Start(e) => Event::Start(e.into_owned()), diff --git a/src/lib.rs b/src/lib.rs index dc2b2e1c..1eb77875 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,8 +144,8 @@ mod errors; mod escapei; pub mod escape { //! Manage xml character escapes - pub(crate) use escapei::do_unescape; - pub use escapei::{escape, unescape, unescape_with, EscapeError}; + pub(crate) use crate::escapei::{do_unescape, EscapeError}; + pub use crate::escapei::{escape, partial_escape, unescape, unescape_with}; } pub mod events; mod reader; @@ -156,7 +156,6 @@ mod writer; // reexports #[cfg(feature = "serialize")] -pub use errors::serialize::DeError; -pub use errors::{Error, Result}; -pub use reader::Reader; -pub use writer::Writer; +pub use crate::errors::serialize::DeError; +pub use crate::errors::{Error, Result}; +pub use crate::{reader::Reader, writer::Writer}; diff --git a/src/reader.rs b/src/reader.rs index 609fcead..ddb672fb 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -2,16 +2,14 @@ #[cfg(feature = "encoding")] use std::borrow::Cow; -use std::fs::File; use std::io::{self, BufRead, BufReader}; -use std::path::Path; -use std::str::from_utf8; +use std::{fs::File, path::Path, str::from_utf8}; #[cfg(feature = "encoding")] use encoding_rs::{Encoding, UTF_16BE, UTF_16LE}; -use errors::{Error, Result}; -use events::{attributes::Attribute, BytesDecl, BytesEnd, BytesStart, BytesText, Event}; +use crate::errors::{Error, Result}; +use crate::events::{attributes::Attribute, BytesDecl, BytesEnd, BytesStart, BytesText, Event}; use memchr; diff --git a/src/writer.rs b/src/writer.rs index a1376945..04ed699d 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -1,10 +1,9 @@ //! A module to handle `Writer` +use crate::errors::{Error, Result}; +use crate::events::{attributes::Attribute, BytesStart, BytesText, Event}; use std::io::Write; -use errors::{Error, Result}; -use events::Event; - /// XML writer. /// /// Writes XML `Event`s to a `Write` implementor. @@ -12,7 +11,6 @@ use events::Event; /// # Examples /// /// ```rust -/// # extern crate quick_xml; /// # fn main() { /// use quick_xml::{Reader, Writer}; /// use quick_xml::events::{Event, BytesEnd, BytesStart}; @@ -170,6 +168,136 @@ impl Writer { } Ok(()) } + + /// Provides a simple, high-level API for writing XML elements. + /// + /// Returns an [ElementWriter] that simplifies setting attributes and writing content inside the element. + /// + /// # Example + /// + /// ```rust + /// # use quick_xml::Result; + /// # fn main() -> Result<()> { + /// use quick_xml::{Error, Writer}; + /// use quick_xml::events::{BytesStart, BytesText, Event}; + /// use std::io::Cursor; + /// + /// let mut writer = Writer::new(Cursor::new(Vec::new())); + /// + /// // writes + /// writer.create_element("tag") + /// .with_attribute(("attr1", "value1")) // chain `with_attribute()` calls to add many attributes + /// .write_empty()?; + /// + /// // writes with some text inside + /// writer.create_element("tag") + /// .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()) // or add attributes from an iterator + /// .write_text_content(BytesText::from_plain_str("with some text inside"))?; + /// + /// // writes appleorange + /// writer.create_element("tag") + /// .write_inner_content(|writer| { + /// let fruits = ["apple", "orange"]; + /// for (quant, item) in fruits.iter().enumerate() { + /// writer + /// .create_element("fruit") + /// .with_attribute(("quantity", quant.to_string().as_str())) + /// .write_text_content(BytesText::from_plain_str(item))?; + /// } + /// Ok(()) + /// })?; + /// # Ok(()) + /// # } + /// ``` + #[must_use] + pub fn create_element<'a, N>(&'a mut self, name: &'a N) -> ElementWriter + where + N: 'a + AsRef<[u8]> + ?Sized, + { + ElementWriter { + writer: self, + start_tag: BytesStart::borrowed_name(name.as_ref()), + } + } +} + +pub struct ElementWriter<'a, W: Write> { + writer: &'a mut Writer, + start_tag: BytesStart<'a>, +} + +impl<'a, W: Write> ElementWriter<'a, W> { + /// Adds an attribute to this element. + pub fn with_attribute<'b, I>(mut self, attr: I) -> Self + where + I: Into>, + { + self.start_tag.push_attribute(attr); + self + } + + /// Add additional attributes to this element using an iterator. + /// + /// The yielded items must be convertible to [`Attribute`] using `Into`. + /// + /// [`Attribute`]: attributes/struct.Attributes.html + pub fn with_attributes<'b, I>(mut self, attributes: I) -> Self + where + I: IntoIterator, + I::Item: Into>, + { + self.start_tag.extend_attributes(attributes); + self + } + + /// Write some text inside the current element. + pub fn write_text_content(self, text: BytesText) -> Result<&'a mut Writer> { + self.writer + .write_event(Event::Start(self.start_tag.to_borrowed()))?; + self.writer.write_event(Event::Text(text))?; + self.writer + .write_event(Event::End(self.start_tag.to_end()))?; + Ok(self.writer) + } + + /// Write a CData event `` inside the current element. + pub fn write_cdata_content(self, text: BytesText) -> Result<&'a mut Writer> { + self.writer + .write_event(Event::Start(self.start_tag.to_borrowed()))?; + self.writer.write_event(Event::CData(text))?; + self.writer + .write_event(Event::End(self.start_tag.to_end()))?; + Ok(self.writer) + } + + /// Write a processing instruction `` inside the current element. + pub fn write_pi_content(self, text: BytesText) -> Result<&'a mut Writer> { + self.writer + .write_event(Event::Start(self.start_tag.to_borrowed()))?; + self.writer.write_event(Event::PI(text))?; + self.writer + .write_event(Event::End(self.start_tag.to_end()))?; + Ok(self.writer) + } + + /// Write an empty (self-closing) tag. + pub fn write_empty(self) -> Result<&'a mut Writer> { + self.writer.write_event(Event::Empty(self.start_tag))?; + Ok(self.writer) + } + + /// Create a new scope for writing XML inside the current element. + pub fn write_inner_content(mut self, closure: F) -> Result<&'a mut Writer> + where + F: Fn(&mut Writer) -> Result<()>, + { + self.writer + .write_event(Event::Start(self.start_tag.to_borrowed()))?; + closure(&mut self.writer)?; + self.writer + .write_event(Event::End(self.start_tag.to_end()))?; + Ok(self.writer) + } } #[derive(Clone)] @@ -210,7 +338,7 @@ impl Indentation { #[cfg(test)] mod indentation { use super::*; - use events::*; + use crate::events::*; #[test] fn self_closed() { @@ -224,8 +352,8 @@ mod indentation { .expect("write tag failed"); assert_eq!( - buffer, - br#""#.as_ref() + std::str::from_utf8(&buffer).unwrap(), + r#""# ); } @@ -246,10 +374,9 @@ mod indentation { .expect("write end tag failed"); assert_eq!( - buffer, - br#" + std::str::from_utf8(&buffer).unwrap(), + r#" "# - .as_ref() ); } @@ -275,11 +402,10 @@ mod indentation { .expect("write end tag failed"); assert_eq!( - buffer, - br#" + std::str::from_utf8(&buffer).unwrap(), + r#" "# - .as_ref() ); } @@ -305,8 +431,8 @@ mod indentation { .expect("write end tag failed"); assert_eq!( - buffer, - br#"text"#.as_ref() + std::str::from_utf8(&buffer).unwrap(), + r#"text"# ); } @@ -336,10 +462,9 @@ mod indentation { .expect("write end tag failed"); assert_eq!( - buffer, - br#"text + std::str::from_utf8(&buffer).unwrap(), + r#"text "# - .as_ref() ); } @@ -371,13 +496,88 @@ mod indentation { .expect("write end tag 1 failed"); assert_eq!( - buffer, - br#" + std::str::from_utf8(&buffer).unwrap(), + r#" "# - .as_ref() + ); + } + #[test] + fn element_writer_empty() { + let mut buffer = Vec::new(); + let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); + + writer + .create_element(b"empty") + .with_attribute(("attr1", "value1")) + .with_attribute(("attr2", "value2")) + .write_empty() + .expect("failure"); + + assert_eq!( + std::str::from_utf8(&buffer).unwrap(), + r#""# + ); + } + + #[test] + fn element_writer_text() { + let mut buffer = Vec::new(); + let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); + + writer + .create_element("paired") + .with_attribute(("attr1", "value1")) + .with_attribute(("attr2", "value2")) + .write_text_content(BytesText::from_plain_str("text")) + .expect("failure"); + + assert_eq!( + std::str::from_utf8(&buffer).unwrap(), + r#"text"# + ); + } + + #[test] + fn element_writer_nested() { + let mut buffer = Vec::new(); + let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); + + writer + .create_element("outer") + .with_attribute(("attr1", "value1")) + .with_attribute(("attr2", "value2")) + .write_inner_content(|writer| { + let fruits = ["apple", "orange", "banana"]; + for (quant, item) in fruits.iter().enumerate() { + writer + .create_element("fruit") + .with_attribute(("quantity", quant.to_string().as_str())) + .write_text_content(BytesText::from_plain_str(item))?; + } + writer + .create_element("inner") + .write_inner_content(|writer| { + writer.create_element("empty").write_empty()?; + Ok(()) + })?; + + Ok(()) + }) + .expect("failure"); + + assert_eq!( + std::str::from_utf8(&buffer).unwrap(), + r#" + apple + orange + banana + + + +"# ); } } diff --git a/tests/documents/html5.txt b/tests/documents/html5.txt index 5f0d6bc7..1ad39c09 100644 --- a/tests/documents/html5.txt +++ b/tests/documents/html5.txt @@ -4,5 +4,7 @@ Characters( StartElement(a, attr-error: error while parsing attribute at position 7: Attribute value must start with a quote.) Characters(Hey) EndElement(a) -InvalidUtf8([10, 38, 110, 98, 115, 112, 59, 10]; invalid utf-8 sequence of 1 bytes from index 1) +Characters( +  +) EndDocument diff --git a/tests/unit_tests.rs b/tests/unit_tests.rs index 5a0d7895..34dc2342 100644 --- a/tests/unit_tests.rs +++ b/tests/unit_tests.rs @@ -1,11 +1,8 @@ -extern crate quick_xml; - use std::io::Cursor; use std::str::from_utf8; -use quick_xml::events::Event::*; use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event}; -use quick_xml::{Reader, Result, Writer}; +use quick_xml::{events::Event::*, Reader, Result, Writer}; macro_rules! next_eq_name { ($r:expr, $t:tt, $bytes:expr) => { @@ -201,55 +198,54 @@ fn test_nested() { } #[test] -fn test_writer() { +fn test_writer() -> Result<()> { let txt = include_str!("../tests/documents/test_writer.xml").trim(); let mut reader = Reader::from_str(txt); reader.trim_text(true); let mut writer = Writer::new(Cursor::new(Vec::new())); let mut buf = Vec::new(); loop { - match reader.read_event(&mut buf) { - Ok(Eof) => break, - Ok(e) => assert!(writer.write_event(e).is_ok()), - Err(e) => panic!("{}", e), + match reader.read_event(&mut buf)? { + Eof => break, + e => assert!(writer.write_event(e).is_ok()), } } let result = writer.into_inner().into_inner(); assert_eq!(result, txt.as_bytes()); + Ok(()) } #[test] -fn test_writer_borrow() { +fn test_writer_borrow() -> Result<()> { let txt = include_str!("../tests/documents/test_writer.xml").trim(); let mut reader = Reader::from_str(txt); reader.trim_text(true); let mut writer = Writer::new(Cursor::new(Vec::new())); let mut buf = Vec::new(); loop { - match reader.read_event(&mut buf) { - Ok(Eof) => break, - Ok(e) => assert!(writer.write_event(&e).is_ok()), // either `e` or `&e` - Err(e) => panic!("{}", e), + match reader.read_event(&mut buf)? { + Eof => break, + e => assert!(writer.write_event(&e).is_ok()), // either `e` or `&e` } } let result = writer.into_inner().into_inner(); assert_eq!(result, txt.as_bytes()); + Ok(()) } #[test] -fn test_writer_indent() { +fn test_writer_indent() -> Result<()> { let txt = include_str!("../tests/documents/test_writer_indent.xml"); let mut reader = Reader::from_str(txt); reader.trim_text(true); let mut writer = Writer::new_with_indent(Cursor::new(Vec::new()), b' ', 4); let mut buf = Vec::new(); loop { - match reader.read_event(&mut buf) { - Ok(Eof) => break, - Ok(e) => assert!(writer.write_event(e).is_ok()), - Err(e) => panic!("{}", e), + match reader.read_event(&mut buf)? { + Eof => break, + e => assert!(writer.write_event(e).is_ok()), } } @@ -261,20 +257,21 @@ fn test_writer_indent() { #[cfg(not(windows))] assert_eq!(result, txt.as_bytes()); + + Ok(()) } #[test] -fn test_writer_indent_cdata() { +fn test_writer_indent_cdata() -> Result<()> { let txt = include_str!("../tests/documents/test_writer_indent_cdata.xml"); let mut reader = Reader::from_str(txt); reader.trim_text(true); let mut writer = Writer::new_with_indent(Cursor::new(Vec::new()), b' ', 4); let mut buf = Vec::new(); loop { - match reader.read_event(&mut buf) { - Ok(Eof) => break, - Ok(e) => assert!(writer.write_event(e).is_ok()), - Err(e) => panic!("{}", e), + match reader.read_event(&mut buf)? { + Eof => break, + e => assert!(writer.write_event(e).is_ok()), } } @@ -285,10 +282,12 @@ fn test_writer_indent_cdata() { #[cfg(not(windows))] assert_eq!(result, txt.as_bytes()); + + Ok(()) } #[test] -fn test_write_empty_element_attrs() { +fn test_write_empty_element_attrs() -> Result<()> { let str_from = r#""#; let expected = r#""#; let mut reader = Reader::from_str(str_from); @@ -296,19 +295,19 @@ fn test_write_empty_element_attrs() { let mut writer = Writer::new(Cursor::new(Vec::new())); let mut buf = Vec::new(); loop { - match reader.read_event(&mut buf) { - Ok(Eof) => break, - Ok(e) => assert!(writer.write_event(e).is_ok()), - Err(e) => panic!("{}", e), + match reader.read_event(&mut buf)? { + Eof => break, + e => assert!(writer.write_event(e).is_ok()), } } let result = writer.into_inner().into_inner(); assert_eq!(String::from_utf8(result).unwrap(), expected); + Ok(()) } #[test] -fn test_write_attrs() { +fn test_write_attrs() -> Result<()> { let str_from = r#""#; let expected = r#""#; let mut reader = Reader::from_str(str_from); @@ -316,25 +315,26 @@ fn test_write_attrs() { let mut writer = Writer::new(Cursor::new(Vec::new())); let mut buf = Vec::new(); loop { - let event = match reader.read_event(&mut buf) { - Ok(Eof) => break, - Ok(Start(elem)) => { - let mut attrs = elem.attributes().collect::>>().unwrap(); + let event = match reader.read_event(&mut buf)? { + Eof => break, + Start(elem) => { + let mut attrs = elem.attributes().collect::>>()?; attrs.extend_from_slice(&[("a", "b").into(), ("c", "d").into()]); let mut elem = BytesStart::owned(b"copy".to_vec(), 4); elem.extend_attributes(attrs); elem.push_attribute(("x", "y\"z")); Start(elem) } - Ok(End(_)) => End(BytesEnd::borrowed(b"copy")), - Ok(e) => e, - Err(e) => panic!("{}", e), + End(_) => End(BytesEnd::borrowed(b"copy")), + e => e, }; assert!(writer.write_event(event).is_ok()); } let result = writer.into_inner().into_inner(); assert_eq!(result, expected.as_bytes()); + + Ok(()) } #[test] @@ -646,7 +646,7 @@ fn test_escaped_content() { } #[test] -fn test_read_write_roundtrip_results_in_identity() { +fn test_read_write_roundtrip_results_in_identity() -> Result<()> { let input = r#"
@@ -661,19 +661,19 @@ fn test_read_write_roundtrip_results_in_identity() { let mut writer = Writer::new(Cursor::new(Vec::new())); let mut buf = Vec::new(); loop { - match reader.read_event(&mut buf) { - Ok(Eof) => break, - Ok(e) => assert!(writer.write_event(e).is_ok()), - Err(e) => panic!("{}", e), + match reader.read_event(&mut buf)? { + Eof => break, + e => assert!(writer.write_event(e).is_ok()), } } let result = writer.into_inner().into_inner(); assert_eq!(result, input.as_bytes()); + Ok(()) } #[test] -fn test_read_write_roundtrip() { +fn test_read_write_roundtrip() -> Result<()> { let input = r#"
@@ -688,19 +688,19 @@ fn test_read_write_roundtrip() { let mut writer = Writer::new(Cursor::new(Vec::new())); let mut buf = Vec::new(); loop { - match reader.read_event(&mut buf) { - Ok(Eof) => break, - Ok(e) => assert!(writer.write_event(e).is_ok()), - Err(e) => panic!("{}", e), + match reader.read_event(&mut buf)? { + Eof => break, + e => assert!(writer.write_event(e).is_ok()), } } let result = writer.into_inner().into_inner(); assert_eq!(String::from_utf8(result).unwrap(), input.to_string()); + Ok(()) } #[test] -fn test_read_write_roundtrip_escape() { +fn test_read_write_roundtrip_escape() -> Result<()> { let input = r#"
@@ -715,25 +715,25 @@ fn test_read_write_roundtrip_escape() { let mut writer = Writer::new(Cursor::new(Vec::new())); let mut buf = Vec::new(); loop { - match reader.read_event(&mut buf) { - Ok(Eof) => break, - Ok(Text(e)) => { + match reader.read_event(&mut buf)? { + Eof => break, + Text(e) => { let t = e.escaped(); assert!(writer .write_event(Event::Text(BytesText::from_escaped(t.to_vec()))) .is_ok()); } - Ok(e) => assert!(writer.write_event(e).is_ok()), - Err(e) => panic!("{}", e), + e => assert!(writer.write_event(e).is_ok()), } } let result = writer.into_inner().into_inner(); assert_eq!(String::from_utf8(result).unwrap(), input.to_string()); + Ok(()) } #[test] -fn test_read_write_roundtrip_escape_text() { +fn test_read_write_roundtrip_escape_text() -> Result<()> { let input = r#"
@@ -748,21 +748,21 @@ fn test_read_write_roundtrip_escape_text() { let mut writer = Writer::new(Cursor::new(Vec::new())); let mut buf = Vec::new(); loop { - match reader.read_event(&mut buf) { - Ok(Eof) => break, - Ok(Text(e)) => { + match reader.read_event(&mut buf)? { + Eof => break, + Text(e) => { let t = e.unescape_and_decode(&reader).unwrap(); assert!(writer .write_event(Event::Text(BytesText::from_plain_str(&t))) .is_ok()); } - Ok(e) => assert!(writer.write_event(e).is_ok()), - Err(e) => panic!("{}", e), + e => assert!(writer.write_event(e).is_ok()), } } let result = writer.into_inner().into_inner(); assert_eq!(String::from_utf8(result).unwrap(), input.to_string()); + Ok(()) } #[test]