tl/queryselector/
selector.rs

1use crate::Node;
2
3/// A single query selector node
4#[derive(Debug, Clone)]
5pub enum Selector<'a> {
6    /// Tag selector: foo
7    Tag(&'a [u8]),
8    /// ID selector: #foo
9    Id(&'a [u8]),
10    /// Class selector: .foo
11    Class(&'a [u8]),
12    /// All selector: *
13    All,
14    /// And combinator: .foo.bar
15    And(Box<Selector<'a>>, Box<Selector<'a>>),
16    /// Or combinator: .foo, .bar
17    Or(Box<Selector<'a>>, Box<Selector<'a>>),
18    /// Descendant combinator: .foo .bar
19    Descendant(Box<Selector<'a>>, Box<Selector<'a>>),
20    /// Parent combinator: .foo > .bar
21    Parent(Box<Selector<'a>>, Box<Selector<'a>>),
22    /// Attribute: \[foo\]
23    Attribute(&'a [u8]),
24    /// Attribute with value: [foo=bar]
25    AttributeValue(&'a [u8], &'a [u8]),
26    /// Attribute with whitespace-separated list of values that contains a value: [foo~=bar]
27    AttributeValueWhitespacedContains(&'a [u8], &'a [u8]),
28    /// Attribute with value that starts with: [foo^=bar]
29    AttributeValueStartsWith(&'a [u8], &'a [u8]),
30    /// Attribute with value that ends with: [foo$=bar]
31    AttributeValueEndsWith(&'a [u8], &'a [u8]),
32    /// Attribute with value that contains: [foo*=bar]
33    AttributeValueSubstring(&'a [u8], &'a [u8]),
34}
35
36impl<'a> Selector<'a> {
37    /// Checks if the given node matches this selector
38    pub fn matches<'b>(&self, node: &Node<'b>) -> bool {
39        match self {
40            Self::Tag(tag) => node.as_tag().is_some_and(|t| t._name.as_bytes().eq(*tag)),
41            Self::Id(id) => node
42                .as_tag()
43                .is_some_and(|t| t._attributes.id == Some((*id).into())),
44            Self::Class(class) => node
45                .as_tag()
46                .is_some_and(|t| t._attributes.is_class_member(*class)),
47            Self::And(a, b) => a.matches(node) && b.matches(node),
48            Self::Or(a, b) => a.matches(node) || b.matches(node),
49            Self::All => true,
50            Self::Attribute(attribute) => node
51                .as_tag()
52                .is_some_and(|t| t._attributes.get(*attribute).is_some()),
53            Self::AttributeValue(attribute, value) => {
54                check_attribute(node, attribute, value, |attr, value| attr == value)
55            }
56            Self::AttributeValueEndsWith(attribute, value) => {
57                check_attribute(node, attribute, value, |attr, value| attr.ends_with(value))
58            }
59            Self::AttributeValueStartsWith(attribute, value) => {
60                check_attribute(node, attribute, value, |attr, value| {
61                    attr.starts_with(value)
62                })
63            }
64            Self::AttributeValueSubstring(attribute, value) => {
65                check_attribute(node, attribute, value, |attr, value| attr.contains(value))
66            }
67            Self::AttributeValueWhitespacedContains(attribute, value) => {
68                check_attribute(node, attribute, value, |attr, value| {
69                    attr.split_whitespace().any(|x| x == value)
70                })
71            }
72            _ => false,
73        }
74    }
75}
76
77fn check_attribute<F>(node: &Node, attribute: &[u8], value: &[u8], callback: F) -> bool
78where
79    F: Fn(&str, &str) -> bool,
80{
81    node.as_tag().is_some_and(|t| {
82        t._attributes
83            .get(attribute)
84            .flatten()
85            .is_some_and(|attr| callback(&attr.as_utf8_str(), &String::from_utf8_lossy(value)))
86    })
87}