svgparser/
style.rs

1// Copyright 2018 Evgeniy Reizner
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! Module for parsing [`<style>`] data.
10//!
11//! [`<style>`]: https://www.w3.org/TR/SVG/styling.html#StyleAttribute
12
13use std::fmt;
14use std::str;
15
16use xmlparser::{
17    self,
18    FromSpan,
19    Reference,
20    Stream,
21    StrSpan,
22};
23
24use error::{
25    StreamError,
26    StreamResult,
27};
28use {
29    AttributeId,
30};
31
32/// Style token.
33#[derive(PartialEq)]
34pub enum Token<'a> {
35    /// Tuple contains attribute's name and value of an XML element.
36    XmlAttribute(&'a str, &'a str),
37    /// Tuple contains attribute's ID and value of an SVG element.
38    SvgAttribute(AttributeId, StrSpan<'a>),
39    /// Tuple contains ENTITY reference. Just a name without `&` and `;`.
40    EntityRef(&'a str),
41}
42
43impl<'a> fmt::Debug for Token<'a> {
44    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45        match *self {
46            Token::XmlAttribute(name, ref value) =>
47                write!(f, "XmlAttribute({}, {})", name, value),
48            Token::SvgAttribute(id, ref value) =>
49                write!(f, "SvgAttribute({:?}, {:?})", id, value),
50            Token::EntityRef(name) =>
51                write!(f, "EntityRef({})", name),
52        }
53    }
54}
55
56/// Tokenizer for the \<style\> data.
57#[derive(Clone, Copy, PartialEq)]
58pub struct Tokenizer<'a> {
59    stream: Stream<'a>,
60}
61
62impl<'a> FromSpan<'a> for Tokenizer<'a> {
63    fn from_span(span: StrSpan<'a>) -> Self {
64        Tokenizer {
65            stream: Stream::from_span(span)
66        }
67    }
68}
69
70impl<'a> fmt::Debug for Tokenizer<'a> {
71    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72        write!(f, "StyleTokenizer({:?})", self.stream.span())
73    }
74}
75
76impl<'a> Iterator for Tokenizer<'a> {
77    type Item = StreamResult<Token<'a>>;
78
79    /// Extracts a next style object from the stream.
80    ///
81    /// # Errors
82    ///
83    /// - Most of the `Error` types can occur.
84    ///
85    /// # Notes
86    ///
87    /// - By SVG spec `style` attribute can contain any style sheet language,
88    ///   but we only support CSS2, which is default.
89    /// - Objects with `-` prefix will be ignored since we can't write them as XML attributes.
90    ///   Library will print a warning to stderr.
91    /// - All comments are automatically skipped.
92    fn next(&mut self) -> Option<Self::Item> {
93        self.stream.skip_spaces();
94
95        if self.stream.at_end() {
96            return None;
97        }
98
99        macro_rules! try2 {
100            ($expr:expr) => {
101                match $expr {
102                    Ok(value) => value,
103                    Err(e) => {
104                        return Some(Err(e.into()));
105                    }
106                }
107            }
108        }
109
110        let c = try2!(self.stream.curr_byte());
111        if c == b'/' {
112            try2!(skip_comment(&mut self.stream));
113            self.next()
114        } else if c == b'-' {
115            try2!(parse_prefix(&mut self.stream));
116            self.next()
117        } else if c == b'&' {
118            Some(parse_entity_ref(&mut self.stream))
119        } else if is_ident_char(c) {
120            Some(parse_attribute(&mut self.stream))
121        } else {
122            let pos = self.stream.gen_error_pos();
123            self.stream.jump_to_end();
124            Some(Err(xmlparser::StreamError::InvalidChar(c as char, "/-&".into(), pos).into()))
125        }
126    }
127}
128
129fn skip_comment(stream: &mut Stream) -> StreamResult<()> {
130    stream.skip_string(b"/*")?;
131    stream.skip_bytes(|_, c| c != b'*');
132    stream.skip_string(b"*/")?;
133    stream.skip_spaces();
134
135    Ok(())
136}
137
138fn parse_attribute<'a>(stream: &mut Stream<'a>) -> StreamResult<Token<'a>> {
139    let name = stream.consume_bytes(|_, c| is_ident_char(c));
140
141    if name.is_empty() {
142        // TODO: this
143        // The error type is irrelevant because we will ignore it anyway.
144        return Err(xmlparser::StreamError::UnexpectedEndOfStream.into());
145    }
146
147    stream.skip_spaces();
148    stream.consume_byte(b':')?;
149    stream.skip_spaces();
150
151    let value = if stream.curr_byte()? == b'\'' {
152        stream.advance(1);
153        let v = stream.consume_bytes(|_, c| c != b'\'');
154        stream.consume_byte(b'\'')?;
155        v
156    } else if stream.starts_with(b"&apos;") {
157        stream.advance(6);
158        let v = stream.consume_bytes(|_, c| c != b'&');
159        stream.skip_string(b"&apos;")?;
160        v
161    } else {
162        stream.consume_bytes(|_, c| c != b';' && c != b'/')
163    }.trim();
164
165    if value.len() == 0 {
166        return Err(xmlparser::StreamError::UnexpectedEndOfStream.into());
167    }
168
169    stream.skip_spaces();
170
171    // ';;;' is valid style data, we need to skip it
172    while stream.is_curr_byte_eq(b';') {
173        stream.advance(1);
174        stream.skip_spaces();
175    }
176
177    if let Some(aid) = AttributeId::from_name(name.to_str()) {
178        Ok(Token::SvgAttribute(aid, value))
179    } else {
180        Ok(Token::XmlAttribute(name.to_str(), value.to_str()))
181    }
182}
183
184fn parse_entity_ref<'a>(stream: &mut Stream<'a>) -> StreamResult<Token<'a>> {
185    match stream.consume_reference()? {
186        Reference::EntityRef(name) => {
187            Ok(Token::EntityRef(name.to_str()))
188        }
189        Reference::CharRef(_) => {
190            Err(StreamError::InvalidEntityRef(stream.gen_error_pos()))
191        }
192    }
193}
194
195fn parse_prefix<'a>(stream: &mut Stream<'a>) -> StreamResult<()> {
196    // prefixed attributes are not supported, aka '-webkit-*'
197
198    stream.advance(1); // -
199    let t = parse_attribute(stream)?;
200
201    if let Token::XmlAttribute(name, _) = t {
202        warn!("Style attribute '-{}' is skipped.", name);
203    }
204
205    Ok(())
206}
207
208// TODO: to xmlparser traits
209fn is_ident_char(c: u8) -> bool {
210    match c {
211          b'0'...b'9'
212        | b'A'...b'Z'
213        | b'a'...b'z'
214        | b'-'
215        | b'_' => true,
216        _ => false,
217    }
218}