svg/node/element/
tag.rs

1//! The tags.
2
3#![allow(non_upper_case_globals)]
4
5use crate::node::Attributes;
6use crate::parser::{Error, Reader, Result};
7
8/// A tag.
9#[derive(Clone, Debug)]
10pub struct Tag<'l>(pub &'l str, pub Type, pub Attributes);
11
12/// A [type][1] of a tag.
13///
14/// [1]: http://www.w3.org/TR/REC-xml/#sec-starttags
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub enum Type {
17    /// A start tag.
18    Start,
19    /// An end tag.
20    End,
21    /// An empty tag.
22    Empty,
23}
24
25struct Parser<'l> {
26    reader: Reader<'l>,
27}
28
29impl<'l> Tag<'l> {
30    /// Parse a tag.
31    #[inline]
32    pub fn parse(content: &'l str) -> Result<Tag<'l>> {
33        Parser::new(content).process()
34    }
35}
36
37macro_rules! raise(
38    ($parser:expr, $($argument:tt)*) => (
39        return Err(Error::new($parser.reader.position(), format!($($argument)*)))
40    );
41);
42
43impl<'l> Parser<'l> {
44    #[inline]
45    fn new(content: &'l str) -> Self {
46        Parser {
47            reader: Reader::new(content),
48        }
49    }
50
51    fn process(&mut self) -> Result<Tag<'l>> {
52        if self.reader.consume_char('/') {
53            self.read_end_tag()
54        } else {
55            self.read_start_or_empty_tag()
56        }
57    }
58
59    fn read_attribute(&mut self) -> Result<Option<(String, String)>> {
60        let attribute = self
61            .reader
62            .capture(|reader| reader.consume_attribute())
63            .map(String::from);
64        match attribute {
65            Some(attribute) => {
66                let k = attribute.find('=').unwrap();
67                let name = attribute[0..k].trim_end();
68                let value = attribute[(k + 1)..].trim_start();
69                let value = &value[1..(value.len() - 1)];
70                Ok(Some((String::from(name), String::from(value))))
71            }
72            _ => Ok(None),
73        }
74    }
75
76    fn read_attributes(&mut self) -> Result<Attributes> {
77        let mut attributes = Attributes::new();
78        loop {
79            self.reader.consume_whitespace();
80            match self.read_attribute()? {
81                Some((name, value)) => {
82                    attributes.insert(name, value.into());
83                }
84                _ => break,
85            }
86        }
87        Ok(attributes)
88    }
89
90    fn read_end_tag(&mut self) -> Result<Tag<'l>> {
91        let name = self.read_name()?;
92        self.reader.consume_whitespace();
93        if !self.reader.is_done() {
94            raise!(self, "found an end tag with excessive data");
95        }
96        Ok(Tag(name, Type::End, Attributes::default()))
97    }
98
99    fn read_name(&mut self) -> Result<&'l str> {
100        let name = self.reader.capture(|reader| reader.consume_name());
101        match name {
102            Some(name) => Ok(name),
103            _ => raise!(self, "expected a name"),
104        }
105    }
106
107    fn read_start_or_empty_tag(&mut self) -> Result<Tag<'l>> {
108        let name = self.read_name()?;
109        let attributes = self.read_attributes()?;
110        self.reader.consume_whitespace();
111        let tail = self.reader.capture(|reader| reader.consume_all());
112        match tail {
113            Some("/") => Ok(Tag(name, Type::Empty, attributes)),
114            Some(_) => raise!(self, "found an unexpected ending of a tag"),
115            _ => Ok(Tag(name, Type::Start, attributes)),
116        }
117    }
118}
119
120macro_rules! implement {
121    ($($const_name:ident: $tag_name:expr,)*) => ($(
122        #[doc = $tag_name]
123        pub const $const_name: &'static str = $tag_name;
124    )*);
125}
126
127implement! {
128    Anchor: "a",
129    Animate: "animate",
130    AnimateColor: "animateColor",
131    AnimateMotion: "animateMotion",
132    AnimateTransform: "animateTransform",
133    Circle: "circle",
134    ClipPath: "clipPath",
135    Definitions: "defs",
136    Description: "desc",
137    Ellipse: "ellipse",
138    Filter: "filter",
139    FilterEffectBlend: "feBlend",
140    FilterEffectColorMatrix: "feColorMatrix",
141    FilterEffectComponentTransfer: "feComponentTransfer",
142    FilterEffectComposite: "feComposite",
143    FilterEffectConvolveMatrix: "feConvolveMatrix",
144    FilterEffectDiffuseLighting: "feDiffuseLighting",
145    FilterEffectDisplacementMap: "feDisplacementMap",
146    FilterEffectDistantLight: "feDistantLight",
147    FilterEffectDropShadow: "feDropShadow",
148    FilterEffectFlood: "feFlood",
149    FilterEffectFunctionA: "feFuncA",
150    FilterEffectFunctionB: "feFuncB",
151    FilterEffectFunctionG: "feFuncG",
152    FilterEffectFunctionR: "feFuncR",
153    FilterEffectGaussianBlur: "feGaussianBlur",
154    FilterEffectImage: "feImage",
155    FilterEffectMerge: "feMerge",
156    FilterEffectMergeNode: "feMergeNode",
157    FilterEffectMorphology: "feMorphology",
158    FilterEffectOffset: "feOffset",
159    FilterEffectPointLight: "fePointLight",
160    FilterEffectSpecularLighting: "feSpecularLighting",
161    FilterEffectSpotLight: "feSpotLight",
162    FilterEffectTile: "feTile",
163    FilterEffectTurbulence: "feTurbulence",
164    ForeignObject: "foreignObject",
165    Group: "g",
166    Image: "image",
167    Line: "line",
168    LinearGradient: "linearGradient",
169    Link: "link",
170    Marker: "marker",
171    Mask: "mask",
172    MotionPath: "mpath",
173    Path: "path",
174    Pattern: "pattern",
175    Polygon: "polygon",
176    Polyline: "polyline",
177    RadialGradient: "radialGradient",
178    Rectangle: "rect",
179    Script: "script",
180    Stop: "stop",
181    Style: "style",
182    SVG: "svg",
183    Symbol: "symbol",
184    Text: "text",
185    TextPath: "textPath",
186    Title: "title",
187    TSpan: "tspan",
188    Use: "use",
189}
190
191#[cfg(test)]
192mod tests {
193    use super::{Parser, Tag, Type};
194
195    #[test]
196    fn parser_process() {
197        macro_rules! test(
198            ($content:expr, $kind:ident) => ({
199                let mut parser = Parser::new($content);
200                match parser.process().unwrap() {
201                    Tag("foo", Type::$kind, _) => {}
202                    _ => unreachable!(),
203                }
204            });
205        );
206
207        test!("foo", Start);
208        test!("foo ", Start);
209        test!("/foo", End);
210        test!("/foo ", End);
211        test!("foo/", Empty);
212        test!("foo /", Empty);
213    }
214
215    #[test]
216    fn parser_read_attribute() {
217        macro_rules! test(
218            ($content:expr, $name:expr, $value:expr) => ({
219                let mut parser = Parser::new($content);
220                let (name, value) = parser.read_attribute().unwrap().unwrap();
221                assert_eq!(&*name, $name);
222                assert_eq!(&*value, $value);
223            });
224        );
225
226        test!("foo=''", "foo", "");
227        test!("foo='bar'", "foo", "bar");
228        test!("foo =\"bar\"", "foo", "bar");
229        test!("foo= \"bar\"", "foo", "bar");
230        test!("foo\t=\n'bar'  ", "foo", "bar");
231        test!("標籤='數值'", "標籤", "數值");
232    }
233}