use crate::{Node, asm_core};
#[derive(Debug, Clone)]
pub enum Selector<'a, const MAX_SELECTOR_NODES: usize = 0> {
Tag(&'a [u8]),
Id(&'a [u8]),
Class(&'a [u8]),
All,
And(
Box<Selector<'a, MAX_SELECTOR_NODES>>,
Box<Selector<'a, MAX_SELECTOR_NODES>>,
),
Or(
Box<Selector<'a, MAX_SELECTOR_NODES>>,
Box<Selector<'a, MAX_SELECTOR_NODES>>,
),
Descendant(
Box<Selector<'a, MAX_SELECTOR_NODES>>,
Box<Selector<'a, MAX_SELECTOR_NODES>>,
),
Parent(
Box<Selector<'a, MAX_SELECTOR_NODES>>,
Box<Selector<'a, MAX_SELECTOR_NODES>>,
),
Attribute(&'a [u8]),
AttributeValue(&'a [u8], &'a [u8]),
AttributeValueWhitespacedContains(&'a [u8], &'a [u8]),
AttributeValueStartsWith(&'a [u8], &'a [u8]),
AttributeValueEndsWith(&'a [u8], &'a [u8]),
AttributeValueSubstring(&'a [u8], &'a [u8]),
}
impl<'a, const MAX_SELECTOR_NODES: usize> Selector<'a, MAX_SELECTOR_NODES> {
pub fn matches<'b>(&self, node: &Node<'b>) -> bool {
match self {
Self::Tag(tag) => node
.as_tag()
.is_some_and(|t| asm_core::bytes_eq(t._name.as_bytes(), tag)),
Self::Id(id) => node.as_tag().is_some_and(|t| {
t._attributes
.id
.as_ref()
.is_some_and(|attr| asm_core::bytes_eq(attr.as_bytes(), id))
}),
Self::Class(class) => node
.as_tag()
.is_some_and(|t| t._attributes.is_class_member(*class)),
Self::And(a, b) => a.matches(node) && b.matches(node),
Self::Or(a, b) => a.matches(node) || b.matches(node),
Self::All => true,
Self::Attribute(attribute) => node
.as_tag()
.is_some_and(|t| t._attributes.get(*attribute).is_some()),
Self::AttributeValue(attribute, value) => {
check_attribute(node, attribute, value, asm_core::bytes_eq)
}
Self::AttributeValueEndsWith(attribute, value) => {
check_attribute(node, attribute, value, asm_core::ends_with)
}
Self::AttributeValueStartsWith(attribute, value) => {
check_attribute(node, attribute, value, asm_core::starts_with)
}
Self::AttributeValueSubstring(attribute, value) => {
check_attribute(node, attribute, value, asm_core::contains_bytes)
}
Self::AttributeValueWhitespacedContains(attribute, value) => check_attribute(
node,
attribute,
value,
asm_core::contains_ascii_whitespace_token,
),
_ => false,
}
}
}
fn check_attribute<F>(node: &Node, attribute: &[u8], value: &[u8], callback: F) -> bool
where
F: Fn(&[u8], &[u8]) -> bool,
{
node.as_tag().is_some_and(|t| {
t._attributes
.get(attribute)
.flatten()
.is_some_and(|attr| callback(attr.as_bytes(), value))
})
}