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<'_> = Stream::from(text);
17 let n: f64 = 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: Result = 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}
225