use base64;
use std::borrow::Cow;
use std::io::Write;
use xml_rs::name::Name;
use xml_rs::namespace::Namespace;
use xml_rs::writer::{EmitterConfig, Error as XmlWriterError, EventWriter, XmlEvent};
use events::{Event, Writer};
use Error;
static XML_PROLOGUE: &str = r#"
"#;
impl From for Error {
fn from(err: XmlWriterError) -> Error {
match err {
XmlWriterError::Io(err) => Error::Io(err),
_ => Error::InvalidData,
}
}
}
#[derive(PartialEq)]
enum Element {
Dictionary,
Array,
}
pub struct XmlWriter {
xml_writer: EventWriter,
stack: Vec,
expecting_key: bool,
written_prologue: bool,
// Not very nice
empty_namespace: Namespace,
}
impl XmlWriter {
pub fn new(writer: W) -> XmlWriter {
let config = EmitterConfig::new()
.line_separator("\n")
.indent_string("\t")
.perform_indent(true)
.write_document_declaration(false)
.normalize_empty_elements(true)
.cdata_to_characters(true)
.keep_element_names_stack(false)
.autopad_comments(true);
XmlWriter {
xml_writer: EventWriter::new_with_config(writer, config),
stack: Vec::new(),
expecting_key: false,
written_prologue: false,
empty_namespace: Namespace::empty(),
}
}
fn write_element_and_value(&mut self, name: &str, value: &str) -> Result<(), Error> {
self.start_element(name)?;
self.write_value(value)?;
self.end_element(name)?;
Ok(())
}
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),
})?;
Ok(())
}
fn end_element(&mut self, name: &str) -> Result<(), Error> {
self.xml_writer.write(XmlEvent::EndElement {
name: Some(Name::local(name)),
})?;
Ok(())
}
fn write_value(&mut self, value: &str) -> Result<(), Error> {
self.xml_writer.write(XmlEvent::Characters(value))?;
Ok(())
}
pub fn write(&mut self, event: &Event) -> Result<(), Error> {
::write(self, event)
}
}
impl Writer for XmlWriter {
fn write(&mut self, event: &Event) -> Result<(), Error> {
if !self.written_prologue {
self.xml_writer
.inner_mut()
.write_all(XML_PROLOGUE.as_bytes())?;
self.written_prologue = true;
}
if self.expecting_key {
match *event {
Event::EndDictionary => match self.stack.pop() {
Some(Element::Dictionary) => {
self.end_element("dict")?;
self.expecting_key = self.stack.last() == Some(&Element::Dictionary);
}
_ => return Err(Error::InvalidData),
},
Event::StringValue(ref value) => {
self.write_element_and_value("key", &*value)?;
self.expecting_key = false;
}
_ => return Err(Error::InvalidData),
}
} else {
match *event {
Event::StartArray(_) => {
self.start_element("array")?;
self.stack.push(Element::Array);
}
Event::EndArray => match self.stack.pop() {
Some(Element::Array) => self.end_element("array")?,
_ => return Err(Error::InvalidData),
},
Event::StartDictionary(_) => {
self.start_element("dict")?;
self.stack.push(Element::Dictionary);
}
Event::EndDictionary => return Err(Error::InvalidData),
Event::BooleanValue(true) => {
self.start_element("true")?;
self.end_element("true")?;
}
Event::BooleanValue(false) => {
self.start_element("false")?;
self.end_element("false")?;
}
Event::DataValue(ref value) => {
let base64_data = base64::encode_config(&value, base64::MIME);
self.write_element_and_value("data", &base64_data)?;
}
Event::DateValue(ref value) => {
self.write_element_and_value("date", &value.to_rfc3339())?
}
Event::IntegerValue(ref value) => {
self.write_element_and_value("integer", &value.to_string())?
}
Event::RealValue(ref value) => {
self.write_element_and_value("real", &value.to_string())?
}
Event::StringValue(ref value) => self.write_element_and_value("string", &*value)?,
};
self.expecting_key = self.stack.last() == Some(&Element::Dictionary);
}
// If there are no more open tags then write the element
if self.stack.len() == 0 {
// 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().write_all(b"\n")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use humantime::parse_rfc3339_weak;
use std::io::Cursor;
use super::*;
use events::Event::*;
#[test]
fn streaming_parser() {
let plist = &[
StartDictionary(None),
StringValue("Author".to_owned()),
StringValue("William Shakespeare".to_owned()),
StringValue("Lines".to_owned()),
StartArray(None),
StringValue("It is a tale told by an idiot,".to_owned()),
StringValue("Full of sound and fury, signifying nothing.".to_owned()),
EndArray,
StringValue("Death".to_owned()),
IntegerValue(1564),
StringValue("Height".to_owned()),
RealValue(1.60),
StringValue("Data".to_owned()),
DataValue(vec![0, 0, 0, 190, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0]),
StringValue("Birthdate".to_owned()),
DateValue(parse_rfc3339_weak("1981-05-16 11:32:06").unwrap().into()),
StringValue("Comment".to_owned()),
StringValue("2 < 3".to_owned()), // make sure characters are escaped
EndDictionary,
];
let mut cursor = Cursor::new(Vec::new());
{
let mut plist_w = XmlWriter::new(&mut cursor);
for item in plist {
plist_w.write(item).unwrap();
}
}
let comparison = "
\tAuthor
\tWilliam Shakespeare
\tLines
\t
\t\tIt is a tale told by an idiot,
\t\tFull of sound and fury, signifying nothing.
\t
\tDeath
\t1564
\tHeight
\t1.6
\tData
\tAAAAvgAAAAMAAAAeAAAA
\tBirthdate
\t1981-05-16T11:32:06Z
\tComment
\t2 < 3
";
let s = String::from_utf8(cursor.into_inner()).unwrap();
assert_eq!(s, comparison);
}
}