svgtypes/
number.rs

1// Copyright 2018 the SVG Types Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use std::str::FromStr;
5
6use crate::{ByteExt, Error, Stream};
7
8/// An [SVG number](https://www.w3.org/TR/SVG2/types.html#InterfaceSVGNumber).
9#[derive(Clone, Copy, PartialEq, Debug)]
10pub struct Number(pub f64);
11
12impl std::str::FromStr for Number {
13    type Err = Error;
14
15    fn from_str(text: &str) -> Result<Self, Self::Err> {
16        let mut s = Stream::from(text);
17        let n = s.parse_number()?;
18        s.skip_spaces();
19        if !s.at_end() {
20            return Err(Error::UnexpectedData(s.calc_char_pos()));
21        }
22
23        Ok(Self(n))
24    }
25}
26
27impl Stream<'_> {
28    /// Parses number from the stream.
29    ///
30    /// This method will detect a number length and then
31    /// will pass a substring to the `f64::from_str` method.
32    ///
33    /// <https://www.w3.org/TR/SVG2/types.html#InterfaceSVGNumber>
34    ///
35    /// # Errors
36    ///
37    /// Returns only `InvalidNumber`.
38    pub fn parse_number(&mut self) -> Result<f64, Error> {
39        // Strip off leading whitespaces.
40        self.skip_spaces();
41
42        let start = self.pos();
43
44        if self.at_end() {
45            return Err(Error::InvalidNumber(self.calc_char_pos_at(start)));
46        }
47
48        self.parse_number_impl()
49            .map_err(|_| Error::InvalidNumber(self.calc_char_pos_at(start)))
50    }
51
52    fn parse_number_impl(&mut self) -> Result<f64, Error> {
53        let start = self.pos();
54
55        let mut c = self.curr_byte()?;
56
57        // Consume sign.
58        if c.is_sign() {
59            self.advance(1);
60            c = self.curr_byte()?;
61        }
62
63        // Consume integer.
64        match c {
65            b'0'..=b'9' => self.skip_digits(),
66            b'.' => {}
67            _ => return Err(Error::InvalidNumber(0)),
68        }
69
70        // Consume fraction.
71        if let Ok(b'.') = self.curr_byte() {
72            self.advance(1);
73            self.skip_digits();
74        }
75
76        if let Ok(c) = self.curr_byte() {
77            if matches!(c, b'e' | b'E') {
78                let c2 = self.next_byte()?;
79                // Check for `em`/`ex`.
80                if c2 != b'm' && c2 != b'x' {
81                    self.advance(1);
82
83                    match self.curr_byte()? {
84                        b'+' | b'-' => {
85                            self.advance(1);
86                            self.skip_digits();
87                        }
88                        b'0'..=b'9' => self.skip_digits(),
89                        _ => {
90                            return Err(Error::InvalidNumber(0));
91                        }
92                    }
93                }
94            }
95        }
96
97        let s = self.slice_back(start);
98
99        // Use the default f64 parser now.
100        if let Ok(n) = f64::from_str(s) {
101            // inf, nan, etc. are an error.
102            if n.is_finite() {
103                return Ok(n);
104            }
105        }
106
107        Err(Error::InvalidNumber(0))
108    }
109
110    /// Parses number from a list of numbers.
111    pub fn parse_list_number(&mut self) -> Result<f64, Error> {
112        if self.at_end() {
113            return Err(Error::UnexpectedEndOfStream);
114        }
115
116        let n = self.parse_number()?;
117        self.skip_spaces();
118        self.parse_list_separator();
119        Ok(n)
120    }
121}
122
123/// A pull-based [`<list-of-numbers>`] parser.
124///
125/// # Examples
126///
127/// ```
128/// use svgtypes::NumberListParser;
129///
130/// let mut p = NumberListParser::from("10, 20 -50");
131/// assert_eq!(p.next().unwrap().unwrap(), 10.0);
132/// assert_eq!(p.next().unwrap().unwrap(), 20.0);
133/// assert_eq!(p.next().unwrap().unwrap(), -50.0);
134/// assert_eq!(p.next().is_none(), true);
135/// ```
136///
137/// [`<list-of-numbers>`]: https://www.w3.org/TR/SVG2/types.html#InterfaceSVGNumberList
138#[derive(Clone, Copy, PartialEq, Eq, Debug)]
139pub struct NumberListParser<'a>(Stream<'a>);
140
141impl<'a> From<&'a str> for NumberListParser<'a> {
142    #[inline]
143    fn from(v: &'a str) -> Self {
144        NumberListParser(Stream::from(v))
145    }
146}
147
148impl Iterator for NumberListParser<'_> {
149    type Item = Result<f64, Error>;
150
151    fn next(&mut self) -> Option<Self::Item> {
152        if self.0.at_end() {
153            None
154        } else {
155            let v = self.0.parse_list_number();
156            if v.is_err() {
157                self.0.jump_to_end();
158            }
159
160            Some(v)
161        }
162    }
163}
164
165#[rustfmt::skip]
166#[cfg(test)]
167mod tests {
168    use crate::Stream;
169
170    macro_rules! test_p {
171        ($name:ident, $text:expr, $result:expr) => (
172            #[test]
173            fn $name() {
174                let mut s = Stream::from($text);
175                assert_eq!(s.parse_number().unwrap(), $result);
176            }
177        )
178    }
179
180    test_p!(parse_1,  "0", 0.0);
181    test_p!(parse_2,  "1", 1.0);
182    test_p!(parse_3,  "-1", -1.0);
183    test_p!(parse_4,  " -1 ", -1.0);
184    test_p!(parse_5,  "  1  ", 1.0);
185    test_p!(parse_6,  ".4", 0.4);
186    test_p!(parse_7,  "-.4", -0.4);
187    test_p!(parse_8,  "-.4text", -0.4);
188    test_p!(parse_9,  "-.01 text", -0.01);
189    test_p!(parse_10, "-.01 4", -0.01);
190    test_p!(parse_11, ".0000000000008", 0.0000000000008);
191    test_p!(parse_12, "1000000000000", 1000000000000.0);
192    test_p!(parse_13, "123456.123456", 123456.123456);
193    test_p!(parse_14, "+10", 10.0);
194    test_p!(parse_15, "1e2", 100.0);
195    test_p!(parse_16, "1e+2", 100.0);
196    test_p!(parse_17, "1E2", 100.0);
197    test_p!(parse_18, "1e-2", 0.01);
198    test_p!(parse_19, "1ex", 1.0);
199    test_p!(parse_20, "1em", 1.0);
200    test_p!(parse_21, "12345678901234567890", 12345678901234567000.0);
201    test_p!(parse_22, "0.", 0.0);
202    test_p!(parse_23, "1.3e-2", 0.013);
203    // test_number!(parse_24, "1e", 1.0); // TODO: this
204
205    macro_rules! test_p_err {
206        ($name:ident, $text:expr) => (
207            #[test]
208            fn $name() {
209                let mut s = Stream::from($text);
210                assert_eq!(s.parse_number().unwrap_err().to_string(),
211                           "invalid number at position 1");
212            }
213        )
214    }
215
216    test_p_err!(parse_err_1, "q");
217    test_p_err!(parse_err_2, "");
218    test_p_err!(parse_err_3, "-");
219    test_p_err!(parse_err_4, "+");
220    test_p_err!(parse_err_5, "-q");
221    test_p_err!(parse_err_6, ".");
222    test_p_err!(parse_err_7, "99999999e99999999");
223    test_p_err!(parse_err_8, "-99999999e99999999");
224}