Repository: tafia/quick-xml
Pull request #10 (2016-03-25 04:24:36)

Xmldecl
Adds [XmlDecl parsing](https://www.w3.org/TR/REC-xml/#sec-prolog-dtd)

- Rename `Event::Header` into `Event::Decl`
- Parses, version, encoding and standalone out of `<?xml ... ?> event

The parsing itself is performed on demand, which won't impact performance when you do not want it.

Also adds support for `'` in attribute parsing


Patch:
diff --git a/src/attributes.rs b/src/attributes.rs
index 0e7a8e8..6787e71 100644
--- a/src/attributes.rs
+++ b/src/attributes.rs
@@ -32,7 +32,7 @@ impl<'a> Iterator for Attributes<'a> {
 
         let len = self.bytes.len();
         let p = self.position;
-        if len == p { return None; }
+        if len <= p { return None; }
 
         let mut iter = self.bytes[p..].iter().cloned().enumerate();
 
@@ -62,6 +62,7 @@ impl<'a> Iterator for Attributes<'a> {
         let mut end_key = None;
         let mut start_val = None;
         let mut end_val = None;
+        let mut quote = 0;
         loop {
             match iter.next() {
                 Some((i, b' '))
@@ -80,14 +81,15 @@ impl<'a> Iterator for Attributes<'a> {
                         end_key = Some(i);
                     }
                 },
-                Some((i, b'"')) => {
+                Some((i, q @ b'"')) | Some((i, q @ b'\'')) => {
                     if !has_equal {
                         self.was_error = true;
                         return Some(Err(Error::Malformed("Unexpected quote before '='".to_owned())));
                     }
                     if start_val.is_none() {
                         start_val = Some(i + 1);
-                    } else if end_val.is_none() {
+                        quote = q;
+                    } else if quote == q && end_val.is_none() {
                         end_val = Some(i);
                         break;
                     }
diff --git a/src/lib.rs b/src/lib.rs
index 86fba9d..e5e7121 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -260,11 +260,18 @@ impl<B: BufRead> XmlReader<B> {
                         return Some(Ok(Event::End(Element::from_buffer(buf, 1, len, len))))
                     },
                     b'?' => {
-                        if len > 1 && buf[len - 1] == b'?' {
-                            return Some(Ok(Event::Header(Element::from_buffer(buf, 1, len - 1, len - 1))));
+                        if len > 5 && buf[len - 1] == b'?' {
+                            if &buf[1..4] == b"xml" && is_whitespace(buf[4]) {
+                                return Some(Ok(Event::Decl(XmlDecl { 
+                                    element: Element::from_buffer(buf, 1, len - 1, 3)
+                                })));
+                            } else {
+                                return Some(Err(Error::Malformed(
+                                            "Xml declaration must start with '?xml '".to_owned())));
+                            }
                         } else {
                             self.exit = true;
-                            return Some(Err(Error::Malformed("Unescaped Header event".to_owned())));
+                            return Some(Err(Error::Malformed("Unescaped XmlDecl event".to_owned())));
                         }
                     },
                     b'!' => {
@@ -476,6 +483,54 @@ impl fmt::Debug for Element {
     }
 }
 
+/// Wrapper around Element to parse XmlDecl
+///
+/// Postpone element parsing only when needed
+#[derive(Debug)]
+pub struct XmlDecl {
+    element: Element,
+}
+
+impl XmlDecl {
+
+    /// Gets xml version, including quotes (' or ")
+    pub fn version(&self) -> Result<&[u8]> {
+        match self.element.attributes().next() {
+            Some(Err(e)) => Err(e),
+            Some(Ok((b"version", v))) => Ok(v),
+            Some(Ok((k, _))) => Err(Error::Malformed(format!(
+                        "XmlDecl must start with 'version' attribute, found {:?}", k.as_str()))),
+            None => Err(Error::Malformed(
+                    "XmlDecl must start with 'version' attribute, found none".to_owned())),
+        }
+    }
+
+    /// Gets xml encoding, including quotes (' or ")
+    pub fn encoding(&self) -> Option<Result<&[u8]>> {
+        for a in self.element.attributes() {
+            match a {
+                Err(e) => return Some(Err(e)),
+                Ok((b"encoding", v)) => return Some(Ok(v)),
+                _ => (),
+            }
+        }
+        None
+    }
+
+    /// Gets xml standalone, including quotes (' or ")
+    pub fn standalone(&self) -> Option<Result<&[u8]>> {
+        for a in self.element.attributes() {
+            match a {
+                Err(e) => return Some(Err(e)),
+                Ok((b"standalone", v)) => return Some(Ok(v)),
+                _ => (),
+            }
+        }
+        None
+    }
+    
+}
+
 /// Event to interprete node as they are parsed
 #[derive(Debug)]
 pub enum Event {
@@ -489,8 +544,8 @@ pub enum Event {
     Comment(Element),
     /// <![CDATA[...]]>
     CData(Element),
-    /// <?...?>
-    Header(Element),
+    /// <?xml ...?>
+    Decl(XmlDecl),
 }
 
 impl Event {
@@ -502,8 +557,8 @@ impl Event {
             Event::End(ref e) |
             Event::Text(ref e) |
             Event::Comment(ref e) |
-            Event::CData(ref e) |
-            Event::Header(ref e) => e,
+            Event::CData(ref e) => e,
+            Event::Decl(ref e) => &e.element,
         }
     }
 }
@@ -587,7 +642,7 @@ impl<W: Write> XmlWriter<W> {
             Event::Text(ref e) => self.write_bytes(e.content()),
             Event::Comment(ref e) => self.write_wrapped_str(b"<!--", e, b"-->"),
             Event::CData(ref e) => self.write_wrapped_str(b"<![CDATA[", e, b"]]>"),
-            Event::Header(ref e) => self.write_wrapped_str(b"<?", e, b"?>"),
+            Event::Decl(ref e) => self.write_wrapped_str(b"<?", &e.element, b"?>"),
         }
     }
 


Test patch:
diff --git a/src/test.rs b/src/test.rs
index b8670e2..c79a9b2 100644
--- a/src/test.rs
+++ b/src/test.rs
@@ -73,9 +73,30 @@ fn test_comment() {
 }
 
 #[test]
-fn test_header() {
-    let mut r = XmlReader::from_str("<?header?>").trim_text(true);
-    next_eq!(r, Header, b"header");
+fn test_xml_decl() {
+    let mut r = XmlReader::from_str("<?xml version=\"1.0\" encoding='utf-8'?>").trim_text(true);
+    match r.next() {
+        Some(Ok(Decl(ref e))) => {
+            match e.version() {
+                Ok(v) => assert!(v == b"1.0", "expecting version '1.0', got '{:?}", v.as_str()),
+                Err(e) => assert!(false, "{:?}", e),
+            }
+            match e.encoding() {
+                Some(Ok(v)) => assert!(v == b"utf-8", "expecting encoding 'utf-8', got '{:?}",
+                                       v.as_str()),
+                Some(Err(e)) => assert!(false, "{:?}", e),
+                None => assert!(false, "cannot find encoding"),
+            }
+            match e.standalone() {
+                None => assert!(true),
+                e => assert!(false, "doesn't expect standalone, got {:?}", e),
+            }
+        },
+        Some(Err((e, pos))) => {
+            assert!(false, "{:?} at buffer position {}", e, pos);
+        },
+        _ => assert!(false, "unable to parse XmlDecl"),
+    }
 }
 
 #[test]