diff --git a/Cargo.toml b/Cargo.toml index 856f119..2b0a8ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ base64 = "0.13.0" time = { version = "0.3.3", features = ["parsing", "formatting"] } indexmap = "1.0.2" line-wrap = "0.1.1" -xml_rs = { package = "xml-rs", version = "0.8.2" } +quick_xml = { package = "quick-xml", version = "0.22.0" } serde = { version = "1.0.2", optional = true } [dev-dependencies] diff --git a/src/error.rs b/src/error.rs index 77cb87e..4f9599d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,7 +27,6 @@ pub(crate) enum ErrorKind { // Xml format-specific errors UnclosedXmlElement, - UnpairedXmlClosingTag, UnexpectedXmlCharactersExpectedElement, UnexpectedXmlOpeningTag, UnknownXmlElement, @@ -61,11 +60,8 @@ pub(crate) enum ErrorKind { Serde(String), } -#[derive(Debug)] -pub(crate) enum FilePosition { - LineColumn(u64, u64), - Offset(u64), -} +#[derive(Debug, Clone, Copy)] +pub(crate) struct FilePosition(pub(crate) u64); #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub(crate) enum EventKind { @@ -141,14 +137,7 @@ impl fmt::Display for Error { impl fmt::Display for FilePosition { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - FilePosition::LineColumn(line, column) => { - write!(f, "line {}, column {}", line, column) - } - FilePosition::Offset(offset) => { - write!(f, "offset {}", offset) - } - } + write!(f, "offset {}", self.0) } } @@ -160,7 +149,7 @@ impl From for Error { impl ErrorKind { pub fn with_byte_offset(self, offset: u64) -> Error { - self.with_position(FilePosition::Offset(offset)) + self.with_position(FilePosition(offset)) } pub fn with_position(self, pos: FilePosition) -> Error { diff --git a/src/serde_tests.rs b/src/serde_tests.rs index 32168c1..0a799de 100644 --- a/src/serde_tests.rs +++ b/src/serde_tests.rs @@ -862,7 +862,9 @@ fn dictionary_serialize_xml() { \t\tFirstKey \t\tFirstValue \t\tSecondKey -\t\t\n\t\tChQeKA==\n\t\t +\t\t +\t\tChQeKA== +\t\t \t\tThirdKey \t\t1.234 \t\tFourthKey @@ -880,6 +882,34 @@ fn dictionary_serialize_xml() { assert_eq!(xml, comparison); } +#[test] +fn empty_array_and_dictionary_serialize_to_xml() { + #[derive(Serialize, Default)] + struct Empty { + vec: Vec, + map: BTreeMap, + } + + // Serialize dictionary as an XML plist. + let mut buf = Cursor::new(Vec::new()); + crate::to_writer_xml(&mut buf, &Empty::default()).unwrap(); + let buf = buf.into_inner(); + let xml = std::str::from_utf8(&buf).unwrap(); + + let comparison = " + + + +\tvec +\t +\tmap +\t + +"; + + assert_eq!(xml, comparison); +} + #[test] fn serde_yaml_to_value() { let value: Value = serde_yaml::from_str("true").unwrap(); diff --git a/src/stream/mod.rs b/src/stream/mod.rs index 9e6e929..fc2ca9a 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -86,8 +86,9 @@ enum StackItem<'a> { /// Options for customizing serialization of XML plists. #[derive(Clone, Debug)] pub struct XmlWriteOptions { - indent_str: Cow<'static, str>, root_element: bool, + indent_char: u8, + indent_amount: usize, } impl XmlWriteOptions { @@ -96,8 +97,38 @@ impl XmlWriteOptions { /// This may be either an `&'static str` or an owned `String`. /// /// The default is `\t`. - pub fn indent_string(mut self, indent_str: impl Into>) -> Self { - self.indent_str = indent_str.into(); + /// + /// Since replacing `xml-rs` with `quick-xml`, the indent string has to consist of a single + /// repeating ascii character. This is a backwards compatibility function, prefer using + /// `XmlWriteOptions::with_indent`. + #[deprecated(since="1.4.0", note="please use `with_indent` instead")] + pub fn indent_string(self, indent_str: impl Into>) -> Self { + let indent_str = indent_str.into(); + let indent_str = indent_str.as_ref(); + + if indent_str.is_empty() { + return self.with_indent(0, 0); + } + + assert!( + indent_str.chars().all(|chr| chr.is_ascii()), + "indent str must be ascii" + ); + let indent_str = indent_str.as_bytes(); + assert!( + indent_str.iter().all(|chr| chr == &indent_str[0]), + "indent str must consist of a single repeating character" + ); + + self.with_indent(indent_str[0], indent_str.len()) + } + + /// Specifies the character and amount used for indentation. + /// + /// The default is indenting with a single tab. + pub fn with_indent(mut self, indent_char: u8, indent_amount: usize) -> Self { + self.indent_char = indent_char; + self.indent_amount = indent_amount; self } @@ -122,7 +153,8 @@ impl XmlWriteOptions { impl Default for XmlWriteOptions { fn default() -> Self { XmlWriteOptions { - indent_str: Cow::Borrowed("\t"), + indent_char: b'\t', + indent_amount: 1, root_element: true, } } diff --git a/src/stream/xml_reader.rs b/src/stream/xml_reader.rs index 7f6396c..bcde35e 100644 --- a/src/stream/xml_reader.rs +++ b/src/stream/xml_reader.rs @@ -1,15 +1,5 @@ -use base64; -use std::{ - io::{self, Read}, - str::FromStr, -}; -use xml_rs::{ - common::{is_whitespace_str, Position}, - reader::{ - Error as XmlReaderError, ErrorKind as XmlReaderErrorKind, EventReader, ParserConfig, - XmlEvent, - }, -}; +use quick_xml::{events::Event as XmlEvent, Error as XmlReaderError, Reader as EventReader}; +use std::io::{self, BufReader, Read}; use crate::{ error::{Error, ErrorKind, FilePosition}, @@ -17,95 +7,149 @@ use crate::{ Date, Integer, }; +#[derive(Clone, PartialEq, Eq)] +struct ElmName(Box<[u8]>); + +impl From<&[u8]> for ElmName { + fn from(bytes: &[u8]) -> Self { + ElmName(Box::from(bytes)) + } +} + +impl AsRef<[u8]> for ElmName { + fn as_ref(&self) -> &[u8] { + &*self.0 + } +} + pub struct XmlReader { - xml_reader: EventReader, - queued_event: Option, - element_stack: Vec, + buffer: Vec, finished: bool, + state: ReaderState, } +struct ReaderState(EventReader>); + impl XmlReader { pub fn new(reader: R) -> XmlReader { - let config = ParserConfig::new() - .trim_whitespace(false) - .whitespace_to_characters(true) - .cdata_to_characters(true) - .ignore_comments(true) - .coalesce_characters(true); + let mut xml_reader = EventReader::from_reader(BufReader::new(reader)); + xml_reader.trim_text(true); + xml_reader.check_end_names(true); + xml_reader.expand_empty_elements(true); XmlReader { - xml_reader: EventReader::new_with_config(reader, config), - queued_event: None, - element_stack: Vec::new(), + buffer: Vec::new(), finished: false, + state: ReaderState(xml_reader), } } +} - fn read_content(&mut self) -> Result { - loop { - match self.xml_reader.next() { - Ok(XmlEvent::Characters(s)) => return Ok(s), - Ok(event @ XmlEvent::EndElement { .. }) => { - self.queued_event = Some(event); - return Ok("".to_owned()); - } - Ok(XmlEvent::EndDocument) => { - return Err(self.with_pos(ErrorKind::UnclosedXmlElement)) - } - Ok(XmlEvent::StartElement { .. }) => { - return Err(self.with_pos(ErrorKind::UnexpectedXmlOpeningTag)); - } - Ok(XmlEvent::ProcessingInstruction { .. }) => (), - Ok(XmlEvent::StartDocument { .. }) - | Ok(XmlEvent::CData(_)) - | Ok(XmlEvent::Comment(_)) - | Ok(XmlEvent::Whitespace(_)) => { - unreachable!("parser does not output CData, Comment or Whitespace events"); - } - Err(err) => return Err(from_xml_error(err)), +impl From for ErrorKind { + fn from(err: XmlReaderError) -> Self { + match err { + XmlReaderError::Io(err) if err.kind() == io::ErrorKind::UnexpectedEof => { + ErrorKind::UnexpectedEof } + XmlReaderError::Io(err) => ErrorKind::Io(err), + XmlReaderError::UnexpectedEof(_) => ErrorKind::UnexpectedEof, + XmlReaderError::Utf8(_) => ErrorKind::InvalidXmlUtf8, + _ => ErrorKind::InvalidXmlSyntax, } } +} - fn next_event(&mut self) -> Result { - if let Some(event) = self.queued_event.take() { - Ok(event) - } else { - self.xml_reader.next() +impl Iterator for XmlReader { + type Item = Result; + + fn next(&mut self) -> Option> { + if self.finished { + return None; } + match self.state.read_next(&mut self.buffer) { + Ok(Some(event)) => Some(Ok(event)), + Ok(None) => { + self.finished = true; + None + } + Err(err) => { + self.finished = true; + Some(Err(err)) + } + } + } +} + +impl ReaderState { + fn xml_reader_pos(&self) -> FilePosition { + let pos = self.0.buffer_position(); + FilePosition(pos as u64) } - fn read_next(&mut self) -> Result, Error> { + fn with_pos(&self, kind: ErrorKind) -> Error { + kind.with_position(self.xml_reader_pos()) + } + + fn read_xml_event<'buf>(&mut self, buffer: &'buf mut Vec) -> Result, Error> { + let event = self.0.read_event(buffer); + let pos = self.xml_reader_pos(); + event.map_err(|err| ErrorKind::from(err).with_position(pos)) + } + + fn read_content(&mut self, buffer: &mut Vec) -> Result { loop { - match self.next_event() { - Ok(XmlEvent::StartDocument { .. }) => {} - Ok(XmlEvent::StartElement { name, .. }) => { - // Add the current element to the element stack - self.element_stack.push(name.local_name.clone()); + match self.read_xml_event(buffer)? { + XmlEvent::Text(text) => { + let unespcaped = text + .unescaped() + .map_err(|err| self.with_pos(ErrorKind::from(err)))?; + return String::from_utf8(unespcaped.to_vec()) + .map_err(|_| self.with_pos(ErrorKind::InvalidUtf8String)); + } + XmlEvent::End(_) => { + return Ok("".to_owned()); + } + XmlEvent::Eof => return Err(self.with_pos(ErrorKind::UnclosedXmlElement)), + XmlEvent::Start(_) => return Err(self.with_pos(ErrorKind::UnexpectedXmlOpeningTag)), + XmlEvent::PI(_) + | XmlEvent::Empty(_) + | XmlEvent::Comment(_) + | XmlEvent::CData(_) + | XmlEvent::Decl(_) + | XmlEvent::DocType(_) => { + // skip + } + } + } + } - match &name.local_name[..] { - "plist" => (), - "array" => return Ok(Some(Event::StartArray(None))), - "dict" => return Ok(Some(Event::StartDictionary(None))), - "key" => return Ok(Some(Event::String(self.read_content()?.into()))), - "true" => return Ok(Some(Event::Boolean(true))), - "false" => return Ok(Some(Event::Boolean(false))), - "data" => { - let mut s = self.read_content()?; + fn read_next(&mut self, buffer: &mut Vec) -> Result, Error> { + loop { + match self.read_xml_event(buffer)? { + XmlEvent::Start(name) => { + match name.local_name() { + b"plist" => {} + b"array" => return Ok(Some(Event::StartArray(None))), + b"dict" => return Ok(Some(Event::StartDictionary(None))), + b"key" => { + return Ok(Some(Event::String(self.read_content(buffer)?.into()))) + } + b"data" => { + let mut encoded = self.read_content(buffer)?; // Strip whitespace and line endings from input string - s.retain(|c| !c.is_ascii_whitespace()); - let data = base64::decode(&s) + encoded.retain(|c| !c.is_ascii_whitespace()); + let data = base64::decode(&encoded) .map_err(|_| self.with_pos(ErrorKind::InvalidDataString))?; return Ok(Some(Event::Data(data.into()))); } - "date" => { - let s = self.read_content()?; - let date = - Date::from_xml_format(&s).map_err(|e| self.with_pos(e.into()))?; + b"date" => { + let s = self.read_content(buffer)?; + let date = Date::from_xml_format(&s) + .map_err(|_| self.with_pos(ErrorKind::InvalidDateString))?; return Ok(Some(Event::Date(date))); } - "integer" => { - let s = self.read_content()?; + b"integer" => { + let s = self.read_content(buffer)?; match Integer::from_str(&s) { Ok(i) => return Ok(Some(Event::Integer(i))), Err(_) => { @@ -113,109 +157,44 @@ impl XmlReader { } } } - "real" => { - let s = self.read_content()?; - match f64::from_str(&s) { + b"real" => { + let s = self.read_content(buffer)?; + match s.parse() { Ok(f) => return Ok(Some(Event::Real(f))), Err(_) => return Err(self.with_pos(ErrorKind::InvalidRealString)), } } - "string" => return Ok(Some(Event::String(self.read_content()?.into()))), - _ => return Err(self.with_pos(ErrorKind::UnknownXmlElement)), - } - } - Ok(XmlEvent::EndElement { name, .. }) => { - // Check the corrent element is being closed - match self.element_stack.pop() { - Some(ref open_name) if &name.local_name == open_name => (), - Some(ref _open_name) => { - return Err(self.with_pos(ErrorKind::UnclosedXmlElement)) + b"string" => { + return Ok(Some(Event::String(self.read_content(buffer)?.into()))) } - None => return Err(self.with_pos(ErrorKind::UnpairedXmlClosingTag)), - } - - match &name.local_name[..] { - "array" | "dict" => return Ok(Some(Event::EndCollection)), - "plist" | _ => (), + b"true" => return Ok(Some(Event::Boolean(true))), + b"false" => return Ok(Some(Event::Boolean(false))), + _ => return Err(self.with_pos(ErrorKind::UnknownXmlElement)), } } - Ok(XmlEvent::EndDocument) => { - if self.element_stack.is_empty() { - return Ok(None); - } else { - return Err(self.with_pos(ErrorKind::UnclosedXmlElement)); + XmlEvent::End(name) => { + match name.local_name() { + b"array" | b"dict" => return Ok(Some(Event::EndCollection)), + b"plist" | _ => (), } } - - Ok(XmlEvent::Characters(c)) => { - if !is_whitespace_str(&c) { - return Err( - self.with_pos(ErrorKind::UnexpectedXmlCharactersExpectedElement) - ); - } - } - Ok(XmlEvent::CData(_)) | Ok(XmlEvent::Comment(_)) | Ok(XmlEvent::Whitespace(_)) => { - unreachable!("parser does not output CData, Comment or Whitespace events") - } - Ok(XmlEvent::ProcessingInstruction { .. }) => (), - Err(err) => return Err(from_xml_error(err)), - } - } - } - - fn with_pos(&self, kind: ErrorKind) -> Error { - kind.with_position(convert_xml_pos(self.xml_reader.position())) - } -} - -impl Iterator for XmlReader { - type Item = Result; - - fn next(&mut self) -> Option> { - if self.finished { - None - } else { - match self.read_next() { - Ok(Some(event)) => Some(Ok(event)), - Ok(None) => { - self.finished = true; - None + XmlEvent::Eof => return Ok(None), + XmlEvent::Text(_) => { + return Err(self.with_pos(ErrorKind::UnexpectedXmlCharactersExpectedElement)) } - Err(err) => { - self.finished = true; - Some(Err(err)) + XmlEvent::PI(_) + | XmlEvent::Decl(_) + | XmlEvent::DocType(_) + | XmlEvent::CData(_) + | XmlEvent::Comment(_) + | XmlEvent::Empty(_) => { + // skip } } } } } -fn convert_xml_pos(pos: xml_rs::common::TextPosition) -> FilePosition { - // TODO: pos.row and pos.column counts from 0. what do we want to do? - FilePosition::LineColumn(pos.row, pos.column) -} - -fn from_xml_error(err: XmlReaderError) -> Error { - let kind = match err.kind() { - XmlReaderErrorKind::Io(err) if err.kind() == io::ErrorKind::UnexpectedEof => { - ErrorKind::UnexpectedEof - } - XmlReaderErrorKind::Io(err) => { - let err = if let Some(code) = err.raw_os_error() { - io::Error::from_raw_os_error(code) - } else { - io::Error::new(err.kind(), err.to_string()) - }; - ErrorKind::Io(err) - } - XmlReaderErrorKind::Syntax(_) => ErrorKind::InvalidXmlSyntax, - XmlReaderErrorKind::UnexpectedEof => ErrorKind::UnexpectedEof, - XmlReaderErrorKind::Utf8(_) => ErrorKind::InvalidXmlUtf8, - }; - - kind.with_position(convert_xml_pos(err.position())) -} - #[cfg(test)] mod tests { use std::{fs::File, path::Path}; diff --git a/src/stream/xml_writer.rs b/src/stream/xml_writer.rs index aee9826..e8581d8 100644 --- a/src/stream/xml_writer.rs +++ b/src/stream/xml_writer.rs @@ -1,11 +1,8 @@ -use base64; -use line_wrap; -use std::{borrow::Cow, io::Write}; -use xml_rs::{ - name::Name, - namespace::Namespace, - writer::{EmitterConfig, Error as XmlWriterError, EventWriter, XmlEvent}, +use quick_xml::{ + events::{BytesEnd, BytesStart, BytesText, Event as XmlEvent}, + Error as XmlWriterError, Writer as EventWriter, }; +use std::io::Write; use crate::{ error::{self, Error, ErrorKind, EventKind}, @@ -13,7 +10,7 @@ use crate::{ Date, Integer, Uid, }; -static XML_PROLOGUE: &str = r#" +static XML_PROLOGUE: &[u8] = br#" "#; @@ -30,40 +27,39 @@ pub struct XmlWriter { started_plist: bool, stack: Vec, expecting_key: bool, - // Not very nice - empty_namespace: Namespace, + pending_collection: Option, +} + +enum PendingCollection { + Array, + Dictionary, } impl XmlWriter { - #[cfg(fearure = "enable_unstable_features_that_may_break_with_minor_version_bumps")] + #[cfg(feature = "enable_unstable_features_that_may_break_with_minor_version_bumps")] pub fn new(writer: W) -> XmlWriter { let opts = XmlWriteOptions::default(); XmlWriter::new_with_options(writer, &opts) } pub fn new_with_options(writer: W, opts: &XmlWriteOptions) -> XmlWriter { - let config = EmitterConfig::new() - .line_separator("\n") - .indent_string(opts.indent_str.clone()) - .perform_indent(true) - .write_document_declaration(false) - .normalize_empty_elements(true) - .cdata_to_characters(true) - .keep_element_names_stack(false) - .autopad_comments(true) - .pad_self_closing(false); + let xml_writer = if opts.indent_amount == 0 { + EventWriter::new(writer) + } else { + EventWriter::new_with_indent(writer, opts.indent_char, opts.indent_amount) + }; XmlWriter { - xml_writer: EventWriter::new_with_config(writer, config), + xml_writer, write_root_element: opts.root_element, started_plist: false, stack: Vec::new(), expecting_key: false, - empty_namespace: Namespace::empty(), + pending_collection: None, } } - #[cfg(feature="enable_unstable_features_that_may_break_with_minor_version_bumps")] + #[cfg(feature = "enable_unstable_features_that_may_break_with_minor_version_bumps")] pub fn into_inner(self) -> W { self.xml_writer.into_inner() } @@ -77,28 +73,19 @@ impl XmlWriter { fn start_element(&mut self, name: &str) -> Result<(), Error> { self.xml_writer - .write(XmlEvent::StartElement { - name: Name::local(name), - attributes: Cow::Borrowed(&[]), - namespace: Cow::Borrowed(&self.empty_namespace), - }) - .map_err(from_xml_error)?; + .write_event(XmlEvent::Start(BytesStart::borrowed_name(name.as_bytes())))?; Ok(()) } fn end_element(&mut self, name: &str) -> Result<(), Error> { self.xml_writer - .write(XmlEvent::EndElement { - name: Some(Name::local(name)), - }) - .map_err(from_xml_error)?; + .write_event(XmlEvent::End(BytesEnd::borrowed(name.as_bytes())))?; Ok(()) } fn write_value(&mut self, value: &str) -> Result<(), Error> { self.xml_writer - .write(XmlEvent::Characters(value)) - .map_err(from_xml_error)?; + .write_event(XmlEvent::Text(BytesText::from_plain_str(value)))?; Ok(()) } @@ -109,8 +96,8 @@ impl XmlWriter { if !self.started_plist { if self.write_root_element { self.xml_writer - .inner_mut() - .write_all(XML_PROLOGUE.as_bytes()) + .inner() + .write_all(XML_PROLOGUE) .map_err(error::from_io_without_position)?; } @@ -125,13 +112,13 @@ impl XmlWriter { // We didn't tell the xml_writer about the tag so we'll skip telling it // about the tag as well. self.xml_writer - .inner_mut() + .inner() .write_all(b"\n") .map_err(error::from_io_without_position)?; } self.xml_writer - .inner_mut() + .inner() .flush() .map_err(error::from_io_without_position)?; } @@ -144,6 +131,7 @@ impl XmlWriter { event_kind: EventKind, f: F, ) -> Result<(), Error> { + self.handle_pending_collection()?; self.write_event(|this| { if this.expecting_key { return Err(ErrorKind::UnexpectedEventType { @@ -157,28 +145,65 @@ impl XmlWriter { Ok(()) }) } + + fn handle_pending_collection(&mut self) -> Result<(), Error> { + if let Some(PendingCollection::Array) = self.pending_collection { + self.pending_collection = None; + let res = self.write_value_event(EventKind::StartArray, |this| { + this.start_element("array")?; + this.stack.push(Element::Array); + Ok(()) + }); + res + } else if let Some(PendingCollection::Dictionary) = self.pending_collection { + self.pending_collection = None; + let res = self.write_value_event(EventKind::StartDictionary, |this| { + this.start_element("dict")?; + this.stack.push(Element::Dictionary); + this.expecting_key = true; + Ok(()) + }); + res + } else { + Ok(()) + } + } } impl Writer for XmlWriter { fn write_start_array(&mut self, _len: Option) -> Result<(), Error> { - self.write_value_event(EventKind::StartArray, |this| { - this.start_element("array")?; - this.stack.push(Element::Array); - Ok(()) - }) + self.handle_pending_collection()?; + self.pending_collection = Some(PendingCollection::Array); + Ok(()) } fn write_start_dictionary(&mut self, _len: Option) -> Result<(), Error> { - self.write_value_event(EventKind::StartDictionary, |this| { - this.start_element("dict")?; - this.stack.push(Element::Dictionary); - Ok(()) - }) + self.handle_pending_collection()?; + self.pending_collection = Some(PendingCollection::Dictionary); + Ok(()) } fn write_end_collection(&mut self) -> Result<(), Error> { self.write_event(|this| { - match (this.stack.pop(), this.expecting_key) { + match this.pending_collection.take() { + Some(PendingCollection::Array) => { + this.xml_writer + .write_event(XmlEvent::Empty(BytesStart::borrowed_name(b"array")))?; + this.expecting_key = this.stack.last() == Some(&Element::Dictionary); + return Ok(()); + } + Some(PendingCollection::Dictionary) => { + this.xml_writer + .write_event(XmlEvent::Empty(BytesStart::borrowed_name(b"dict")))?; + this.expecting_key = this.stack.last() == Some(&Element::Dictionary); + return Ok(()); + } + _ => {} + }; + match ( + this.stack.pop(), + this.expecting_key, + ) { (Some(Element::Dictionary), true) => { this.end_element("dict")?; } @@ -200,9 +225,10 @@ impl Writer for XmlWriter { fn write_boolean(&mut self, value: bool) -> Result<(), Error> { self.write_value_event(EventKind::Boolean, |this| { - let value_str = if value { "true" } else { "false" }; - this.start_element(value_str)?; - this.end_element(value_str) + let value = if value { "true" } else { "false" }.as_bytes(); + Ok(this + .xml_writer + .write_event(XmlEvent::Empty(BytesStart::borrowed_name(value)))?) }) } @@ -232,6 +258,7 @@ impl Writer for XmlWriter { } fn write_string(&mut self, value: &str) -> Result<(), Error> { + self.handle_pending_collection()?; self.write_event(|this| { if this.expecting_key { this.write_element_and_value("key", &*value)?; @@ -249,13 +276,12 @@ impl Writer for XmlWriter { } } -pub(crate) fn from_xml_error(err: XmlWriterError) -> Error { - match err { - XmlWriterError::Io(err) => ErrorKind::Io(err).without_position(), - XmlWriterError::DocumentStartAlreadyEmitted - | XmlWriterError::LastElementNameNotAvailable - | XmlWriterError::EndElementNameIsNotEqualToLastStartElementName - | XmlWriterError::EndElementNameIsNotSpecified => unreachable!(), +impl From for Error { + fn from(err: XmlWriterError) -> Self { + match err { + XmlWriterError::Io(err) => ErrorKind::Io(err).without_position(), + _ => unreachable!(), + } } } @@ -406,7 +432,7 @@ mod tests { "; - let actual = events_to_xml(plist, XmlWriteOptions::default().indent_string(".")); + let actual = events_to_xml(plist, XmlWriteOptions::default().with_indent(b'.', 1)); assert_eq!(actual, expected); }