aboutsummaryrefslogtreecommitdiffstats
path: root/src/xml/reader.rs
diff options
context:
space:
mode:
authorEdward Barnard2015-08-28 21:17:14 +0700
committerEdward Barnard2015-08-28 21:17:14 +0700
commit3e41485288049b1d7a9be60b84b4d78dcd400f39 (patch)
tree66692c381e6e9f4337868ac8ca43c06dcb909e75 /src/xml/reader.rs
parent1ef9fbdc9377a831eb4abccaa4b18d89b5fd560d (diff)
downloadrust-plist-3e41485288049b1d7a9be60b84b4d78dcd400f39.tar.bz2
Expand folder structure
Diffstat (limited to 'src/xml/reader.rs')
-rw-r--r--src/xml/reader.rs143
1 files changed, 143 insertions, 0 deletions
diff --git a/src/xml/reader.rs b/src/xml/reader.rs
new file mode 100644
index 0000000..f047db4
--- /dev/null
+++ b/src/xml/reader.rs
@@ -0,0 +1,143 @@
+use rustc_serialize::base64::FromBase64;
+use std::io::Read;
+use std::str::FromStr;
+use xml_rs::reader::{EventReader, ParserConfig};
+use xml_rs::reader::events::XmlEvent;
+
+use super::super::{ParserError, ParserResult, PlistEvent};
+
+pub struct StreamingParser<R: Read> {
+ xml_reader: EventReader<R>,
+ element_stack: Vec<String>
+}
+
+impl<R: Read> StreamingParser<R> {
+ pub fn new(reader: R) -> StreamingParser<R> {
+ let config = ParserConfig {
+ trim_whitespace: false,
+ whitespace_to_characters: true,
+ cdata_to_characters: true,
+ ignore_comments: true,
+ coalesce_characters: true,
+ };
+
+ StreamingParser {
+ xml_reader: EventReader::with_config(reader, config),
+ element_stack: Vec::new()
+ }
+ }
+
+ fn read_content<F>(&mut self, f: F) -> ParserResult<PlistEvent> where F:FnOnce(String) -> ParserResult<PlistEvent> {
+ match self.xml_reader.next() {
+ XmlEvent::Characters(s) => f(s),
+ _ => Err(ParserError::InvalidData)
+ }
+ }
+}
+
+impl<R: Read> Iterator for StreamingParser<R> {
+ type Item = ParserResult<PlistEvent>;
+
+ fn next(&mut self) -> Option<ParserResult<PlistEvent>> {
+ loop {
+ match self.xml_reader.next() {
+ 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| {
+ match FromBase64::from_base64(&s[..]) {
+ Ok(b) => Ok(PlistEvent::DataValue(b)),
+ Err(_) => Err(ParserError::InvalidData)
+ }
+ })),
+ "date" => return Some(self.read_content(|s| Ok(PlistEvent::DateValue(s)))),
+ "integer" => return Some(self.read_content(|s| {
+ match FromStr::from_str(&s) {
+ Ok(i) => Ok(PlistEvent::IntegerValue(i)),
+ Err(_) => Err(ParserError::InvalidData)
+ }
+ })),
+ "real" => return Some(self.read_content(|s| {
+ match FromStr::from_str(&s) {
+ Ok(f) => Ok(PlistEvent::RealValue(f)),
+ Err(_) => Err(ParserError::InvalidData)
+ }
+ })),
+ "string" => return Some(self.read_content(|s| Ok(PlistEvent::StringValue(s)))),
+ _ => return Some(Err(ParserError::InvalidData))
+ }
+ },
+ 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(ParserError::InvalidData)),
+ None => return Some(Err(ParserError::InvalidData))
+ }
+
+ match &name.local_name[..] {
+ "array" => return Some(Ok(PlistEvent::EndArray)),
+ "dict" => return Some(Ok(PlistEvent::EndDictionary)),
+ "plist" => return Some(Ok(PlistEvent::EndPlist)),
+ _ => ()
+ }
+ },
+ XmlEvent::EndDocument => {
+ match self.element_stack.is_empty() {
+ true => return None,
+ false => return Some(Err(ParserError::UnexpectedEof))
+ }
+ }
+ _ => ()
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ 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 = StreamingParser::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("Birthdate".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]),
+ EndDictionary,
+ EndPlist
+ ];
+
+ assert_eq!(events, comparison);
+ }
+} \ No newline at end of file