accessibility_scraper/
selector.rs1use std::convert::TryFrom;
4use std::fmt;
5
6use smallvec::SmallVec;
7
8use fast_html5ever::{LocalName, Namespace};
9use selectors::{
10 matching,
11 parser::{self, SelectorParseErrorKind},
12};
13
14use crate::error::SelectorErrorKind;
15use crate::ElementRef;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct Selector {
22 selectors: SmallVec<[parser::Selector<Simple>; 1]>,
24}
25
26impl Selector {
27 pub fn parse(selectors: &'_ str) -> Result<Self, SelectorErrorKind> {
30 let mut parser_input = cssparser::ParserInput::new(selectors);
31 let mut parser = cssparser::Parser::new(&mut parser_input);
32
33 parser::SelectorList::parse(&Parser, &mut parser)
34 .map(|list| Selector { selectors: list.0 })
35 .map_err(SelectorErrorKind::from)
36 }
37
38 pub fn matches(&self, element: &ElementRef) -> bool {
40 self.matches_with_scope(element, None)
41 }
42
43 pub fn matches_with_scope(&self, element: &ElementRef, scope: Option<ElementRef>) -> bool {
47 let mut nth_index_cache = Default::default();
48 let mut context = matching::MatchingContext::new(
49 matching::MatchingMode::Normal,
50 None,
51 Some(&mut nth_index_cache),
52 matching::QuirksMode::NoQuirks,
53 );
56 context.scope_element = scope.map(|x| selectors::Element::opaque(&x));
57 self.selectors
58 .iter()
59 .any(|s| matching::matches_selector(s, 0, None, element, &mut context, &mut |_, _| {}))
60 }
61}
62
63#[derive(Debug, Clone, Copy)]
65pub struct Parser;
66
67impl<'i> parser::Parser<'i> for Parser {
68 type Impl = Simple;
69 type Error = SelectorParseErrorKind<'i>;
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct Simple;
75
76impl parser::SelectorImpl for Simple {
77 type AttrValue = CssString;
78 type Identifier = CssLocalName;
79 type ClassName = CssLocalName;
80 type LocalName = CssLocalName;
81 type PartName = LocalName;
82 type NamespacePrefix = CssLocalName;
83 type NamespaceUrl = Namespace;
84 type BorrowedNamespaceUrl = Namespace;
85 type BorrowedLocalName = CssLocalName;
86
87 type NonTSPseudoClass = NonTSPseudoClass;
88 type PseudoElement = PseudoElement;
89
90 type ExtraMatchingData = ();
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct CssString(pub String);
97
98impl<'a> From<&'a str> for CssString {
99 fn from(val: &'a str) -> Self {
100 Self(val.to_owned())
101 }
102}
103
104impl AsRef<str> for CssString {
105 fn as_ref(&self) -> &str {
106 &self.0
107 }
108}
109
110impl cssparser::ToCss for CssString {
111 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
112 where
113 W: fmt::Write,
114 {
115 cssparser::serialize_string(&self.0, dest)
116 }
117}
118
119#[derive(Debug, Default, Clone, PartialEq, Eq)]
121pub struct CssLocalName(pub LocalName);
122
123impl std::fmt::Display for CssString {
124 fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
125 Ok(())
126 }
127}
128
129impl std::fmt::Display for CssLocalName {
130 fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
131 Ok(())
132 }
133}
134
135impl<'a> From<&'a str> for CssLocalName {
136 fn from(val: &'a str) -> Self {
137 Self(val.into())
138 }
139}
140
141impl cssparser::ToCss for CssLocalName {
142 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
143 where
144 W: fmt::Write,
145 {
146 dest.write_str(&self.0)
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
152pub enum NonTSPseudoClass {
153 AnyLink,
155 Link,
157 Visited,
159 Active,
161 Focus,
163 Hover,
165 Enabled,
167 Disabled,
169 Checked,
171 Indeterminate,
173}
174
175impl parser::NonTSPseudoClass for NonTSPseudoClass {
176 type Impl = Simple;
177 fn is_active_or_hover(&self) -> bool {
178 false
179 }
180 fn is_user_action_state(&self) -> bool {
181 false
182 }
183 fn has_zero_specificity(&self) -> bool {
184 false
185 }
186}
187
188impl cssparser::ToCss for NonTSPseudoClass {
189 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
190 where
191 W: fmt::Write,
192 {
193 dest.write_str(match *self {
194 NonTSPseudoClass::AnyLink => ":any-link",
195 NonTSPseudoClass::Link => ":link",
196 NonTSPseudoClass::Visited => ":visited",
197 NonTSPseudoClass::Active => ":active",
198 NonTSPseudoClass::Focus => ":focus",
199 NonTSPseudoClass::Hover => ":hover",
200 NonTSPseudoClass::Enabled => ":enabled",
201 NonTSPseudoClass::Disabled => ":disabled",
202 NonTSPseudoClass::Checked => ":checked",
203 NonTSPseudoClass::Indeterminate => ":indeterminate",
204 })
205 }
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
210pub enum PseudoElement {}
211
212impl parser::PseudoElement for PseudoElement {
213 type Impl = Simple;
214}
215
216impl cssparser::ToCss for PseudoElement {
217 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
218 where
219 W: fmt::Write,
220 {
221 dest.write_str("")
222 }
223}
224
225impl<'i> TryFrom<&'i str> for Selector {
226 type Error = SelectorErrorKind<'i>;
227
228 fn try_from(s: &'i str) -> Result<Self, Self::Error> {
229 Selector::parse(s)
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use std::convert::TryInto;
237
238 #[test]
239 fn selector_conversions() {
240 let s = "#testid.testclass";
241 let _sel: Selector = s.try_into().unwrap();
242
243 let s = s.to_owned();
244 let _sel: Selector = (*s).try_into().unwrap();
245 }
246
247 #[test]
248 #[should_panic]
249 fn invalid_selector_conversions() {
250 let s = "<failing selector>";
251 let _sel: Selector = s.try_into().unwrap();
252 }
253}