use std::collections::HashMap;
use std::hash::BuildHasherDefault;
use std::io::Read;
use std::iter::Peekable;
use std::time::{Duration, UNIX_EPOCH};
use chrono::DateTime;
use fnv::FnvHasher;
use rustc_serialize::base64::FromBase64;
use xml::reader::{EventReader, ParserConfig, Result as XmlResult, XmlEvent};
use plist::Plist;
use result::{Result, Error};
fn xml_event<R: Iterator<Item = XmlResult<XmlEvent>>, P>(input: &mut Peekable<R>,
mut predicate: P)
-> Result<()>
where P: FnMut(&XmlEvent) -> Option<bool>
{
match input.next() {
Some(Ok(e)) => {
match predicate(&e) {
Some(true) => Ok(()),
Some(false) => Err(Error::UnexpectedXmlEvent(e)),
None => xml_event(input, predicate),
}
}
Some(Err(e)) => Err(Error::from(e)),
None => Err(Error::UnexpectedXmlEof),
}
}
fn xml_start<R: Iterator<Item = XmlResult<XmlEvent>>>(input: &mut Peekable<R>,
local_name: &str)
-> Result<()> {
xml_event(input, |e| {
match *e {
XmlEvent::StartElement { ref name, .. } => {
Some(if &name.local_name[..] == local_name {
true
} else {
false
})
}
XmlEvent::Characters(ref _string) => None,
_ => Some(false),
}
})
}
fn xml_end<R: Iterator<Item = XmlResult<XmlEvent>>>(input: &mut Peekable<R>,
local_name: &str)
-> Result<String> {
let mut string = None;
try!(xml_event(input, |e| {
match *e {
XmlEvent::EndElement { ref name } => {
Some(if &name.local_name[..] == local_name {
true
} else {
false
})
}
XmlEvent::Characters(ref s) => {
string = Some(s.clone());
None
}
_ => Some(false),
}
}));
match string {
Some(s) => Ok(s),
None => Ok("".to_string()),
}
}
fn xml_content<R: Iterator<Item = XmlResult<XmlEvent>>>(input: &mut Peekable<R>,
local_name: &str)
-> Result<String> {
try!(xml_start(input, local_name));
xml_end(input, local_name)
}
fn xml_boolean<R: Iterator<Item = XmlResult<XmlEvent>>>(input: &mut Peekable<R>) -> Result<Plist> {
match input.next() {
Some(Ok(e)) => {
match e {
XmlEvent::StartElement { ref name, .. } => {
match &name.local_name[..] {
"true" => {
try!(xml_end(input, "true"));
return Ok(Plist::Boolean(true));
}
"false" => {
try!(xml_end(input, "false"));
return Ok(Plist::Boolean(false));
}
_ => (),
}
}
XmlEvent::Characters(ref _string) => return xml_boolean(input),
_ => (),
};
Err(Error::UnexpectedXmlEvent(e))
}
Some(Err(e)) => Err(Error::from(e)),
None => Err(Error::UnexpectedXmlEof),
}
}
fn xml_integer<R: Iterator<Item = XmlResult<XmlEvent>>>(input: &mut Peekable<R>) -> Result<Plist> {
let string = try!(xml_content(input, "integer"));
let integer = try!(i64::from_str_radix(string.as_ref(), 10));
Ok(Plist::Integer(integer))
}
fn xml_real<R: Iterator<Item = XmlResult<XmlEvent>>>(input: &mut Peekable<R>) -> Result<Plist> {
let string = try!(xml_content(input, "real"));
let real = try!(string.parse());
Ok(Plist::Real(real))
}
fn xml_date<R: Iterator<Item = XmlResult<XmlEvent>>>(input: &mut Peekable<R>) -> Result<Plist> {
let string = try!(xml_content(input, "date"));
let secs = try!(DateTime::parse_from_rfc3339(string.as_ref())).timestamp() as u64;
Ok(Plist::DateTime(UNIX_EPOCH + Duration::from_secs(secs)))
}
fn xml_data<R: Iterator<Item = XmlResult<XmlEvent>>>(input: &mut Peekable<R>) -> Result<Plist> {
let string = try!(xml_content(input, "data"));
let stripped = string.split_whitespace()
.fold(String::with_capacity(string.len()), |mut x, y| {
x.push_str(y);
x
});
Ok(Plist::Data(try!(stripped.from_base64())))
}
fn xml_string<R: Iterator<Item = XmlResult<XmlEvent>>>(input: &mut Peekable<R>) -> Result<Plist> {
Ok(Plist::String(try!(xml_content(input, "string"))))
}
fn xml_array<R: Iterator<Item = XmlResult<XmlEvent>>>(input: &mut Peekable<R>) -> Result<Plist> {
try!(xml_start(input, "array"));
let mut array = Vec::new();
loop {
match xml_object(input) {
Ok(o) => array.push(o),
Err(Error::UnexpectedXmlEvent(e)) => {
let valid = if let XmlEvent::EndElement { ref name } = e {
&name.local_name[..] == "array"
} else {
false
};
if valid {
break;
} else {
return Err(Error::UnexpectedXmlEvent(e));
}
}
Err(e) => return Err(e),
};
}
Ok(Plist::Array(array))
}
fn xml_dict<R: Iterator<Item = XmlResult<XmlEvent>>>(input: &mut Peekable<R>) -> Result<Plist> {
try!(xml_start(input, "dict"));
let fnv = BuildHasherDefault::<FnvHasher>::default();
let mut dict = HashMap::with_hasher(fnv);
loop {
match xml_content(input, "key") {
Ok(key) => {
let value = try!(xml_object(input));
dict.insert(key, value);
}
Err(Error::UnexpectedXmlEvent(e)) => {
let valid = if let XmlEvent::EndElement { ref name } = e {
&name.local_name[..] == "dict"
} else {
false
};
if valid {
break;
} else {
return Err(Error::UnexpectedXmlEvent(e));
}
}
Err(e) => return Err(e),
};
}
Ok(Plist::Dict(dict))
}
fn xml_object<R: Iterator<Item = XmlResult<XmlEvent>>>(input: &mut Peekable<R>) -> Result<Plist> {
let object_func: Option<fn(&mut Peekable<R>) -> Result<Plist>> = match input.peek() {
Some(&Ok(XmlEvent::StartElement { ref name, .. })) => {
Some(match &name.local_name[..] {
"true" => xml_boolean,
"false" => xml_boolean,
"integer" => xml_integer,
"real" => xml_real,
"date" => xml_date,
"data" => xml_data,
"string" => xml_string,
"array" => xml_array,
"dict" => xml_dict,
s => return Err(Error::XmlObjectNotSupported(s.to_string())),
})
}
_ => None,
};
if let Some(func) = object_func {
return func(input);
}
match input.next() {
Some(Ok(XmlEvent::Characters(_string))) => xml_object(input),
Some(Ok(e)) => Err(Error::UnexpectedXmlEvent(e)),
Some(Err(e)) => Err(Error::from(e)),
None => Err(Error::UnexpectedXmlEof),
}
}
pub fn from_xml_reader<R: Read>(input: &mut R) -> Result<Plist> {
let config = ParserConfig {
trim_whitespace: false,
whitespace_to_characters: true,
cdata_to_characters: false,
ignore_comments: true,
coalesce_characters: true,
};
let mut events = EventReader::new_with_config(input, config).into_iter().peekable();
try!(xml_event(&mut events, |e| {
Some(if let &XmlEvent::StartDocument { .. } = e {
true
} else {
false
})
}));
try!(xml_start(&mut events, "plist"));
let object = try!(xml_object(&mut events));
try!(xml_end(&mut events, "plist"));
Ok(object)
}