Skip to main content

rsvg/
parsers.rs

1//! The `Parse` trait for CSS properties, and utilities for parsers.
2
3use cssparser::{Parser, ParserInput, Token};
4use markup5ever::QualName;
5use std::str;
6
7use crate::error::*;
8
9/// Trait to parse values using `cssparser::Parser`.
10pub trait Parse: Sized {
11    /// Parses a value out of the `parser`.
12    ///
13    /// All value types should implement this for composability.
14    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>>;
15
16    /// Convenience function to parse a value out of a `&str`.
17    ///
18    /// This is useful mostly for tests which want to avoid creating a
19    /// `cssparser::Parser` by hand.  Property types do not need to reimplement this.
20    fn parse_str(s: &str) -> Result<Self, ParseError<'_>> {
21        let mut input = ParserInput::new(s);
22        let mut parser = Parser::new(&mut input);
23
24        let res = Self::parse(&mut parser)?;
25        parser.expect_exhausted()?;
26
27        Ok(res)
28    }
29}
30
31/// Consumes a comma if it exists, or does nothing.
32pub fn optional_comma(parser: &mut Parser<'_, '_>) {
33    let _ = parser.try_parse(|p| p.expect_comma());
34}
35
36/// Parses an `f32` and ensures that it is not an infinity or NaN.
37pub fn finite_f32(n: f32) -> Result<f32, ValueErrorKind> {
38    if n.is_finite() {
39        Ok(n)
40    } else {
41        Err(ValueErrorKind::Value("expected finite number".to_string()))
42    }
43}
44
45/// Trait used to parse attribute values concisely.
46///
47/// Most of the code to parse an SVG element's attributes looks like this:
48///
49/// ```text
50///         for (attr, value) in attrs.iter() {
51///            match attr.expanded() {
52///                expanded_name!("", "foo") => {
53///                    set_attribute(&mut self.foo, attr.parse(value), session);
54///                }
55///                // ...
56///            }
57///         }
58/// ```
59///
60/// This trait is so that `attr.parse(value)` and the surrounding call
61/// to [crate::element::set_attribute()] can be written in that way:  `attr.parse(value)`
62/// returns a `Result`, and then `set_attribute()` modifies `self.foo` based on whether
63/// the result is `Ok()` or not.
64///
65/// This trait is only implemented for [QualName], which is how individual attributes are
66/// represented.
67pub trait ParseValue<T: Parse> {
68    /// Parses a `value` string into a type `T`.
69    fn parse(&self, value: &str) -> Result<T, ElementError>;
70}
71
72impl<T: Parse> ParseValue<T> for QualName {
73    /// Parse a value from an XML attribute.
74    ///
75    /// Say we have an attribute `bar` like in `<foo bar="42"/>`.  If `attr` is a [`QualName`]
76    /// for the attribute `bar`, then we'll parse it like `attr.parse("42")`.
77    ///
78    /// The reason for doing things that way is so that, in case of a parse error, this
79    /// function can annotate the error result with the attribute's name.
80    ///
81    /// Note that attribute values are parsed entirely, thus the call to
82    /// `expect_exhausted()` below.  We don't want to allow garbage in the string after
83    /// the initial value has been parsed.
84    fn parse(&self, value: &str) -> Result<T, ElementError> {
85        let mut input = ParserInput::new(value);
86        let mut parser = Parser::new(&mut input);
87
88        T::parse(&mut parser)
89            .and_then(|v| {
90                parser.expect_exhausted()?;
91                Ok(v)
92            })
93            .attribute(self.clone())
94    }
95}
96
97impl<T: Parse> Parse for Option<T> {
98    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
99        T::parse(parser).map(Some)
100    }
101}
102
103impl Parse for f64 {
104    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
105        let loc = parser.current_source_location();
106        let n = parser.expect_number()?;
107        if n.is_finite() {
108            Ok(f64::from(n))
109        } else {
110            Err(loc.new_custom_error(ValueErrorKind::value_error("expected finite number")))
111        }
112    }
113}
114
115/// Non-Negative number
116#[derive(Debug, Copy, Clone, PartialEq)]
117pub struct NonNegative(pub f64);
118
119impl Parse for NonNegative {
120    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
121        let loc = parser.current_source_location();
122        let n = Parse::parse(parser)?;
123        if n >= 0.0 {
124            Ok(NonNegative(n))
125        } else {
126            Err(loc.new_custom_error(ValueErrorKind::value_error("expected non negative number")))
127        }
128    }
129}
130
131/// CSS number-optional-number
132///
133/// SVG1.1: <https://www.w3.org/TR/SVG11/types.html#DataTypeNumberOptionalNumber>
134#[derive(Debug, Copy, Clone, PartialEq)]
135pub struct NumberOptionalNumber<T: Parse>(pub T, pub T);
136
137impl<T: Parse + Copy> Parse for NumberOptionalNumber<T> {
138    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
139        let x = Parse::parse(parser)?;
140
141        if !parser.is_exhausted() {
142            optional_comma(parser);
143            let y = Parse::parse(parser)?;
144            Ok(NumberOptionalNumber(x, y))
145        } else {
146            Ok(NumberOptionalNumber(x, x))
147        }
148    }
149}
150
151/// CSS number-percentage
152///
153/// CSS Values and Units 3: <https://www.w3.org/TR/css3-values/#typedef-number-percentage>
154#[derive(Debug, Copy, Clone, PartialEq)]
155pub struct NumberOrPercentage {
156    pub value: f64,
157}
158
159impl Parse for NumberOrPercentage {
160    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
161        let loc = parser.current_source_location();
162
163        let value = match parser.next()? {
164            Token::Number { value, .. } => Ok(*value),
165            Token::Percentage { unit_value, .. } => Ok(*unit_value),
166            tok => Err(loc.new_unexpected_token_error(tok.clone())),
167        }?;
168
169        let v = finite_f32(value).map_err(|e| parser.new_custom_error(e))?;
170        Ok(NumberOrPercentage {
171            value: f64::from(v),
172        })
173    }
174}
175
176impl Parse for i32 {
177    /// CSS integer
178    ///
179    /// SVG1.1: <https://www.w3.org/TR/SVG11/types.html#DataTypeInteger>
180    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
181        Ok(parser.expect_integer()?)
182    }
183}
184
185impl Parse for u32 {
186    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
187        let loc = parser.current_source_location();
188        let n = parser.expect_integer()?;
189        if n >= 0 {
190            Ok(n as u32)
191        } else {
192            Err(loc.new_custom_error(ValueErrorKind::value_error("expected unsigned number")))
193        }
194    }
195}
196
197/// List separated by optional commas, with bounds for the required and maximum number of items.
198#[derive(Clone, Debug, PartialEq)]
199pub struct CommaSeparatedList<T: Parse, const REQUIRED: usize, const MAX: usize>(pub Vec<T>);
200
201impl<T: Parse, const REQUIRED: usize, const MAX: usize> Parse
202    for CommaSeparatedList<T, REQUIRED, MAX>
203{
204    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
205        let loc = parser.current_source_location();
206        let mut v = Vec::<T>::with_capacity(MAX);
207        for i in 0..MAX {
208            if i != 0 {
209                optional_comma(parser);
210            }
211
212            v.push(T::parse(parser)?);
213
214            if parser.is_exhausted() {
215                break;
216            }
217        }
218
219        if REQUIRED > 0 && v.len() < REQUIRED {
220            Err(loc.new_custom_error(ValueErrorKind::value_error("expected more values")))
221        } else {
222            v.shrink_to_fit();
223            Ok(CommaSeparatedList(v))
224        }
225    }
226}
227
228/// Parses a list of identifiers from a `cssparser::Parser`
229///
230/// # Example
231///
232/// ```
233/// # use cssparser::{ParserInput, Parser};
234/// # use rsvg::parse_identifiers;
235/// # fn main() -> Result<(), cssparser::BasicParseError<'static>> {
236/// # let mut input = ParserInput::new("true");
237/// # let mut parser = Parser::new(&mut input);
238/// let my_boolean = parse_identifiers!(
239///     parser,
240///     "true" => true,
241///     "false" => false,
242/// )?;
243/// # Ok(())
244/// # }
245/// ```
246#[doc(hidden)]
247#[macro_export]
248macro_rules! parse_identifiers {
249    ($parser:expr,
250     $($str:expr => $val:expr,)+) => {
251        {
252            let loc = $parser.current_source_location();
253            let token = $parser.next()?;
254
255            match token {
256                $(&cssparser::Token::Ident(ref cow) if cow.eq_ignore_ascii_case($str) => Ok($val),)+
257
258                _ => Err(loc.new_basic_unexpected_token_error(token.clone()))
259            }
260        }
261    };
262}
263
264/// CSS Custom identifier.
265///
266/// CSS Values and Units 4: <https://www.w3.org/TR/css-values-4/#custom-idents>
267#[derive(Debug, Clone, PartialEq, Eq, Hash)]
268pub struct CustomIdent(pub String);
269
270impl Parse for CustomIdent {
271    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
272        let loc = parser.current_source_location();
273        let token = parser.next()?;
274
275        match token {
276            // CSS-wide keywords and "default" are errors here
277            // https://www.w3.org/TR/css-values-4/#css-wide-keywords
278            Token::Ident(cow) => {
279                for s in &["initial", "inherit", "unset", "default"] {
280                    if cow.eq_ignore_ascii_case(s) {
281                        return Err(loc.new_basic_unexpected_token_error(token.clone()).into());
282                    }
283                }
284
285                Ok(CustomIdent(cow.as_ref().to_string()))
286            }
287
288            _ => Err(loc.new_basic_unexpected_token_error(token.clone()).into()),
289        }
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296
297    use markup5ever::{QualName, local_name, ns};
298
299    #[test]
300    fn parses_number_optional_number() {
301        assert_eq!(
302            NumberOptionalNumber::parse_str("1, 2").unwrap(),
303            NumberOptionalNumber(1.0, 2.0)
304        );
305        assert_eq!(
306            NumberOptionalNumber::parse_str("1 2").unwrap(),
307            NumberOptionalNumber(1.0, 2.0)
308        );
309        assert_eq!(
310            NumberOptionalNumber::parse_str("1").unwrap(),
311            NumberOptionalNumber(1.0, 1.0)
312        );
313
314        assert_eq!(
315            NumberOptionalNumber::parse_str("-1, -2").unwrap(),
316            NumberOptionalNumber(-1.0, -2.0)
317        );
318        assert_eq!(
319            NumberOptionalNumber::parse_str("-1 -2").unwrap(),
320            NumberOptionalNumber(-1.0, -2.0)
321        );
322        assert_eq!(
323            NumberOptionalNumber::parse_str("-1").unwrap(),
324            NumberOptionalNumber(-1.0, -1.0)
325        );
326    }
327
328    #[test]
329    fn invalid_number_optional_number() {
330        assert!(NumberOptionalNumber::<f64>::parse_str("").is_err());
331        assert!(NumberOptionalNumber::<f64>::parse_str("1x").is_err());
332        assert!(NumberOptionalNumber::<f64>::parse_str("x1").is_err());
333        assert!(NumberOptionalNumber::<f64>::parse_str("1 x").is_err());
334        assert!(NumberOptionalNumber::<f64>::parse_str("1 , x").is_err());
335        assert!(NumberOptionalNumber::<f64>::parse_str("1 , 2x").is_err());
336        assert!(NumberOptionalNumber::<f64>::parse_str("1 2 x").is_err());
337    }
338
339    #[test]
340    fn parses_integer() {
341        assert_eq!(i32::parse_str("0").unwrap(), 0);
342        assert_eq!(i32::parse_str("1").unwrap(), 1);
343        assert_eq!(i32::parse_str("-1").unwrap(), -1);
344
345        assert_eq!(u32::parse_str("0").unwrap(), 0);
346        assert_eq!(u32::parse_str("1").unwrap(), 1);
347    }
348
349    #[test]
350    fn invalid_integer() {
351        assert!(i32::parse_str("").is_err());
352        assert!(i32::parse_str("1x").is_err());
353        assert!(i32::parse_str("1.5").is_err());
354
355        assert!(u32::parse_str("").is_err());
356        assert!(u32::parse_str("1x").is_err());
357        assert!(u32::parse_str("1.5").is_err());
358        assert!(u32::parse_str("-1").is_err());
359    }
360
361    #[test]
362    fn parses_integer_optional_integer() {
363        assert_eq!(
364            NumberOptionalNumber::parse_str("1, 2").unwrap(),
365            NumberOptionalNumber(1, 2)
366        );
367        assert_eq!(
368            NumberOptionalNumber::parse_str("1 2").unwrap(),
369            NumberOptionalNumber(1, 2)
370        );
371        assert_eq!(
372            NumberOptionalNumber::parse_str("1").unwrap(),
373            NumberOptionalNumber(1, 1)
374        );
375
376        assert_eq!(
377            NumberOptionalNumber::parse_str("-1, -2").unwrap(),
378            NumberOptionalNumber(-1, -2)
379        );
380        assert_eq!(
381            NumberOptionalNumber::parse_str("-1 -2").unwrap(),
382            NumberOptionalNumber(-1, -2)
383        );
384        assert_eq!(
385            NumberOptionalNumber::parse_str("-1").unwrap(),
386            NumberOptionalNumber(-1, -1)
387        );
388    }
389
390    #[test]
391    fn invalid_integer_optional_integer() {
392        assert!(NumberOptionalNumber::<i32>::parse_str("").is_err());
393        assert!(NumberOptionalNumber::<i32>::parse_str("1x").is_err());
394        assert!(NumberOptionalNumber::<i32>::parse_str("x1").is_err());
395        assert!(NumberOptionalNumber::<i32>::parse_str("1 x").is_err());
396        assert!(NumberOptionalNumber::<i32>::parse_str("1 , x").is_err());
397        assert!(NumberOptionalNumber::<i32>::parse_str("1 , 2x").is_err());
398        assert!(NumberOptionalNumber::<i32>::parse_str("1 2 x").is_err());
399        assert!(NumberOptionalNumber::<i32>::parse_str("1.5").is_err());
400        assert!(NumberOptionalNumber::<i32>::parse_str("1 2.5").is_err());
401        assert!(NumberOptionalNumber::<i32>::parse_str("1, 2.5").is_err());
402    }
403
404    #[test]
405    fn parses_comma_separated_list() {
406        assert_eq!(
407            CommaSeparatedList::<f64, 1, 1>::parse_str("5").unwrap(),
408            CommaSeparatedList(vec![5.0])
409        );
410
411        assert_eq!(
412            CommaSeparatedList::<f64, 4, 4>::parse_str("1 2 3 4").unwrap(),
413            CommaSeparatedList(vec![1.0, 2.0, 3.0, 4.0])
414        );
415
416        assert_eq!(
417            CommaSeparatedList::<f64, 0, 5>::parse_str("1 2 3 4 5").unwrap(),
418            CommaSeparatedList(vec![1.0, 2.0, 3.0, 4.0, 5.0])
419        );
420
421        assert_eq!(
422            CommaSeparatedList::<f64, 0, 5>::parse_str("1 2 3").unwrap(),
423            CommaSeparatedList(vec![1.0, 2.0, 3.0])
424        );
425    }
426
427    #[test]
428    fn errors_on_invalid_comma_separated_list() {
429        // empty
430        assert!(CommaSeparatedList::<f64, 1, 1>::parse_str("").is_err());
431        assert!(CommaSeparatedList::<f64, 0, 1>::parse_str("").is_err());
432
433        // garbage
434        assert!(CommaSeparatedList::<f64, 1, 1>::parse_str("foo").is_err());
435        assert!(CommaSeparatedList::<f64, 2, 2>::parse_str("1foo").is_err());
436        assert!(CommaSeparatedList::<f64, 2, 2>::parse_str("1 foo").is_err());
437        assert!(CommaSeparatedList::<f64, 2, 2>::parse_str("1 foo 2").is_err());
438        assert!(CommaSeparatedList::<f64, 2, 2>::parse_str("1,foo").is_err());
439
440        // too many
441        assert!(CommaSeparatedList::<f64, 1, 1>::parse_str("1 2").is_err());
442
443        // extra token
444        assert!(CommaSeparatedList::<f64, 1, 1>::parse_str("1,").is_err());
445        assert!(CommaSeparatedList::<f64, 0, 1>::parse_str("1,").is_err());
446
447        // too few
448        assert!(CommaSeparatedList::<f64, 2, 2>::parse_str("1").is_err());
449        assert!(CommaSeparatedList::<f64, 3, 3>::parse_str("1 2").is_err());
450    }
451
452    #[test]
453    fn detects_too_many_numbers_bug_1138() {
454        // The root cause of this bug is that we didn't check for token exhaustion when
455        // parsing attribute values in `impl<T: Parse> ParseValue<T> for QualName`.  So,
456        // for this test, we actually invoke the parser for CommaSeparatedList via that impl.
457
458        let attribute = QualName::new(None, ns!(svg), local_name!("matrix"));
459
460        // should parse 20 numbers and error out on the 21st
461        let r: Result<CommaSeparatedList<f64, 20, 20>, _> =
462            attribute.parse("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0,500000 0 ");
463        assert!(r.is_err());
464    }
465
466    #[test]
467    fn parses_custom_ident() {
468        assert_eq!(
469            CustomIdent::parse_str("hello").unwrap(),
470            CustomIdent("hello".to_string())
471        );
472    }
473
474    #[test]
475    fn invalid_custom_ident_yields_error() {
476        assert!(CustomIdent::parse_str("initial").is_err());
477        assert!(CustomIdent::parse_str("inherit").is_err());
478        assert!(CustomIdent::parse_str("unset").is_err());
479        assert!(CustomIdent::parse_str("default").is_err());
480        assert!(CustomIdent::parse_str("").is_err());
481    }
482}