escape text and attribute values

Astro created

Change summary

src/element.rs | 26 +++++++++++++++++++++++---
src/tests.rs   | 28 ++++++++++++++++++++++++++++
2 files changed, 51 insertions(+), 3 deletions(-)

Detailed changes

src/element.rs 🔗

@@ -18,6 +18,22 @@ use std::slice;
 
 use convert::{IntoElements, IntoAttributeValue, ElementEmitter};
 
+/// Escape XML text
+pub fn write_escaped<W: Write>(writer: &mut W, input: &str) -> Result<()> {
+    for c in input.chars() {
+        match c {
+            '&' => write!(writer, "&amp;")?,
+            '<' => write!(writer, "&lt;")?,
+            '>' => write!(writer, "&gt;")?,
+            '\'' => write!(writer, "&apos;")?,
+            '"' => write!(writer, "&quot;")?,
+            _ => write!(writer, "{}", c)?,
+        }
+    }
+
+    Ok(())
+}
+
 /// A node in an element tree.
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum Node {
@@ -71,7 +87,7 @@ impl Node {
     fn write_to_inner<W: Write>(&self, writer: &mut W, last_namespace: &mut Option<String>) -> Result<()>{
         match *self {
             Node::Element(ref elmt) => elmt.write_to_inner(writer, last_namespace)?,
-            Node::Text(ref s) => write!(writer, "{}", s)?,
+            Node::Text(ref s) => write_escaped(writer, s)?,
         }
 
         Ok(())
@@ -323,13 +339,17 @@ impl Element {
 
         if let Some(ref ns) = self.namespace {
             if *last_namespace != self.namespace {
-                write!(writer, " xmlns=\"{}\"", ns)?;
+                write!(writer, " xmlns=\"")?;
+                write_escaped(writer, ns)?;
+                write!(writer, "\"")?;
                 *last_namespace = Some(ns.clone());
             }
         }
 
         for (key, value) in &self.attributes {
-            write!(writer, " {}=\"{}\"", key, value)?;
+            write!(writer, " {}=\"", key)?;
+                write_escaped(writer, value)?;
+                write!(writer, "\"")?;
         }
 
         if self.children.is_empty() {

src/tests.rs 🔗

@@ -43,6 +43,34 @@ fn writer_works() {
     assert_eq!(String::from_utf8(writer).unwrap(), TEST_STRING);
 }
 
+#[test]
+fn writer_escapes_attributes() {
+    let root = Element::builder("root")
+        .attr("a", "\"Air\" quotes")
+        .build();
+    let mut writer = Vec::new();
+    {
+        root.write_to(&mut writer).unwrap();
+    }
+    assert_eq!(String::from_utf8(writer).unwrap(),
+               r#"<?xml version="1.0" encoding="utf-8"?><root a="&quot;Air&quot; quotes" />"#
+    );
+}
+
+#[test]
+fn writer_escapes_text() {
+    let root = Element::builder("root")
+        .append("<3")
+        .build();
+    let mut writer = Vec::new();
+    {
+        root.write_to(&mut writer).unwrap();
+    }
+    assert_eq!(String::from_utf8(writer).unwrap(),
+               r#"<?xml version="1.0" encoding="utf-8"?><root>&lt;3</root>"#
+    );
+}
+
 #[test]
 fn builder_works() {
     let elem = Element::builder("a")