1use std::iter::Peekable;
2use std::str::Chars;
3
4#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct UnexpectedTokenError(pub char);
8
9#[derive(Clone, Debug)]
12pub struct CompoundSelector {
13 pub scope: Scope,
15 pub parts: Vec<Selector>,
17}
18
19#[derive(Clone, Copy, PartialEq, Debug)]
21pub enum Scope {
22 DirectChild,
25 IndirectChild,
28}
29
30#[derive(Clone, Debug)]
34pub enum Selector {
35 Id(String),
37 TagName(String),
39 Attribute(String, MatchType, String),
41}
42
43#[derive(Clone, Copy, Debug, PartialEq)]
45pub enum MatchType {
46 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 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}