rama_http/protocols/html/selector/
parser.rs1use std::str::FromStr;
10
11use super::SelectorError;
12use super::ast::{
13 AttributeOperator, AttributeSelector, CaseSensitivity, Combinator, ComplexSelector, Compound,
14 LocalName, Nth, NthType, Selector, SelectorPart,
15};
16
17impl FromStr for Selector {
18 type Err = SelectorError;
19
20 fn from_str(s: &str) -> Result<Self, Self::Err> {
21 Parser::new(s).parse_selector()
22 }
23}
24
25struct Parser<'i> {
26 input: &'i str,
27 pos: usize,
28}
29
30impl<'i> Parser<'i> {
31 fn new(input: &'i str) -> Self {
32 Self { input, pos: 0 }
33 }
34
35 fn rest(&self) -> &'i str {
36 self.input.get(self.pos..).unwrap_or("")
38 }
39
40 fn peek(&self) -> Option<char> {
41 self.rest().chars().next().map(preprocess)
42 }
43
44 fn peek_nth(&self, n: usize) -> Option<char> {
45 self.rest().chars().nth(n).map(preprocess)
46 }
47
48 fn bump(&mut self) -> Option<char> {
49 let raw = self.rest().chars().next()?;
50 self.pos += raw.len_utf8();
51 Some(preprocess(raw))
52 }
53
54 fn eat(&mut self, expected: char) -> bool {
55 if self.peek() == Some(expected) {
56 self.pos += expected.len_utf8();
57 true
58 } else {
59 false
60 }
61 }
62
63 fn eof(&self) -> bool {
64 self.pos >= self.input.len()
65 }
66
67 fn skip_ws(&mut self) -> bool {
69 let mut any = false;
70 while self.peek().is_some_and(is_css_whitespace) {
71 self.pos += 1;
72 any = true;
73 }
74 any
75 }
76
77 fn parse_selector(&mut self) -> Result<Selector, SelectorError> {
78 self.skip_ws();
79 if self.eof() {
80 return Err(SelectorError::EmptySelector);
81 }
82
83 let mut selectors = Vec::new();
84 loop {
85 selectors.push(self.parse_complex()?);
86 self.skip_ws();
87 match self.peek() {
88 None => break,
89 Some(',') => {
90 self.bump();
91 self.skip_ws();
92 if self.peek().is_none() {
93 return Err(SelectorError::UnexpectedEnd);
94 }
95 }
96 Some('+') => return Err(SelectorError::UnsupportedCombinator('+')),
97 Some('~') => return Err(SelectorError::UnsupportedCombinator('~')),
98 Some(_) => return Err(SelectorError::UnexpectedToken),
99 }
100 }
101 Ok(Selector { selectors })
102 }
103
104 fn parse_complex(&mut self) -> Result<ComplexSelector, SelectorError> {
105 let mut parts = vec![SelectorPart {
106 combinator: None,
107 compound: self.parse_compound()?,
108 }];
109
110 loop {
111 let saw_ws = self.skip_ws();
112 match self.peek() {
113 None | Some(',') => break,
114 Some('>') => {
115 self.bump();
116 self.skip_ws();
117 if matches!(self.peek(), None | Some(',')) {
118 return Err(SelectorError::DanglingCombinator);
119 }
120 parts.push(SelectorPart {
121 combinator: Some(Combinator::Child),
122 compound: self.parse_compound()?,
123 });
124 }
125 Some('+') => return Err(SelectorError::UnsupportedCombinator('+')),
126 Some('~') => return Err(SelectorError::UnsupportedCombinator('~')),
127 Some(_) => {
128 if !saw_ws {
129 return Err(SelectorError::UnexpectedToken);
130 }
131 parts.push(SelectorPart {
132 combinator: Some(Combinator::Descendant),
133 compound: self.parse_compound()?,
134 });
135 }
136 }
137 }
138
139 Ok(ComplexSelector { parts })
140 }
141
142 fn parse_compound(&mut self) -> Result<Compound, SelectorError> {
143 let mut compound = Compound::default();
144 let mut has_any = false;
145
146 match self.peek() {
148 Some('*') => {
149 self.bump();
150 compound.explicit_universal = true;
151 has_any = true;
152 if self.peek() == Some('|') {
153 return Err(SelectorError::NamespacedSelector);
154 }
155 }
156 Some('|') => return Err(SelectorError::NamespacedSelector),
157 Some(c) if is_ident_start(c) || c == '\\' || c == '-' => {
158 let name = self.parse_ident()?;
159 if self.peek() == Some('|') {
160 return Err(SelectorError::NamespacedSelector);
161 }
162 compound.name = Some(LocalName::new(&name));
163 has_any = true;
164 }
165 _ => {}
166 }
167
168 loop {
170 match self.peek() {
171 Some('#') => {
172 self.bump();
173 compound.id = Some(self.parse_ident()?.into());
174 }
175 Some('.') => {
176 self.bump();
177 compound.classes.push(self.parse_ident()?.into());
178 }
179 Some('[') => compound.attributes.push(self.parse_attribute()?),
180 Some(':') => self.parse_pseudo(&mut compound)?,
181 _ => break,
182 }
183 has_any = true;
184 }
185
186 if !has_any {
187 return Err(if self.eof() {
188 SelectorError::UnexpectedEnd
189 } else {
190 SelectorError::UnexpectedToken
191 });
192 }
193 Ok(compound)
194 }
195
196 fn parse_attribute(&mut self) -> Result<AttributeSelector, SelectorError> {
197 self.bump(); self.skip_ws();
199
200 let name = match self.peek() {
201 Some('=' | ']') => return Err(SelectorError::MissingAttributeName),
202 Some('*' | '|') => return Err(SelectorError::NamespacedSelector),
203 None => return Err(SelectorError::UnexpectedEnd),
204 _ => self
205 .parse_ident()
206 .map_err(|_err| SelectorError::UnexpectedTokenInAttribute)?,
207 };
208 if self.peek() == Some('|') && self.peek_nth(1) != Some('=') {
211 return Err(SelectorError::NamespacedSelector);
212 }
213 self.skip_ws();
214
215 let operator = match self.peek() {
216 Some(']') => None,
217 Some('=') => {
218 self.bump();
219 Some(AttributeOperator::Equals)
220 }
221 Some('~') => Some(self.parse_attr_operator(AttributeOperator::Includes)?),
222 Some('|') => Some(self.parse_attr_operator(AttributeOperator::DashMatch)?),
223 Some('^') => Some(self.parse_attr_operator(AttributeOperator::Prefix)?),
224 Some('$') => Some(self.parse_attr_operator(AttributeOperator::Suffix)?),
225 Some('*') => Some(self.parse_attr_operator(AttributeOperator::Substring)?),
226 None => return Err(SelectorError::UnexpectedEnd),
227 Some(_) => return Err(SelectorError::UnexpectedTokenInAttribute),
228 };
229
230 let (value, case) = if operator.is_some() {
231 self.skip_ws();
232 let value = match self.peek() {
233 Some('"' | '\'') => self.parse_string()?,
234 Some(']') | None => return Err(SelectorError::UnexpectedTokenInAttribute),
235 _ => self
236 .parse_ident()
237 .map_err(|_err| SelectorError::UnexpectedTokenInAttribute)?,
238 };
239 self.skip_ws();
240 let case = match self.peek() {
241 Some('i' | 'I') => {
242 self.bump();
243 self.skip_ws();
244 CaseSensitivity::AsciiCaseInsensitive
245 }
246 Some('s' | 'S') => {
247 self.bump();
248 self.skip_ws();
249 CaseSensitivity::CaseSensitive
250 }
251 _ => CaseSensitivity::CaseSensitive,
252 };
253 (value, case)
254 } else {
255 (String::new(), CaseSensitivity::CaseSensitive)
256 };
257
258 if !self.eat(']') {
259 return Err(if self.eof() {
260 SelectorError::UnexpectedEnd
261 } else {
262 SelectorError::UnexpectedTokenInAttribute
263 });
264 }
265
266 Ok(AttributeSelector {
267 name: name.to_ascii_lowercase().into(),
268 operator,
269 value: value.into(),
270 case,
271 })
272 }
273
274 fn parse_attr_operator(
277 &mut self,
278 operator: AttributeOperator,
279 ) -> Result<AttributeOperator, SelectorError> {
280 self.bump(); if self.eat('=') {
282 Ok(operator)
283 } else {
284 Err(SelectorError::UnexpectedTokenInAttribute)
285 }
286 }
287
288 fn parse_pseudo(&mut self, compound: &mut Compound) -> Result<(), SelectorError> {
289 self.bump(); if self.eat(':') {
291 return Err(SelectorError::UnsupportedPseudoClass);
294 }
295
296 let name = self.parse_ident()?.to_ascii_lowercase();
297
298 if self.eat('(') {
299 match name.as_str() {
300 "nth-child" => {
301 let (a, b) = self.parse_nth_args()?;
302 compound.nth.push(Nth {
303 ty: NthType::Child,
304 a,
305 b,
306 });
307 }
308 "nth-of-type" => {
309 let (a, b) = self.parse_nth_args()?;
310 compound.nth.push(Nth {
311 ty: NthType::OfType,
312 a,
313 b,
314 });
315 }
316 "not" => self.parse_negation(compound)?,
317 _ => {
318 self.skip_to_close_paren();
319 return Err(SelectorError::UnsupportedPseudoClass);
320 }
321 }
322 Ok(())
323 } else {
324 match name.as_str() {
325 "first-child" => {
326 compound.nth.push(Nth {
327 ty: NthType::Child,
328 a: 0,
329 b: 1,
330 });
331 Ok(())
332 }
333 "first-of-type" => {
334 compound.nth.push(Nth {
335 ty: NthType::OfType,
336 a: 0,
337 b: 1,
338 });
339 Ok(())
340 }
341 _ => Err(SelectorError::UnsupportedPseudoClass),
342 }
343 }
344 }
345
346 fn parse_negation(&mut self, compound: &mut Compound) -> Result<(), SelectorError> {
347 self.skip_ws();
349 if self.peek() == Some(')') {
350 self.bump();
351 return Err(SelectorError::EmptyNegation);
352 }
353 loop {
354 compound.negations.push(self.parse_compound()?);
355 self.skip_ws();
356 match self.peek() {
357 Some(')') => {
358 self.bump();
359 return Ok(());
360 }
361 Some(',') => {
362 self.bump();
363 self.skip_ws();
364 if self.peek() == Some(')') {
365 return Err(SelectorError::EmptyNegation);
366 }
367 }
368 None => return Err(SelectorError::UnexpectedEnd),
369 Some(_) => return Err(SelectorError::UnsupportedPseudoClass),
372 }
373 }
374 }
375
376 fn parse_nth_args(&mut self) -> Result<(i32, i32), SelectorError> {
378 self.skip_ws();
379 let anb = self.parse_anb()?;
380 self.skip_ws();
381 if !self.eat(')') {
382 return Err(SelectorError::UnexpectedToken);
383 }
384 Ok(anb)
385 }
386
387 fn parse_anb(&mut self) -> Result<(i32, i32), SelectorError> {
389 if self.match_keyword_ci("even") {
390 return Ok((2, 0));
391 }
392 if self.match_keyword_ci("odd") {
393 return Ok((2, 1));
394 }
395
396 let mut sign = 1i64;
398 let has_sign = match self.peek() {
399 Some('+') => {
400 self.bump();
401 true
402 }
403 Some('-') => {
404 self.bump();
405 sign = -1;
406 true
407 }
408 _ => false,
409 };
410 if has_sign && self.peek().is_some_and(is_css_whitespace) {
411 return Err(SelectorError::InvalidNth);
412 }
413
414 let digits = self.read_digits();
415
416 if matches!(self.peek(), Some('n' | 'N')) {
417 self.bump();
418 let a = sign * digits.unwrap_or(1);
419 let b = self.parse_anb_b()?;
420 Ok((clamp_i32(a)?, clamp_i32(b)?))
421 } else {
422 let value = digits.ok_or(SelectorError::InvalidNth)?;
423 Ok((0, clamp_i32(sign * value)?))
424 }
425 }
426
427 fn parse_anb_b(&mut self) -> Result<i64, SelectorError> {
429 let save = self.pos;
430 self.skip_ws();
431 let sign = match self.peek() {
432 Some('+') => {
433 self.bump();
434 1
435 }
436 Some('-') => {
437 self.bump();
438 -1
439 }
440 _ => {
441 self.pos = save;
442 return Ok(0);
443 }
444 };
445 self.skip_ws();
446 if matches!(self.peek(), Some('+' | '-')) {
447 return Err(SelectorError::InvalidNth);
448 }
449 let digits = self.read_digits().ok_or(SelectorError::InvalidNth)?;
450 Ok(sign * digits)
451 }
452
453 fn read_digits(&mut self) -> Option<i64> {
454 let mut value: i64 = 0;
455 let mut any = false;
456 while let Some(c) = self.peek() {
457 let Some(d) = c.to_digit(10) else { break };
458 value = value.saturating_mul(10).saturating_add(i64::from(d));
459 any = true;
460 self.bump();
461 }
462 any.then_some(value)
463 }
464
465 fn match_keyword_ci(&mut self, keyword: &str) -> bool {
468 let rest = self.rest();
469 if rest.len() < keyword.len() {
470 return false;
471 }
472 let Some(head) = rest.get(..keyword.len()) else {
473 return false;
474 };
475 if !head.eq_ignore_ascii_case(keyword) {
476 return false;
477 }
478 if let Some(next) = rest[keyword.len()..].chars().next()
479 && is_ident_char(next)
480 {
481 return false;
482 }
483 self.pos += keyword.len();
484 true
485 }
486
487 fn parse_ident(&mut self) -> Result<String, SelectorError> {
488 let mut out = String::new();
489 match self.peek() {
490 Some('\\') => out.push(self.parse_escape()?),
491 Some('-') => {
492 self.bump();
493 out.push('-');
494 }
495 Some(c) if is_ident_start(c) => {
496 self.bump();
497 out.push(c);
498 }
499 _ => {
500 return Err(if self.eof() {
501 SelectorError::UnexpectedEnd
502 } else {
503 SelectorError::UnexpectedToken
504 });
505 }
506 }
507
508 loop {
509 match self.peek() {
510 Some('\\') => out.push(self.parse_escape()?),
511 Some(c) if is_ident_char(c) => {
512 self.bump();
513 out.push(c);
514 }
515 _ => break,
516 }
517 }
518
519 if out == "-" {
520 return Err(SelectorError::UnexpectedToken);
521 }
522 Ok(out)
523 }
524
525 fn parse_string(&mut self) -> Result<String, SelectorError> {
526 let quote = self.bump().unwrap_or('"');
527 let mut out = String::new();
528 loop {
529 match self.peek() {
530 None => return Err(SelectorError::UnexpectedEnd),
531 Some(c) if c == quote => {
532 self.bump();
533 break;
534 }
535 Some('\n') => return Err(SelectorError::UnexpectedToken),
536 Some('\\') => {
537 self.bump();
538 match self.peek() {
539 None => return Err(SelectorError::UnexpectedEnd),
540 Some('\n') => {
542 self.bump();
543 }
544 Some(c) if c.is_ascii_hexdigit() => out.push(self.parse_hex_escape()),
545 Some(c) => {
546 self.bump();
547 out.push(c);
548 }
549 }
550 }
551 Some(c) => {
552 self.bump();
553 out.push(c);
554 }
555 }
556 }
557 Ok(out)
558 }
559
560 fn parse_escape(&mut self) -> Result<char, SelectorError> {
562 self.bump(); match self.peek() {
564 None | Some('\n') => Err(SelectorError::UnexpectedToken),
565 Some(c) if c.is_ascii_hexdigit() => Ok(self.parse_hex_escape()),
566 Some(c) => {
567 self.bump();
568 Ok(c)
569 }
570 }
571 }
572
573 fn parse_hex_escape(&mut self) -> char {
576 let mut value: u32 = 0;
577 let mut count = 0;
578 while count < 6 {
579 match self.peek().and_then(|c| c.to_digit(16)) {
580 Some(d) => {
581 value = value * 16 + d;
582 self.bump();
583 count += 1;
584 }
585 None => break,
586 }
587 }
588 if self.peek().is_some_and(is_css_whitespace) {
589 self.bump();
590 }
591 if value == 0 || (0xD800..=0xDFFF).contains(&value) || value > 0x0010_FFFF {
592 '\u{FFFD}'
593 } else {
594 char::from_u32(value).unwrap_or('\u{FFFD}')
595 }
596 }
597
598 fn skip_to_close_paren(&mut self) {
602 let mut depth = 1usize;
603 while let Some(c) = self.bump() {
604 match c {
605 '(' => depth += 1,
606 ')' => {
607 depth -= 1;
608 if depth == 0 {
609 break;
610 }
611 }
612 _ => {}
613 }
614 }
615 }
616}
617
618fn clamp_i32(value: i64) -> Result<i32, SelectorError> {
619 i32::try_from(value).map_err(|_err| SelectorError::InvalidNth)
620}
621
622fn preprocess(c: char) -> char {
627 if c == '\0' { '\u{FFFD}' } else { c }
628}
629
630fn is_css_whitespace(c: char) -> bool {
632 matches!(c, ' ' | '\t' | '\n' | '\r' | '\u{0c}')
633}
634
635fn is_ident_start(c: char) -> bool {
636 c.is_ascii_alphabetic() || c == '_' || !c.is_ascii()
637}
638
639fn is_ident_char(c: char) -> bool {
640 is_ident_start(c) || c.is_ascii_digit() || c == '-'
641}