tl/queryselector/
selector.rs1use crate::Node;
2
3#[derive(Debug, Clone)]
5pub enum Selector<'a> {
6 Tag(&'a [u8]),
8 Id(&'a [u8]),
10 Class(&'a [u8]),
12 All,
14 And(Box<Selector<'a>>, Box<Selector<'a>>),
16 Or(Box<Selector<'a>>, Box<Selector<'a>>),
18 Descendant(Box<Selector<'a>>, Box<Selector<'a>>),
20 Parent(Box<Selector<'a>>, Box<Selector<'a>>),
22 Attribute(&'a [u8]),
24 AttributeValue(&'a [u8], &'a [u8]),
26 AttributeValueWhitespacedContains(&'a [u8], &'a [u8]),
28 AttributeValueStartsWith(&'a [u8], &'a [u8]),
30 AttributeValueEndsWith(&'a [u8], &'a [u8]),
32 AttributeValueSubstring(&'a [u8], &'a [u8]),
34}
35
36impl<'a> Selector<'a> {
37 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}