tl/queryselector/
selector.rs1use crate::Node;
2
3#[derive(Debug, Clone)]
5pub enum Selector<'a, const MAX_SELECTOR_NODES: usize = 0> {
6 Tag(&'a [u8]),
8 Id(&'a [u8]),
10 Class(&'a [u8]),
12 All,
14 #[cfg(feature = "std")]
16 And(
17 Box<Selector<'a, MAX_SELECTOR_NODES>>,
18 Box<Selector<'a, MAX_SELECTOR_NODES>>,
19 ),
20 #[cfg(feature = "std")]
22 Or(
23 Box<Selector<'a, MAX_SELECTOR_NODES>>,
24 Box<Selector<'a, MAX_SELECTOR_NODES>>,
25 ),
26 #[cfg(feature = "std")]
28 Descendant(
29 Box<Selector<'a, MAX_SELECTOR_NODES>>,
30 Box<Selector<'a, MAX_SELECTOR_NODES>>,
31 ),
32 #[cfg(feature = "std")]
34 Parent(
35 Box<Selector<'a, MAX_SELECTOR_NODES>>,
36 Box<Selector<'a, MAX_SELECTOR_NODES>>,
37 ),
38 Attribute(&'a [u8]),
40 AttributeValue(&'a [u8], &'a [u8]),
42 AttributeValueWhitespacedContains(&'a [u8], &'a [u8]),
44 AttributeValueStartsWith(&'a [u8], &'a [u8]),
46 AttributeValueEndsWith(&'a [u8], &'a [u8]),
48 AttributeValueSubstring(&'a [u8], &'a [u8]),
50}
51
52impl<'a, const MAX_SELECTOR_NODES: usize> Selector<'a, MAX_SELECTOR_NODES> {
53 pub fn matches<'b>(&self, node: &Node<'b>) -> bool {
55 match self {
56 Self::Tag(tag) => node.as_tag().is_some_and(|t| t._name.as_bytes().eq(*tag)),
57 Self::Id(id) => node
58 .as_tag()
59 .is_some_and(|t| t._attributes.id == Some((*id).into())),
60 Self::Class(class) => node
61 .as_tag()
62 .is_some_and(|t| t._attributes.is_class_member(*class)),
63 #[cfg(feature = "std")]
64 Self::And(a, b) => a.matches(node) && b.matches(node),
65 #[cfg(feature = "std")]
66 Self::Or(a, b) => a.matches(node) || b.matches(node),
67 Self::All => true,
68 Self::Attribute(attribute) => node
69 .as_tag()
70 .is_some_and(|t| t._attributes.get(*attribute).is_some()),
71 Self::AttributeValue(attribute, value) => {
72 check_attribute(node, attribute, value, |attr, value| attr == value)
73 }
74 Self::AttributeValueEndsWith(attribute, value) => {
75 check_attribute(node, attribute, value, |attr, value| attr.ends_with(value))
76 }
77 Self::AttributeValueStartsWith(attribute, value) => {
78 check_attribute(node, attribute, value, |attr, value| {
79 attr.starts_with(value)
80 })
81 }
82 Self::AttributeValueSubstring(attribute, value) => {
83 check_attribute(node, attribute, value, |attr, value| attr.contains(value))
84 }
85 Self::AttributeValueWhitespacedContains(attribute, value) => {
86 check_attribute(node, attribute, value, |attr, value| {
87 attr.split_whitespace().any(|x| x == value)
88 })
89 }
90 #[cfg(feature = "std")]
91 _ => false,
92 }
93 }
94}
95
96fn check_attribute<F>(node: &Node, attribute: &[u8], value: &[u8], callback: F) -> bool
97where
98 F: Fn(&str, &str) -> bool,
99{
100 let Ok(value) = core::str::from_utf8(value) else {
101 return false;
102 };
103 node.as_tag().is_some_and(|t| {
104 t._attributes
105 .get(attribute)
106 .flatten()
107 .and_then(|attr| attr.try_as_utf8_str())
108 .is_some_and(|attr| callback(attr, value))
109 })
110}