plist 0.0.11

A rusty plist parser. Very much a work in progress.
Documentation
use chrono::{DateTime, UTC};
use rustc_serialize::base64::FromBase64;
use std::io::Read;
use std::str::FromStr;
use xml_rs::reader::{EventReader as XmlEventReader, ParserConfig, XmlEvent};

use super::super::{ReadError, ReadResult, PlistEvent};

pub struct EventReader<R: Read> {
    xml_reader: XmlEventReader<R>,
    queued_event: Option<XmlEvent>,
    element_stack: Vec<String>,
    finished: bool,
}

impl<R: Read> EventReader<R> {
    pub fn new(reader: R) -> EventReader<R> {
        let config = ParserConfig {
            trim_whitespace: false,
            whitespace_to_characters: true,
            cdata_to_characters: true,
            ignore_comments: true,
            coalesce_characters: true,
        };

        EventReader {
            xml_reader: XmlEventReader::new_with_config(reader, config),
            queued_event: None,
            element_stack: Vec::new(),
            finished: false,
        }
    }

    fn read_content<F>(&mut self, f: F) -> ReadResult<PlistEvent>
        where F: FnOnce(String) -> ReadResult<PlistEvent>
    {
        match self.xml_reader.next() {
            Ok(XmlEvent::Characters(s)) => f(s),
            Ok(event @ XmlEvent::EndElement{..}) => {
                self.queued_event = Some(event);
                f("".to_owned())
            }
            _ => Err(ReadError::InvalidData),
        }
    }

    fn next_event(&mut self) -> Result<XmlEvent, ()> {
        if let Some(event) = self.queued_event.take() {
            Ok(event)
        } else {
            self.xml_reader.next().map_err(|_| ())
        }
    }

    fn read_next(&mut self) -> Option<ReadResult<PlistEvent>> {
        loop {
            match self.next_event() {
                Ok(XmlEvent::StartElement { name, .. }) => {
                    // Add the current element to the element stack
                    self.element_stack.push(name.local_name.clone());

                    match &name.local_name[..] {
                        "plist" => return Some(Ok(PlistEvent::StartPlist)),
                        "array" => return Some(Ok(PlistEvent::StartArray(None))),
                        "dict" => return Some(Ok(PlistEvent::StartDictionary(None))),
                        "key" => return Some(self.read_content(|s| Ok(PlistEvent::StringValue(s)))),
                        "true" => return Some(Ok(PlistEvent::BooleanValue(true))),
                        "false" => return Some(Ok(PlistEvent::BooleanValue(false))),
                        "data" => {
                            return Some(self.read_content(|s| {
                                let s: String = s.replace(" ", "").replace("\t", "");
                                match FromBase64::from_base64(&s[..]) {
                                    Ok(b) => Ok(PlistEvent::DataValue(b)),
                                    Err(_) => Err(ReadError::InvalidData),
                                }
                            }))
                        }
                        "date" => {
                            return Some(self.read_content(|s| {
                                let date = try!(DateTime::parse_from_rfc3339(&s));
                                Ok(PlistEvent::DateValue(date.with_timezone(&UTC)))
                            }))
                        }
                        "integer" => {
                            return Some(self.read_content(|s| {
                                match FromStr::from_str(&s) {
                                    Ok(i) => Ok(PlistEvent::IntegerValue(i)),
                                    Err(_) => Err(ReadError::InvalidData),
                                }
                            }))
                        }
                        "real" => {
                            return Some(self.read_content(|s| {
                                match FromStr::from_str(&s) {
                                    Ok(f) => Ok(PlistEvent::RealValue(f)),
                                    Err(_) => Err(ReadError::InvalidData),
                                }
                            }))
                        }
                        "string" => {
                            return Some(self.read_content(|s| Ok(PlistEvent::StringValue(s))))
                        }
                        _ => return Some(Err(ReadError::InvalidData)),
                    }
                }
                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 Some(Err(ReadError::InvalidData)),
                        None => return Some(Err(ReadError::InvalidData)),
                    }

                    match &name.local_name[..] {
                        "array" => return Some(Ok(PlistEvent::EndArray)),
                        "dict" => return Some(Ok(PlistEvent::EndDictionary)),
                        "plist" => return Some(Ok(PlistEvent::EndPlist)),
                        _ => (),
                    }
                }
                Ok(XmlEvent::EndDocument) => {
                    match self.element_stack.is_empty() {
                        true => return None,
                        false => return Some(Err(ReadError::UnexpectedEof)),
                    }
                }
                Err(_) => return Some(Err(ReadError::InvalidData)),
                _ => (),
            }
        }
    }
}

impl<R: Read> Iterator for EventReader<R> {
    type Item = ReadResult<PlistEvent>;

    fn next(&mut self) -> Option<ReadResult<PlistEvent>> {
        if self.finished {
            None
        } else {
            match self.read_next() {
                Some(Ok(event)) => Some(Ok(event)),
                Some(Err(err)) => {
                    self.finished = true;
                    Some(Err(err))
                }
                None => {
                    self.finished = true;
                    None
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use chrono::{TimeZone, UTC};
    use std::fs::File;
    use std::path::Path;

    use super::*;
    use PlistEvent;

    #[test]
    fn streaming_parser() {
        use PlistEvent::*;

        let reader = File::open(&Path::new("./tests/data/xml.plist")).unwrap();
        let streaming_parser = EventReader::new(reader);
        let events: Vec<PlistEvent> = streaming_parser.map(|e| e.unwrap()).collect();

        let comparison = &[StartPlist,
                           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(UTC.ymd(1981, 05, 16).and_hms(11, 32, 06)),
                           StringValue("Blank".to_owned()),
                           StringValue("".to_owned()),
                           EndDictionary,
                           EndPlist];

        assert_eq!(events, comparison);
    }

    #[test]
    fn bad_data() {
        let reader = File::open(&Path::new("./tests/data/xml_error.plist")).unwrap();
        let streaming_parser = EventReader::new(reader);
        let events: Vec<_> = streaming_parser.collect();

        assert!(events.last().unwrap().is_err());
    }
}