1#[cfg(not(feature = "std"))]
2use crate::ParseError;
3use crate::{stream::Stream, util};
4
5use super::Selector;
6
7pub struct Parser<'a> {
9 stream: Stream<'a, u8>,
10}
11
12impl<'a> Parser<'a> {
13 pub fn new(input: &'a [u8]) -> Self {
15 Self {
16 stream: Stream::new(input),
17 }
18 }
19
20 #[cfg(not(feature = "std"))]
21 fn skip_whitespaces(&mut self) -> bool {
22 let has_whitespace = self.stream.expect_and_skip_cond(b' ');
23 while !self.stream.is_eof() {
24 if self.stream.expect_and_skip(b' ').is_none() {
25 break;
26 }
27 }
28 has_whitespace
29 }
30
31 fn read_identifier(&mut self) -> &'a [u8] {
32 let start = self.stream.idx;
33
34 while !self.stream.is_eof() {
35 let is_ident = self.stream.current().copied().is_some_and(util::is_ident);
36 if !is_ident {
37 break;
38 } else {
39 self.stream.advance();
40 }
41 }
42
43 self.stream.slice(start, self.stream.idx)
44 }
45
46 #[cfg(feature = "std")]
47 fn read_css_identifier(&mut self) -> &'a [u8] {
48 let start = self.stream.idx;
49 while self
50 .stream
51 .current_cpy()
52 .is_some_and(|c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_' || c >= 128)
53 {
54 self.stream.advance();
55 }
56 self.stream.slice(start, self.stream.idx)
57 }
58
59 #[cfg(not(feature = "std"))]
60 fn parse_combinator<const MAX_SELECTOR_NODES: usize>(
61 &mut self,
62 left: Selector<'a, MAX_SELECTOR_NODES>,
63 ) -> Result<Selector<'a, MAX_SELECTOR_NODES>, ParseError> {
64 let has_whitespaces = self.skip_whitespaces();
65 if self.stream.current_cpy().is_none() {
66 return Ok(left);
67 }
68 if has_whitespaces || matches!(self.stream.current_cpy(), Some(b',' | b'>')) {
69 return Err(ParseError::SelectorCapacityExceeded);
70 }
71 Err(ParseError::SelectorCapacityExceeded)
72 }
73
74 fn parse_attribute<const MAX_SELECTOR_NODES: usize>(
75 &mut self,
76 ) -> Option<Selector<'a, MAX_SELECTOR_NODES>> {
77 let attribute = self.read_identifier();
78 if attribute.is_empty() {
79 return None;
80 }
81 let ty = match self.stream.current_cpy() {
82 Some(b']') => {
83 self.stream.advance();
84 Selector::Attribute(attribute)
85 }
86 Some(b'=') => {
87 self.stream.advance();
88 let quote = self.stream.expect_oneof_and_skip(b"\"'");
89 let value = if let Some(q) = quote {
90 let start = self.stream.idx;
91 while self
92 .stream
93 .current_cpy()
94 .is_some_and(|c| c != q && c != b'\\')
95 {
96 self.stream.advance();
97 }
98 self.stream.slice(start, self.stream.idx)
99 } else {
100 self.read_identifier()
101 };
102 if let Some(quote) = quote {
103 self.stream.expect_and_skip(quote)?;
105 }
106 self.stream.expect_and_skip(b']')?;
107 Selector::AttributeValue(attribute, value)
108 }
109 Some(c @ b'~' | c @ b'^' | c @ b'$' | c @ b'*') => {
110 self.stream.advance();
111 self.stream.expect_and_skip(b'=')?;
112 let quote = self.stream.expect_oneof_and_skip(b"\"'");
113 let value = if let Some(q) = quote {
114 let start = self.stream.idx;
115 while self
116 .stream
117 .current_cpy()
118 .is_some_and(|c| c != q && c != b'\\')
119 {
120 self.stream.advance();
121 }
122 self.stream.slice(start, self.stream.idx)
123 } else {
124 self.read_identifier()
125 };
126 if let Some(quote) = quote {
127 self.stream.expect_and_skip(quote)?;
129 }
130 self.stream.expect_and_skip(b']')?;
131 match c {
132 b'~' => Selector::AttributeValueWhitespacedContains(attribute, value),
133 b'^' => Selector::AttributeValueStartsWith(attribute, value),
134 b'$' => Selector::AttributeValueEndsWith(attribute, value),
135 b'*' => Selector::AttributeValueSubstring(attribute, value),
136 _ => unreachable!(),
137 }
138 }
139 _ => return None,
140 };
141 Some(ty)
142 }
143
144 #[cfg(feature = "std")]
146 pub fn selector(&mut self) -> Option<Selector<'a>> {
147 css(self.stream.data())
148 }
149
150 #[cfg(not(feature = "std"))]
152 pub fn selector<const MAX_SELECTOR_NODES: usize>(
153 &mut self,
154 ) -> Result<Selector<'a, MAX_SELECTOR_NODES>, ParseError> {
155 self.skip_whitespaces();
156 let tok = self
157 .stream
158 .current_cpy()
159 .ok_or(ParseError::SelectorCapacityExceeded)?;
160
161 let left = match tok {
162 b'#' => {
163 self.stream.advance();
164 let id = self.read_identifier();
165 Selector::Id(id)
166 }
167 b'.' => {
168 self.stream.advance();
169 let class = self.read_identifier();
170 Selector::Class(class)
171 }
172 b'*' => {
173 self.stream.advance();
174 Selector::All
175 }
176 b'[' => {
177 self.stream.advance();
178 self.parse_attribute::<MAX_SELECTOR_NODES>()
179 .ok_or(ParseError::SelectorCapacityExceeded)?
180 }
181 _ if util::is_ident(tok) => {
182 let tag = self.read_identifier();
183 Selector::Tag(tag)
184 }
185 _ => return Err(ParseError::SelectorCapacityExceeded),
186 };
187
188 self.parse_combinator(left)
189 }
190}
191
192#[cfg(feature = "std")]
193fn css(input: &[u8]) -> Option<Selector<'_>> {
194 css_at(input, 0)
195}
196
197#[cfg(feature = "std")]
198fn css_at(input: &[u8], depth_limit: usize) -> Option<Selector<'_>> {
199 if depth_limit >= 64 {
200 return None;
201 }
202 let input = trim(input);
203 if input.is_empty() {
204 return None;
205 }
206 let mut depth = 0usize;
208 let mut quote = None;
209 let mut relations = Vec::new();
210 let mut commas = Vec::new();
211 for (i, &c) in input.iter().enumerate() {
212 if let Some(q) = quote {
213 if c == q {
214 quote = None;
215 }
216 continue;
217 }
218 match c {
219 b'\'' | b'"' => quote = Some(c),
220 b'[' | b'(' => depth += 1,
221 b']' | b')' => depth = depth.checked_sub(1)?,
222 b',' if depth == 0 => commas.push(i),
223 b'>' | b'+' | b'~' if depth == 0 => relations.push((i, c)),
224 c if c.is_ascii_whitespace() && depth == 0 => relations.push((i, b' ')),
225 _ => {}
226 }
227 }
228 if depth != 0 || quote.is_some() {
229 return None;
230 }
231 if let Some(&i) = commas.first() {
232 return Some(Selector::Or(
233 Box::new(css_at(&input[..i], depth_limit + 1)?),
234 Box::new(css_at(&input[i + 1..], depth_limit + 1)?),
235 ));
236 }
237 for &(i, c) in relations.iter().rev() {
240 let left = trim(&input[..i]);
241 let right = trim(&input[i + 1..]);
242 if c == b' '
243 && (left.is_empty()
244 || right.is_empty()
245 || left.last().is_some_and(|c| b">+~".contains(c))
246 || right.first().is_some_and(|c| b">+~".contains(c)))
247 {
248 continue;
249 }
250 let left = Box::new(css_at(left, depth_limit + 1)?);
251 let right = Box::new(css_at(right, depth_limit + 1)?);
252 return Some(match c {
253 b'>' => Selector::Parent(left, right),
254 b'+' => Selector::Adjacent(left, right),
255 b'~' => Selector::Sibling(left, right),
256 _ => Selector::Descendant(left, right),
257 });
258 }
259 let mut parser = Parser::new(input);
260 let mut result = None;
261 let mut compounds = 0;
262 while let Some(tok) = parser.stream.current_cpy() {
263 compounds += 1;
264 if compounds >= 64 {
265 return None;
266 }
267 let simple = match tok {
268 b'#' | b'.' => {
269 parser.stream.advance();
270 let ident = parser.read_css_identifier();
271 if ident.is_empty() {
272 return None;
273 }
274 if tok == b'#' {
275 Selector::Id(ident)
276 } else {
277 Selector::Class(ident)
278 }
279 }
280 b'*' => {
281 parser.stream.advance();
282 Selector::All
283 }
284 b'[' => {
285 parser.stream.advance();
286 parser.parse_attribute::<0>()?
287 }
288 b':' => {
289 parser.stream.advance();
290 let name = parser.read_css_identifier();
291 match name {
292 b"first-child" => Selector::NthChild(0, 1),
293 b"nth-child" | b"not" | b"has" => {
294 parser.stream.expect_and_skip(b'(')?;
295 let start = parser.stream.idx;
296 let mut depth = 1;
297 let mut quote = None;
298 while let Some(c) = parser.stream.current_cpy() {
299 if let Some(q) = quote {
300 if c == q {
301 quote = None;
302 }
303 } else {
304 match c {
305 b'\'' | b'"' => quote = Some(c),
306 b'(' => depth += 1,
307 b')' => {
308 depth -= 1;
309 if depth == 0 {
310 break;
311 }
312 }
313 _ => {}
314 }
315 }
316 parser.stream.advance();
317 }
318 let arg = trim(parser.stream.slice(start, parser.stream.idx));
319 parser.stream.expect_and_skip(b')')?;
320 match name {
321 b"not" => Selector::Not(Box::new(css_at(arg, depth_limit + 1)?)),
322 b"has" => Selector::Has(Box::new(css_at(arg, depth_limit + 1)?)),
323 _ => {
324 let text = core::str::from_utf8(arg).ok()?;
325 let (a, b) = match text {
326 "odd" => (2, 1),
327 "even" => (2, 0),
328 _ if text.contains('n') => {
329 let (a, b) = text.split_once('n')?;
330 let a = match a.trim() {
331 "" | "+" => 1,
332 "-" => -1,
333 a => a.parse::<i32>().ok()?,
334 };
335 let b = b.trim();
336 let b = if b.is_empty() {
337 0
338 } else {
339 if !b.starts_with('+') && !b.starts_with('-') {
340 return None;
341 }
342 let compact: String = b
343 .chars()
344 .filter(|c| !c.is_ascii_whitespace())
345 .collect();
346 compact.parse::<i32>().ok()?
347 };
348 (a, b)
349 }
350 _ => (0, text.parse::<i32>().ok()?),
351 };
352 Selector::NthChild(a, b)
353 }
354 }
355 }
356 _ => return None,
357 }
358 }
359 _ if tok.is_ascii_alphanumeric() || tok == b'_' || tok == b'-' || tok >= 128 => {
360 if result.is_some() {
362 return None;
363 }
364 Selector::Tag(parser.read_css_identifier())
365 }
366 _ => return None,
367 };
368 result = Some(match result {
369 None => simple,
370 Some(left) => Selector::And(Box::new(left), Box::new(simple)),
371 });
372 }
373 result
374}
375
376#[cfg(feature = "std")]
377fn trim(mut input: &[u8]) -> &[u8] {
378 while input.first().is_some_and(u8::is_ascii_whitespace) {
379 input = &input[1..];
380 }
381 while input.last().is_some_and(u8::is_ascii_whitespace) {
382 input = &input[..input.len() - 1];
383 }
384 input
385}