Repository: tafia/quick-xml
Pull request #6 (2016-02-28 14:21:34)

Improve Element API and string conversion
Now using &[u8] everywhere in Element and Attribute, and added an AsStr trait for converting any byte slice to an &str. Renamed Element::as_bytes() to Element::name(), and added an Element::content() method. Removed Element::as_str(), no longer needed since it is handled by the AsStr trait.

On Attribute, made the value field a &[u8], to make it consistent with the type of the key; both can be converted with a call to as_str().

Will rebase if needed when #5 is merged.

tafia: There is a conflict with the PR #5.
Can you please rebase?
tafia: Could you also update the readme (just copy lib.rs example).
Thanks

Patch:
diff --git a/README.md b/README.md
index 0484243..10016bf 100644
--- a/README.md
+++ b/README.md
@@ -25,8 +25,8 @@ extern crate quick_xml;
 ```rust
 use quick_xml::{XmlReader, Event};
 
-let xml = r#"<tag1 att1 = \"test\">
-                <tag2><!--Test comment-->Test</b>
+let xml = r#"<tag1 att1 = "test">
+                <tag2><!--Test comment-->Test</tag2>
                 <tag2>
                     Test 2
                 </tag2>
@@ -37,7 +37,7 @@ let mut txt = Vec::new();
 for r in reader {
     match r {
         Ok(Event::Start(ref e)) => {
-            match e.as_bytes() {
+            match e.name() {
                 b"tag1" => println!("attributes values: {:?}", 
                                  e.attributes().map(|a| a.unwrap().1).collect::<Vec<_>>()),
                 b"tag2" => count += 1,
@@ -45,7 +45,7 @@ for r in reader {
             }
         },
         Ok(Event::Text(e)) => txt.push(e.into_string()),
-        Err(e) => println!("{:?}", e),
+        Err(e) => panic!("{:?}", e),
         _ => (),
     }
 }
@@ -54,7 +54,7 @@ for r in reader {
 ### Writer
 
 ```rust
-use quick_xml::{Element, Event, XmlReader, XmlWriter};
+use quick_xml::{AsStr, Element, Event, XmlReader, XmlWriter};
 use quick_xml::Event::*;
 use std::io::Cursor;
 use std::iter;
@@ -64,18 +64,19 @@ let reader = XmlReader::from_str(xml).trim_text(true);
 let mut writer = XmlWriter::new(Cursor::new(Vec::new()));
 for r in reader {
     match r {
-        Ok(Event::Start(ref e)) if e.as_bytes() == b"this_tag" => {
+        Ok(Event::Start(ref e)) if e.name() == b"this_tag" => {
             // collect existing attributes
             let mut attrs = e.attributes().map(|attr| attr.unwrap()).collect::<Vec<_>>();
 
-            // adds a new my-key="some value" attribute
-            attrs.push((b"my-key", "some value"));
+            // copy existing attributes, adds a new my-key="some value" attribute
+            let mut elem = Element::new("my_elem").with_attributes(attrs);
+            elem.push_attribute(b"my-key", "some value");
 
             // writes the event to the writer
-            assert!(writer.write(Start(Element::new("my_elem", attrs.into_iter()))).is_ok());
+            assert!(writer.write(Start(elem)).is_ok());
         },
-        Ok(Event::End(ref e)) if e.as_bytes() == b"this_tag" => {
-            assert!(writer.write(End(Element::new("my_elem", iter::empty::<(&str, &str)>()))).is_ok());
+        Ok(Event::End(ref e)) if e.name() == b"this_tag" => {
+            assert!(writer.write(End(Element::new("my_elem"))).is_ok());
         },
         Ok(e) => assert!(writer.write(e).is_ok()),
         Err(e) => panic!("{:?}", e),
diff --git a/src/attributes.rs b/src/attributes.rs
index e8b6b6a..0e7a8e8 100644
--- a/src/attributes.rs
+++ b/src/attributes.rs
@@ -25,7 +25,7 @@ impl<'a> Attributes<'a> {
 }
 
 impl<'a> Iterator for Attributes<'a> {
-    type Item = Result<(&'a[u8], &'a str)>;
+    type Item = Result<(&'a [u8], &'a [u8])>;
     fn next(&mut self) -> Option<Self::Item> {
         
         if self.was_error { return None; }
@@ -101,9 +101,7 @@ impl<'a> Iterator for Attributes<'a> {
         }
         self.position += end_val.unwrap() + 1;
 
-        match ::std::str::from_utf8(&self.bytes[(p + start_val.unwrap())..(p + end_val.unwrap())]) {
-            Ok(s) => Some(Ok((&self.bytes[(p + start_key)..(p + end_key.unwrap())], s))),
-            Err(e) => Some(Err(Error::from(e))),
-        }
+        Some(Ok((&self.bytes[(p + start_key)..(p + end_key.unwrap())],
+           &self.bytes[(p + start_val.unwrap())..(p + end_val.unwrap())])))
     }
 }
diff --git a/src/lib.rs b/src/lib.rs
index a456416..8bc4d3c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -17,7 +17,7 @@
 //! for r in reader {
 //!     match r {
 //!         Ok(Event::Start(ref e)) => {
-//!             match e.as_bytes() {
+//!             match e.name() {
 //!                 b"tag1" => println!("attributes values: {:?}", 
 //!                                  e.attributes().map(|a| a.unwrap().1).collect::<Vec<_>>()),
 //!                 b"tag2" => count += 1,
@@ -34,7 +34,7 @@
 //! # Example of transforming XML
 //!
 //! ```
-//! use quick_xml::{Element, Event, XmlReader, XmlWriter};
+//! use quick_xml::{AsStr, Element, Event, XmlReader, XmlWriter};
 //! use quick_xml::Event::*;
 //! use std::io::Cursor;
 //! use std::iter;
@@ -44,7 +44,7 @@
 //! let mut writer = XmlWriter::new(Cursor::new(Vec::new()));
 //! for r in reader {
 //!     match r {
-//!         Ok(Event::Start(ref e)) if e.as_bytes() == b"this_tag" => {
+//!         Ok(Event::Start(ref e)) if e.name() == b"this_tag" => {
 //!             // collect existing attributes
 //!             let mut attrs = e.attributes().map(|attr| attr.unwrap()).collect::<Vec<_>>();
 //!
@@ -55,7 +55,7 @@
 //!             // writes the event to the writer
 //!             assert!(writer.write(Start(elem)).is_ok());
 //!         },
-//!         Ok(Event::End(ref e)) if e.as_bytes() == b"this_tag" => {
+//!         Ok(Event::End(ref e)) if e.name() == b"this_tag" => {
 //!             assert!(writer.write(End(Element::new("my_elem"))).is_ok());
 //!         },
 //!         Ok(e) => assert!(writer.write(e).is_ok()),
@@ -94,6 +94,19 @@ enum TagState {
     Closed,
 }
 
+/// A trait to support on-demand conversion from UTF-8
+pub trait AsStr {
+    /// Converts this to an &str
+    fn as_str(&self) -> Result<&str>;
+}
+
+/// Implements AsStr for a byte slice
+impl AsStr for [u8] {
+    fn as_str(&self) -> Result<&str> {
+        from_utf8(self).map_err(Error::Utf8)
+    }
+}
+
 /// Xml reader
 ///
 /// Consumes a `BufRead` and streams xml Event
@@ -154,11 +167,11 @@ impl<B: BufRead> XmlReader<B> {
         let end = end.as_ref();
         loop {
             match self.next() {
-                Some(Ok(Event::End(ref e))) if e.as_bytes() == end => {
+                Some(Ok(Event::End(ref e))) if e.name() == end => {
                     if depth == 0 { return Ok(()); }
                     depth -= 1;
                 },
-                Some(Ok(Event::Start(ref e))) if e.as_bytes() == end => depth += 1,
+                Some(Ok(Event::Start(ref e))) if e.name() == end => depth += 1,
                 Some(Err(e)) => return Err(e),
                 None => {
                     warn!("EOF instead of {:?}", from_utf8(end));
@@ -177,7 +190,7 @@ impl<B: BufRead> XmlReader<B> {
             Some(Ok(Event::Text(e))) => {
                 self.read_to_end(end).and_then(|_| e.into_string())
             },
-            Some(Ok(Event::End(ref e))) if e.as_bytes() == end.as_ref() => Ok("".to_owned()),
+            Some(Ok(Event::End(ref e))) if e.name() == end.as_ref() => Ok("".to_owned()),
             Some(Err(e)) => Err(e),
             None => Err(Error::Unexpected("Reached EOF while reading text".to_owned())),
             Some(Ok(_)) => {
@@ -197,8 +210,8 @@ impl<B: BufRead> XmlReader<B> {
     /// let mut r = XmlReader::from_str("<a><b>test</b>\
     ///     <b>test 2</b><c/><b>test 3</b></a>").trim_text(true);
     /// let mut tests = Vec::new();
-    /// r.map_starts::<_, &str>(None, |r, e| match e.as_bytes() {
-    ///     b"a" => r.map_starts(Some("a"), |r, e| match e.as_bytes() {
+    /// r.map_starts::<_, &str>(None, |r, e| match e.name() {
+    ///     b"a" => r.map_starts(Some("a"), |r, e| match e.name() {
     ///         b"b" => r.read_text("b").map(|t| tests.push(t)),
     ///         name => r.read_to_end(name)
     ///     }),
@@ -214,7 +227,7 @@ impl<B: BufRead> XmlReader<B> {
                 let end = end.as_ref();
                 loop {
                     match self.next() {
-                        Some(Ok(Event::End(ref e))) if e.as_bytes() == end => return Ok(()),
+                        Some(Ok(Event::End(ref e))) if e.name() == end => return Ok(()),
                         Some(Ok(Event::Start(ref e))) => try!(f(self, e)),
                         Some(Err(e)) => return Err(e),
                         None => return Err(Error::Unexpected(format!("Unexpected end of {:?}",
@@ -271,7 +284,7 @@ impl<B: BufRead> XmlReader<B> {
                     b'/' => {
                         if self.with_check {
                             let e = self.opened.pop().unwrap();
-                            if &buf[1..] != e.as_bytes() {
+                            if &buf[1..] != e.name() {
                                 self.exit = true;
                                 return Some(Err(Error::Malformed(format!(
                                         "End event {:?} doesn't match last opened element {:?}, opened: {:?}", 
@@ -443,13 +456,13 @@ impl Element {
     }
 
     /// name as &[u8] (without eventual attributes)
-    pub fn as_bytes(&self) -> &[u8] {
+    pub fn name(&self) -> &[u8] {
         &self.buf[self.start..self.name_end]
     }
 
-    /// name as str, (without eventual attributes)
-    pub fn as_str(&self) -> Result<&str> {
-        from_utf8(self.as_bytes()).map_err(Error::Utf8)
+    /// whole content as &[u8] (including eventual attributes)
+    pub fn content(&self) -> &[u8] {
+        &self.buf[self.start..self.end]
     }
 
     /// get attributes iterator
@@ -457,6 +470,18 @@ impl Element {
         Attributes::new(&self.buf[self.start..self.end], self.name_end)
     }
 
+    /// extend the attributes of this element from an iterator over (key, value) tuples.
+    /// Key and value can be anything that implements the AsRef<[u8]> trait,
+    /// like byte slices and strings.
+    pub fn extend_attributes<K, V, I>(&mut self, attributes: I) -> &mut Element
+        where K: AsRef<[u8]>, V: AsRef<[u8]>, I: IntoIterator<Item = (K, V)>
+    {
+        for attr in attributes {
+            self.push_attribute(attr.0, attr.1);
+        }
+        self
+    }
+
     /// consumes entire self (including eventual attributes!) and returns `String`
     ///
     /// useful when we need to get Text event value (which don't have attributes)
@@ -483,7 +508,7 @@ impl Element {
 impl fmt::Debug for Element {
     fn fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {
         write!(f, "Element {{ buf: {:?}, name_end: {}, end: {} }}", 
-               self.as_str(), self.name_end, self.end)
+               self.content().as_str(), self.name_end, self.end)
     }
 }
 
@@ -593,9 +618,9 @@ impl<W: Write> XmlWriter<W> {
     /// Writes the given event to the underlying writer.
     pub fn write(&mut self, event: Event) -> Result<()> {
         match event {
-            Event::Start(e) => self.write_start_tag(e),
+            Event::Start(ref e) => self.write_wrapped_str(b"<", e, b">"),
             Event::End(ref e) => self.write_wrapped_str(b"</", e, b">"),
-            Event::Text(ref e) => self.write_bytes(e.as_bytes()),
+            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"?>"),
@@ -608,15 +633,9 @@ impl<W: Write> XmlWriter<W> {
         Ok(())
     }
 
-    fn write_start_tag(&mut self, element: Element) -> Result<()> {
-        try!(self.write_bytes(b"<"));
-        try!(self.write_bytes(&try!(element.into_string()).into_bytes()));
-        self.write_bytes(b">")
-    }
-
     fn write_wrapped_str(&mut self, before: &[u8], element: &Element, after: &[u8]) -> Result<()> {
         try!(self.write_bytes(before));
-        try!(self.write_bytes(element.as_bytes()));
+        try!(self.write_bytes(&element.content()));
         self.write_bytes(after)
     }
 


Test patch:
diff --git a/src/test.rs b/src/test.rs
index 23f5b23..e2d5998 100644
--- a/src/test.rs
+++ b/src/test.rs
@@ -1,5 +1,6 @@
-use super::{Element, XmlReader, XmlWriter};
+use super::{AsStr, Element, XmlReader, XmlWriter};
 use super::Event::*;
+use super::error::Result;
 use std::str::from_utf8;
 use std::io::Cursor;
 
@@ -8,8 +9,8 @@ macro_rules! next_eq {
         $(
             match $r.next() {
                 Some(Ok($t(ref e))) => {
-                    assert!(e.as_bytes() == $bytes, "expecting {:?}, found {:?}", 
-                            from_utf8($bytes), e.as_str());
+                    assert!(e.name() == $bytes, "expecting {:?}, found {:?}", 
+                            from_utf8($bytes), e.content().as_str());
                 },
                 Some(Ok(e)) => {
                     assert!(false, "expecting {:?}, found {:?}", 
@@ -149,8 +150,8 @@ fn test_write_attrs() {
         let event = event.unwrap();
         let event = match event {
             Start(elem) => {
-                let mut attrs = elem.attributes().map(|e| e.unwrap()).collect::<Vec<_>>();
-                attrs.extend_from_slice(&[(b"a", "b"), (b"c", "d")]);
+                let mut attrs = elem.attributes().collect::<Result<Vec<_>>>().unwrap();
+                attrs.extend_from_slice(&[(b"a", b"b"), (b"c", b"d")]);
                 let mut elem = Element::new("copy").with_attributes(attrs);
                 elem.push_attribute("x", "y");
                 Start(elem)
@@ -170,8 +171,8 @@ fn test_map_starts() {
     let mut r = XmlReader::from_str("<a><b>test</b>\
         <b>test 2</b><c/><b>test 3</b></a>").trim_text(true);
     let mut tests = Vec::new();
-    r.map_starts::<_, &str>(None, |r, e| match e.as_bytes() {
-        b"a" => r.map_starts(Some("a"), |r, e| match e.as_bytes() {
+    r.map_starts::<_, &str>(None, |r, e| match e.name() {
+        b"a" => r.map_starts(Some("a"), |r, e| match e.name() {
             b"b" => r.read_text("b").map(|t| tests.push(t)),
             name => r.read_to_end(name)
         }),