scraper_forky/
selector.rs

1//! CSS selectors.
2
3use std::convert::TryFrom;
4use std::fmt;
5
6use smallvec::SmallVec;
7
8use html5ever::{LocalName, Namespace};
9use selectors::{
10    matching,
11    parser::{self, SelectorParseErrorKind},
12};
13
14use crate::error::SelectorErrorKind;
15use crate::ElementRef;
16
17/// Wrapper around CSS selectors.
18///
19/// Represents a "selector group", i.e. a comma-separated list of selectors.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct Selector {
22    /// The CSS selectors.
23    selectors: SmallVec<[parser::Selector<Simple>; 1]>,
24}
25
26impl Selector {
27    /// Parses a CSS selector group.
28
29    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    /// Returns true if the element matches this selector.
39    pub fn matches(&self, element: &ElementRef) -> bool {
40        self.matches_with_scope(element, None)
41    }
42
43    /// Returns true if the element matches this selector.
44    /// The optional `scope` argument is used to specify which element has `:scope` pseudo-class.
45    /// When it is `None`, `:scope` will match the root element.
46    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            // matching::NeedsSelectorFlags::No,
54            // matching::IgnoreNthChildForInvalidation::No,
55        );
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/// An implementation of `Parser` for `selectors`
64struct Parser;
65impl<'i> parser::Parser<'i> for Parser {
66    type Impl = Simple;
67    type Error = SelectorParseErrorKind<'i>;
68}
69
70/// A simple implementation of `SelectorImpl` with no pseudo-classes or pseudo-elements.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub struct Simple;
73
74impl parser::SelectorImpl for Simple {
75    type AttrValue = CssString;
76    type Identifier = CssLocalName;
77    type ClassName = CssLocalName;
78    type LocalName = CssLocalName;
79    type NamespacePrefix = CssLocalName;
80    type NamespaceUrl = Namespace;
81    type BorrowedNamespaceUrl = Namespace;
82    type BorrowedLocalName = CssLocalName;
83
84    type NonTSPseudoClass = NonTSPseudoClass;
85    type PseudoElement = PseudoElement;
86
87    // see: https://github.com/servo/servo/pull/19747#issuecomment-357106065
88    type ExtraMatchingData = ();
89}
90
91/// Wraps [`String`] so that it can be used with [`selectors`]
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct CssString(pub String);
94
95impl<'a> From<&'a str> for CssString {
96    fn from(val: &'a str) -> Self {
97        Self(val.to_owned())
98    }
99}
100
101impl AsRef<str> for CssString {
102    fn as_ref(&self) -> &str {
103        &self.0
104    }
105}
106
107impl cssparser::ToCss for CssString {
108    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
109    where
110        W: fmt::Write,
111    {
112        cssparser::serialize_string(&self.0, dest)
113    }
114}
115
116/// Wraps [`LocalName`] so that it can be used with [`selectors`]
117#[derive(Debug, Default, Clone, PartialEq, Eq)]
118pub struct CssLocalName(pub LocalName);
119
120impl std::fmt::Display for CssString {
121    fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {  Ok(()) }
122}
123
124impl std::fmt::Display for CssLocalName {
125    fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { Ok(()) }
126}
127
128
129impl<'a> From<&'a str> for CssLocalName {
130    fn from(val: &'a str) -> Self {
131        Self(val.into())
132    }
133}
134
135impl cssparser::ToCss for CssLocalName {
136    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
137    where
138        W: fmt::Write,
139    {
140        dest.write_str(&self.0)
141    }
142}
143
144/// Non Tree-Structural Pseudo-Class.
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum NonTSPseudoClass {}
147
148impl parser::NonTSPseudoClass for NonTSPseudoClass {
149    type Impl = Simple;
150
151    fn is_active_or_hover(&self) -> bool {
152        false
153    }
154
155    // fn is_user_action_state(&self) -> bool {
156    //     false
157    // }
158}
159
160impl cssparser::ToCss for NonTSPseudoClass {
161    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
162    where
163        W: fmt::Write,
164    {
165        dest.write_str("")
166    }
167}
168
169/// CSS Pseudo-Element
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub enum PseudoElement {}
172
173impl parser::PseudoElement for PseudoElement {
174    type Impl = Simple;
175}
176
177impl cssparser::ToCss for PseudoElement {
178    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
179    where
180        W: fmt::Write,
181    {
182        dest.write_str("")
183    }
184}
185
186impl<'i> TryFrom<&'i str> for Selector {
187    type Error = SelectorErrorKind<'i>;
188
189    fn try_from(s: &'i str) -> Result<Self, Self::Error> {
190        Selector::parse(s)
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use std::convert::TryInto;
198
199    #[test]
200    fn selector_conversions() {
201        let s = "#testid.testclass";
202        let _sel: Selector = s.try_into().unwrap();
203
204        let s = s.to_owned();
205        let _sel: Selector = (*s).try_into().unwrap();
206    }
207
208    #[test]
209    #[should_panic]
210    fn invalid_selector_conversions() {
211        let s = "<failing selector>";
212        let _sel: Selector = s.try_into().unwrap();
213    }
214}