1use std::{borrow::Cow, fmt, io, iter, marker::PhantomData, str::FromStr};
4
5pub(crate) use roxmltree::*;
6
7use super::{bytes::memchr_naive, float, int};
8
9#[inline]
10#[must_use]
11pub(crate) const fn is_whitespace(c: char) -> bool {
12 matches!(c, '\t' | '\n' | '\r' | ' ')
15}
16
17#[inline]
18pub(crate) fn trim(s: &str) -> &str {
19 s.trim_matches(is_whitespace)
20}
21#[inline]
22pub(crate) fn trim_start(s: &str) -> &str {
23 s.trim_start_matches(is_whitespace)
24}
25
26#[inline]
28pub(crate) fn comma_to_period(s: &str) -> Cow<'_, str> {
29 if s.as_bytes().contains(&b',') {
30 s.replace(',', ".").into()
31 } else {
32 s.into()
33 }
34}
35
36pub(crate) fn parse_int_array<T>(text: &str) -> ParseIntArray<'_, T>
41where
42 T: int::Integer,
43{
44 ParseIntArray {
45 text,
46 _marker: PhantomData,
47 }
48}
49
50pub(crate) struct ParseIntArray<'a, T> {
51 text: &'a str,
52 _marker: PhantomData<fn() -> T>,
53}
54
55impl<T> Iterator for ParseIntArray<'_, T>
56where
57 T: int::Integer,
58{
59 type Item = io::Result<T>;
60
61 fn next(&mut self) -> Option<Self::Item> {
62 if self.text.is_empty() {
63 return None;
64 }
65 match int::parse_partial(self.text.as_bytes()) {
66 Some((value, n)) => {
67 self.text = trim_start(self.text.get(n..).unwrap_or_default());
68 Some(Ok(value))
69 }
70 None => Some(Err(format_err!("error while parsing an integer"))),
71 }
72 }
73}
74
75pub(crate) fn parse_float_array_exact<T>(text: &str, num: usize) -> ParseFloatArrayExact<'_, T>
115where
116 T: float::Float,
117{
118 ParseFloatArrayExact {
119 text,
120 num,
121 count: 0,
122 _marker: PhantomData,
123 }
124}
125
126pub(crate) struct ParseFloatArrayExact<'a, T> {
127 text: &'a str,
128 num: usize,
129 count: usize,
130 _marker: PhantomData<fn() -> T>,
131}
132
133impl<T> Iterator for ParseFloatArrayExact<'_, T>
134where
135 T: float::Float,
136{
137 type Item = io::Result<T>;
138
139 fn next(&mut self) -> Option<Self::Item> {
140 if self.count >= self.num {
141 if self.text.is_empty() {
142 return None;
143 }
144 return Some(Err(format_err!(
145 "unexpected text {:?} after {} floats",
146 self.text,
147 self.num
148 )));
149 }
150 match float::parse_partial::<T>(self.text.as_bytes()) {
151 Some((value, n)) => {
152 self.text = trim_start(self.text.get(n..).unwrap_or_default());
153 self.count += 1;
154 Some(Ok(value))
155 }
156 None => Some(Err(format_err!("error while parsing a float"))),
157 }
158 }
159}
160
161pub(crate) trait XmlNodeExt<'a, 'input> {
165 fn element_children(&self) -> ElementChildren<'a, 'input>;
166 fn child(&self, name: &str) -> Option<Node<'a, 'input>>;
167 fn required_attribute(&self, name: &str) -> io::Result<&'a str>;
168 fn parse_attribute<T>(&self, name: &str) -> io::Result<Option<T>>
169 where
170 T: FromStr,
171 T::Err: fmt::Display;
172 fn parse_required_attribute<T>(&self, name: &str) -> io::Result<T>
173 where
174 T: FromStr,
175 T::Err: fmt::Display;
176 fn trimmed_text(&self) -> &'a str;
177 fn node_location(&self) -> TextPos;
178 fn text_location(&self) -> TextPos;
179 fn attr_value_location(&self, name: &str) -> TextPos;
180}
181
182impl<'a, 'input> XmlNodeExt<'a, 'input> for Node<'a, 'input> {
183 fn element_children(&self) -> ElementChildren<'a, 'input> {
184 self.children()
185 .filter(|n| n.node_type() == NodeType::Element)
186 }
187
188 fn child(&self, name: &str) -> Option<Node<'a, 'input>> {
189 self.element_children()
190 .find(|n| n.tag_name().name() == name)
191 }
192
193 fn required_attribute(&self, name: &str) -> io::Result<&'a str> {
194 match self.attribute(name) {
195 Some(v) => Ok(v),
196 None => {
197 bail!(
198 "expected {} attribute in <{}> element at {}",
199 name,
200 if self.is_element() {
201 self.tag_name().name()
202 } else {
203 self.parent_element().unwrap().tag_name().name()
204 },
205 self.node_location(),
206 )
207 }
208 }
209 }
210
211 fn parse_attribute<T>(&self, name: &str) -> io::Result<Option<T>>
212 where
213 T: FromStr,
214 T::Err: fmt::Display,
215 {
216 match self.attribute(name) {
217 Some(v) => Ok(Some(v.parse::<T>().map_err(|e| {
218 format_err!(
219 "{} in <{}> element at {}: {:?}",
220 e,
221 self.tag_name().name(),
222 self.attr_value_location(name),
223 v
224 )
225 })?)),
226 None => Ok(None),
227 }
228 }
229
230 fn parse_required_attribute<T>(&self, name: &str) -> io::Result<T>
231 where
232 T: FromStr,
233 T::Err: fmt::Display,
234 {
235 let v = self.required_attribute(name)?;
236 v.parse::<T>().map_err(|e| {
237 format_err!(
238 "{} in <{}> element at {}: {:?}",
239 e,
240 self.tag_name().name(),
241 self.attr_value_location(name),
242 v
243 )
244 })
245 }
246
247 fn trimmed_text(&self) -> &'a str {
248 trim(self.text().unwrap_or_default())
249 }
250
251 #[cold]
252 fn node_location(&self) -> TextPos {
253 let start = self.range().start;
254 self.document().text_pos_at(start)
255 }
256 #[cold]
257 fn text_location(&self) -> TextPos {
258 let mut start = self.range().start;
259 start += memchr_naive(b'>', self.document().input_text()[start..].as_bytes())
260 .map_or(0, |p| p + 1);
261 self.document().text_pos_at(start)
262 }
263 #[cold]
264 fn attr_value_location(&self, name: &str) -> TextPos {
265 let start = self.attribute_node(name).unwrap().range_value().start;
266 self.document().text_pos_at(start)
267 }
268}
269
270pub(crate) type ElementChildren<'a, 'input> =
271 iter::Filter<Children<'a, 'input>, fn(&Node<'a, 'input>) -> bool>;