Skip to main content

arquery/
selector.rs

1use std::iter::Peekable;
2use std::str::Chars;
3
4/// An error which is returned when parsing a selector encounters an unexpected
5/// token
6#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct UnexpectedTokenError(pub char);
8
9/// Represents a component of a parsed CSS selector is used to match a single
10/// element.
11#[derive(Clone, Debug)]
12pub struct CompoundSelector {
13    /// The scope of the selector.
14    pub scope: Scope,
15    /// The individual parts that make up the compound selector.
16    pub parts: Vec<Selector>,
17}
18
19/// The scope of the `CompoundSelector`.
20#[derive(Clone, Copy, PartialEq, Debug)]
21pub enum Scope {
22    /// Implies that the selector must be a direct descendent of the previous
23    /// match (e.g. `body > header`).
24    DirectChild,
25    /// Implies that the selector is a descendent of the previous match (e.g.,
26    /// `body header`).
27    IndirectChild,
28}
29
30/// The individual parts of the `CompoundSelector`. For example, the selector
31/// `input[type="radio"]` has two parts, the `TagName` and `Attribute`
32/// selectors.
33#[derive(Clone, Debug)]
34pub enum Selector {
35    /// Represents an id selector (e.g. `#the-id`)
36    Id(String),
37    /// Represents a tag name selector (e.g. `input`)
38    TagName(String),
39    /// Represents an attribute selector (e.g. `[type="radio"]`)
40    Attribute(String, MatchType, String),
41}
42
43/// The match type for an attribute selector.
44#[derive(Clone, Copy, Debug, PartialEq)]
45pub enum MatchType {
46    /// Indicates that the match must be identical
47    Equals,
48}
49
50macro_rules! expect_token {
51    ($token_option: expr, $token: expr) => {
52        match $token_option {
53            Some($token) => {}
54            Some(token) => return Err(UnexpectedTokenError(token)),
55            None => return Err(UnexpectedTokenError(' ')),
56        }
57    };
58}
59
60#[inline]
61fn non_digit(c: char) -> bool {
62    ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
63}
64
65#[inline]
66fn allowed_character(c: char) -> bool {
67    non_digit(c) || ('0' <= c && c <= '9') || c == '-' || c == '_'
68}
69
70#[inline]
71fn valid_start_token(c: char) -> bool {
72    c == '#' || c == '['
73}
74
75fn extract_valid_string(chars: &mut Peekable<Chars>) -> Result<String, UnexpectedTokenError> {
76    extract_valid_string_until_token(chars, ' ')
77}
78
79fn extract_valid_string_until_token(
80    chars: &mut Peekable<Chars>,
81    stop_token: char,
82) -> Result<String, UnexpectedTokenError> {
83    let mut string = String::new();
84
85    while let Some(&c) = chars.peek() {
86        if c == stop_token {
87            chars.next().unwrap();
88            break;
89        } else if allowed_character(c) {
90            string.push(chars.next().unwrap());
91        } else if valid_start_token(c) {
92            break;
93        } else {
94            return Err(UnexpectedTokenError(c));
95        }
96    }
97
98    return Ok(string);
99}
100
101impl Selector {
102    fn create_list(string: &str) -> Result<Vec<Selector>, UnexpectedTokenError> {
103        let mut selectors = Vec::new();
104
105        let mut chars = string.chars().peekable();
106        while let Some(&c) = chars.peek() {
107            match Selector::next_selector(c, &mut chars) {
108                Ok(selector) => selectors.push(selector),
109
110                Err(err) => return Err(err),
111            }
112        }
113
114        return Ok(selectors);
115    }
116
117    fn next_selector(
118        c: char,
119        chars: &mut Peekable<Chars>,
120    ) -> Result<Selector, UnexpectedTokenError> {
121        if non_digit(c) {
122            Selector::create_tag_name(chars)
123        } else if c == '#' {
124            Selector::create_id(chars)
125        } else if c == '[' {
126            Selector::create_attribute(chars)
127        } else {
128            Err(UnexpectedTokenError(c))
129        }
130    }
131
132    fn create_tag_name(chars: &mut Peekable<Chars>) -> Result<Selector, UnexpectedTokenError> {
133        extract_valid_string(chars).map(|s| Selector::TagName(s))
134    }
135
136    fn create_id(chars: &mut Peekable<Chars>) -> Result<Selector, UnexpectedTokenError> {
137        match chars.next() {
138            Some('#') => return extract_valid_string(chars).map(|s| Selector::Id(s)),
139
140            Some(token) => return Err(UnexpectedTokenError(token)),
141
142            None => return Err(UnexpectedTokenError(' ')),
143        }
144    }
145
146    fn create_attribute(chars: &mut Peekable<Chars>) -> Result<Selector, UnexpectedTokenError> {
147        expect_token!(chars.next(), '[');
148
149        extract_valid_string_until_token(chars, '=')
150            .and_then(|attribute| Ok((attribute, MatchType::Equals)))
151            .and_then(|(attribute, match_type)| {
152                let result = if Some(&'"') == chars.peek() {
153                    chars.next().unwrap();
154                    let result = extract_valid_string_until_token(chars, '"');
155                    expect_token!(chars.next(), ']');
156
157                    result
158                } else {
159                    extract_valid_string_until_token(chars, ']')
160                };
161
162                result.map(|value| Selector::Attribute(attribute, match_type, value))
163            })
164    }
165}
166
167struct SelectorParts<I: Iterator<Item = String>> {
168    inner_iter: I,
169}
170
171impl<I: Iterator<Item = String>> Iterator for SelectorParts<I> {
172    type Item = (Scope, String);
173
174    fn next(&mut self) -> Option<Self::Item> {
175        self.inner_iter.next().and_then(|next_part| {
176            if &next_part == ">" {
177                Some((Scope::DirectChild, self.inner_iter.next().unwrap()))
178            } else {
179                Some((Scope::IndirectChild, next_part))
180            }
181        })
182    }
183}
184
185impl CompoundSelector {
186    /// Parses the string and converts it to a list of `CompoundSelector`s.
187    pub fn parse(selector: &str) -> Result<Vec<CompoundSelector>, UnexpectedTokenError> {
188        let normalized_selector = selector.split(">").collect::<Vec<&str>>().join(" > ");
189
190        let selector_parts = SelectorParts {
191            inner_iter: normalized_selector
192                .split_whitespace()
193                .into_iter()
194                .map(|s| s.to_string()),
195        };
196
197        selector_parts.fold(Ok(Vec::new()), |result_so_far, (scope, part)| {
198            if let Ok(mut compound_selectors) = result_so_far {
199                Selector::create_list(&part).map(|parts| {
200                    compound_selectors.push(CompoundSelector {
201                        scope: scope,
202                        parts: parts,
203                    });
204
205                    compound_selectors
206                })
207            } else {
208                result_so_far
209            }
210        })
211    }
212}