svg 0.4.0

The package provides an SVG composer and parser.
Documentation
//! The tags.

use node::Attributes;
use parser::{Error, Reader, Result};

/// A tag.
pub struct Tag<'l>(pub &'l str, pub Type, pub Attributes);

/// A [type][1] of a tag.
///
/// [1]: http://www.w3.org/TR/REC-xml/#sec-starttags
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Type {
    /// A start tag.
    Start,
    /// An end tag.
    End,
    /// An empty tag.
    Empty,
}

struct Parser<'l> {
    reader: Reader<'l>,
}

impl<'l> Tag<'l> {
    /// Parse a tag.
    #[inline]
    pub fn parse(content: &'l str) -> Result<Tag<'l>> {
        Parser::new(content).process()
    }
}

macro_rules! raise(
    ($parser:expr, $($argument:tt)*) => (
        return Err(Error::new($parser.reader.position(), format!($($argument)*)));
    );
);

impl<'l> Parser<'l> {
    #[inline]
    fn new(content: &'l str) -> Self {
        Parser { reader: Reader::new(content) }
    }

    fn process(&mut self) -> Result<Tag<'l>> {
        if self.reader.consume_char('/') {
            self.read_end_tag()
        } else {
            self.read_start_or_empty_tag()
        }
    }

    fn read_attribute(&mut self) -> Result<Option<(String, String)>> {
        let attribute = self.reader.capture(|reader| {
            reader.consume_attribute();
        }).and_then(|attribute| Some(String::from(attribute)));
        match attribute {
            Some(attribute) => {
                let k = (&attribute).find('=').unwrap();
                let name = (&attribute[0..k]).trim_right();
                let value = (&attribute[(k+1)..]).trim_left();
                let value = &value[1..(value.len()-1)];
                Ok(Some((String::from(name), String::from(value))))
            },
            _ => Ok(None),
        }
    }

    fn read_attributes(&mut self) -> Result<Attributes> {
        let mut attributes = Attributes::new();
        loop {
            self.reader.consume_whitespace();
            match try!(self.read_attribute()) {
                Some((name, value)) => {
                    attributes.insert(name, value.into());
                },
                _ => break,
            }
        }
        Ok(attributes)
    }

    fn read_end_tag(&mut self) -> Result<Tag<'l>> {
        let name = try!(self.read_name());
        self.reader.consume_whitespace();
        if !self.reader.is_done() {
            raise!(self, "found an end tag with excessive data");
        }
        Ok(Tag(name, Type::End, Attributes::default()))
    }

    fn read_name(&mut self) -> Result<&'l str> {
        let name = self.reader.capture(|reader| {
            reader.consume_name();
        });
        match name {
            Some(name) => Ok(name),
            _ => raise!(self, "expected a name"),
        }
    }

    fn read_start_or_empty_tag(&mut self) -> Result<Tag<'l>> {
        let name = try!(self.read_name());
        let attributes = try!(self.read_attributes());
        self.reader.consume_whitespace();
        let tail = self.reader.capture(|reader| {
            reader.consume_all();
        });
        match tail {
            Some("/") => Ok(Tag(name, Type::Empty, attributes)),
            Some(_) => raise!(self, "found an unexpected ending of a tag"),
            _ => Ok(Tag(name, Type::Start, attributes)),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{Parser, Tag, Type};

    #[test]
    fn parser_process() {
        macro_rules! test(
            ($content:expr, $kind:ident) => ({
                let mut parser = Parser::new($content);
                match parser.process().unwrap() {
                    Tag("foo", Type::$kind, _) => {},
                    _ => unreachable!(),
                }
            });
        );

        test!("foo", Start);
        test!("foo ", Start);
        test!("/foo", End);
        test!("/foo ", End);
        test!("foo/", Empty);
        test!("foo /", Empty);
    }

    #[test]
    fn parser_read_attribute() {
        macro_rules! test(
            ($content:expr, $name:expr, $value:expr) => ({
                let mut parser = Parser::new($content);
                let (name, value) = parser.read_attribute().unwrap().unwrap();
                assert_eq!(&*name, $name);
                assert_eq!(&*value, $value);
            });
        );

        test!("foo='bar'", "foo", "bar");
        test!("foo =\"bar\"", "foo", "bar");
        test!("foo= \"bar\"", "foo", "bar");
        test!("foo\t=\n'bar'  ", "foo", "bar");
    }
}