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