Skip to main content

tl/queryselector/
parser.rs

1#[cfg(not(feature = "std"))]
2use crate::ParseError;
3use crate::{stream::Stream, util};
4
5use super::Selector;
6
7/// A query selector parser
8pub struct Parser<'a> {
9    stream: Stream<'a, u8>,
10}
11
12impl<'a> Parser<'a> {
13    /// Creates a new query selector parser
14    pub fn new(input: &'a [u8]) -> Self {
15        Self {
16            stream: Stream::new(input),
17        }
18    }
19
20    fn skip_whitespaces(&mut self) -> bool {
21        let has_whitespace = self.stream.expect_and_skip_cond(b' ');
22        while !self.stream.is_eof() {
23            if self.stream.expect_and_skip(b' ').is_none() {
24                break;
25            }
26        }
27        has_whitespace
28    }
29
30    fn read_identifier(&mut self) -> &'a [u8] {
31        let start = self.stream.idx;
32
33        while !self.stream.is_eof() {
34            let is_ident = self.stream.current().copied().is_some_and(util::is_ident);
35            if !is_ident {
36                break;
37            } else {
38                self.stream.advance();
39            }
40        }
41
42        self.stream.slice(start, self.stream.idx)
43    }
44
45    #[cfg(feature = "std")]
46    fn parse_combinator(&mut self, left: Selector<'a>) -> Option<Selector<'a>> {
47        let has_whitespaces = self.skip_whitespaces();
48
49        let tok = if let Some(tok) = self.stream.current_cpy() {
50            tok
51        } else {
52            return Some(left);
53        };
54
55        let combinator = match tok {
56            b',' => {
57                self.stream.advance();
58                let right = self.selector()?;
59                Selector::Or(Box::new(left), Box::new(right))
60            }
61            b'>' => {
62                self.stream.advance();
63                let right = self.selector()?;
64                Selector::Parent(Box::new(left), Box::new(right))
65            }
66            _ if has_whitespaces => {
67                let right = self.selector()?;
68                Selector::Descendant(Box::new(left), Box::new(right))
69            }
70            _ if !has_whitespaces => {
71                let right = self.selector()?;
72                Selector::And(Box::new(left), Box::new(right))
73            }
74            _ => unreachable!(),
75        };
76
77        Some(combinator)
78    }
79
80    #[cfg(not(feature = "std"))]
81    fn parse_combinator<const MAX_SELECTOR_NODES: usize>(
82        &mut self,
83        left: Selector<'a, MAX_SELECTOR_NODES>,
84    ) -> Result<Selector<'a, MAX_SELECTOR_NODES>, ParseError> {
85        let has_whitespaces = self.skip_whitespaces();
86        if self.stream.current_cpy().is_none() {
87            return Ok(left);
88        }
89        if has_whitespaces || matches!(self.stream.current_cpy(), Some(b',' | b'>')) {
90            return Err(ParseError::SelectorCapacityExceeded);
91        }
92        Err(ParseError::SelectorCapacityExceeded)
93    }
94
95    fn parse_attribute<const MAX_SELECTOR_NODES: usize>(
96        &mut self,
97    ) -> Option<Selector<'a, MAX_SELECTOR_NODES>> {
98        let attribute = self.read_identifier();
99        let ty = match self.stream.current_cpy() {
100            Some(b']') => {
101                self.stream.advance();
102                Selector::Attribute(attribute)
103            }
104            Some(b'=') => {
105                self.stream.advance();
106                let quote = self.stream.expect_oneof_and_skip(b"\"'");
107                let value = self.read_identifier();
108                if let Some(quote) = quote {
109                    // Only require the given quote if the value starts with a quote
110                    self.stream.expect_and_skip(quote)?;
111                }
112                self.stream.expect_and_skip(b']')?;
113                Selector::AttributeValue(attribute, value)
114            }
115            Some(c @ b'~' | c @ b'^' | c @ b'$' | c @ b'*') => {
116                self.stream.advance();
117                self.stream.expect_and_skip(b'=')?;
118                let quote = self.stream.expect_oneof_and_skip(b"\"'");
119                let value = self.read_identifier();
120                if let Some(quote) = quote {
121                    // Only require the given quote if the value starts with a quote
122                    self.stream.expect_and_skip(quote)?;
123                }
124                self.stream.expect_and_skip(b']')?;
125                match c {
126                    b'~' => Selector::AttributeValueWhitespacedContains(attribute, value),
127                    b'^' => Selector::AttributeValueStartsWith(attribute, value),
128                    b'$' => Selector::AttributeValueEndsWith(attribute, value),
129                    b'*' => Selector::AttributeValueSubstring(attribute, value),
130                    _ => unreachable!(),
131                }
132            }
133            _ => return None,
134        };
135        Some(ty)
136    }
137
138    /// Parses a full selector
139    #[cfg(feature = "std")]
140    pub fn selector(&mut self) -> Option<Selector<'a>> {
141        self.skip_whitespaces();
142        let tok = self.stream.current_cpy()?;
143
144        let left = match tok {
145            b'#' => {
146                self.stream.advance();
147                let id = self.read_identifier();
148                Selector::Id(id)
149            }
150            b'.' => {
151                self.stream.advance();
152                let class = self.read_identifier();
153                Selector::Class(class)
154            }
155            b'*' => {
156                self.stream.advance();
157                Selector::All
158            }
159            b'[' => {
160                self.stream.advance();
161                self.parse_attribute::<0>()?
162            }
163            _ if util::is_ident(tok) => {
164                let tag = self.read_identifier();
165                Selector::Tag(tag)
166            }
167            _ => return None,
168        };
169
170        self.parse_combinator(left)
171    }
172
173    /// Parses a full selector without allocation.
174    #[cfg(not(feature = "std"))]
175    pub fn selector<const MAX_SELECTOR_NODES: usize>(
176        &mut self,
177    ) -> Result<Selector<'a, MAX_SELECTOR_NODES>, ParseError> {
178        self.skip_whitespaces();
179        let tok = self
180            .stream
181            .current_cpy()
182            .ok_or(ParseError::SelectorCapacityExceeded)?;
183
184        let left = match tok {
185            b'#' => {
186                self.stream.advance();
187                let id = self.read_identifier();
188                Selector::Id(id)
189            }
190            b'.' => {
191                self.stream.advance();
192                let class = self.read_identifier();
193                Selector::Class(class)
194            }
195            b'*' => {
196                self.stream.advance();
197                Selector::All
198            }
199            b'[' => {
200                self.stream.advance();
201                self.parse_attribute::<MAX_SELECTOR_NODES>()
202                    .ok_or(ParseError::SelectorCapacityExceeded)?
203            }
204            _ if util::is_ident(tok) => {
205                let tag = self.read_identifier();
206                Selector::Tag(tag)
207            }
208            _ => return Err(ParseError::SelectorCapacityExceeded),
209        };
210
211        self.parse_combinator(left)
212    }
213}