Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added XmlWriter and Element::create API #2

Merged
merged 4 commits into from
Feb 26, 2016
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub mod attributes;
mod test;

use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::io::{self, BufRead, BufReader, Write};
use std::iter::Iterator;
use std::path::Path;
use std::fmt;
Expand Down Expand Up @@ -303,6 +303,25 @@ impl Element {
Attributes::new(&self.buf[self.start..self.end], self.name_end)
}

/// Creates a new Element from the given name and attributes.
/// attributes are represented as an iterator over (key, value) tuples where
/// key and value are both &str.
pub fn create<'a, I: Iterator<Item = (&'a str, &'a str)>>(name: &str, attributes: I) -> Element {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few remarks:

  • you can use new instead of create and rename the existing (private) new function into from_buffer for instance (opened for other suggestion)
  • (&'a str, &'a str) is not symmetric with Attributes::Item = (&[u8], &str). I wanted to avoid utf8 conversion when possible because most of the time we (I ?) compare it with statically known names, thus I could always use key == b"mykey". Having symmetric behavior could help creating Elements based on other Elements (in your test, you had to use from_utf8), I think it is a common scenario
    • in your example, we could use Vec<u8> and push bytes directly
    • an alternative is to have 2 Attributes-like iterators, one pure &[u8], another one &str

let mut string = String::from(name);
let name_end = string.len();
for attr in attributes {
string.push_str(&format!(" {}=\"{}\"", attr.0, attr.1));
}
let bytes = string.into_bytes();
let end = bytes.len();
Element {
buf: bytes,
start: 0,
end: end,
name_end: name_end
}
}

/// consumes entire self (including eventual attributes!) and returns `String`
///
/// useful when we need to get Text event value (which don't have attributes)
Expand Down Expand Up @@ -395,3 +414,53 @@ fn read_until<R: BufRead>(r: &mut R, byte: u8, buf: &mut Vec<u8>) -> Result<usiz
Ok(read)
}

/// Xml writer
///
/// Consumes a `Write` and writes xml Events
pub struct XmlWriter<W: Write> {
/// underlying writer
writer: W
}

impl<W: Write> XmlWriter<W> {

/// Creates a XmlWriter from a generic Write
pub fn new(inner: W) -> XmlWriter<W> {
XmlWriter {
writer: inner
}
}

/// Consumes this Xml Writer, returning the underlying writer.
pub fn into_inner(self) -> W { self.writer }

pub fn write(&mut self, event: Event) -> Result<()> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment for the pub function (even simple)?

match event {
Event::Start(e) => self.write_start_tag(e),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just using self.write_wrapped_str(b"<", element, b">")?

Event::End(ref e) => self.write_wrapped_str(b"</", e, b">"),
Event::Text(ref e) => self.write_bytes(e.as_bytes()),
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"?>"),
}
}

#[inline]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alignment typo

fn write_bytes(&mut self, value: &[u8]) -> Result<()> {
try!(self.writer.write(value));
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()));
self.write_bytes(after)
}

}
42 changes: 41 additions & 1 deletion src/test.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::{XmlReader, Element};
use super::{Element, XmlReader, XmlWriter};
use super::Event::*;
use std::str::from_utf8;
use std::io::Cursor;

macro_rules! next_eq {
($r: expr, $($t:path, $bytes:expr),*) => {
Expand Down Expand Up @@ -125,3 +126,42 @@ fn test_nested() {
);
}

#[test]
fn test_writer() {
let str = r#"<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.github.sample" android:versionName="Lollipop" android:versionCode="5.1"><application android:label="SampleApplication"></application></manifest>"#;
let reader = XmlReader::from_str(&str).trim_text(true);
let mut writer = XmlWriter::new(Cursor::new(Vec::new()));
for event in reader {
assert!(writer.write(event.unwrap()).is_ok());
}

let result = writer.into_inner().into_inner();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alignment typo

assert_eq!(result, str.as_bytes());
}

#[test]
fn test_write_attrs() {
let str_from = r#"<elem attr="val"></elem>"#;
let expected = r#"<elem attr="val" another="one"></elem>"#;
let reader = XmlReader::from_str(&str_from).trim_text(true);
let mut writer = XmlWriter::new(Cursor::new(Vec::new()));
for event in reader {
let event = event.unwrap();
let event = match event {
Start(elem) => {
let mut attrs = elem.attributes().map(|e| {
let (k,v) = e.unwrap();
(from_utf8(k).unwrap(), v)
}).collect::<Vec<_>>();
attrs.push(("another", "one"));
let elem = Element::create("elem", attrs.into_iter());
Start(elem)
},
_ => event
};
assert!(writer.write(event).is_ok());
}

let result = writer.into_inner().into_inner();
assert_eq!(result, expected.as_bytes());
}