use crate::Node;
#[derive(Debug, Clone)]
pub enum Selector<'a> {
Tag(&'a [u8]),
Id(&'a [u8]),
Class(&'a [u8]),
All,
And(Box<Selector<'a>>, Box<Selector<'a>>),
Or(Box<Selector<'a>>, Box<Selector<'a>>),
Descendant(Box<Selector<'a>>, Box<Selector<'a>>),
Parent(Box<Selector<'a>>, Box<Selector<'a>>),
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> Selector<'a> {
pub fn matches<'b>(&self, node: &Node<'b>) -> bool {
match self {
Self::Tag(tag) => node.as_tag().map_or(false, |t| t._name.as_bytes().eq(*tag)),
Self::Id(id) => node
.as_tag()
.map_or(false, |t| t._attributes.id == Some((*id).into())),
Self::Class(class) => node
.as_tag()
.map_or(false, |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()
.map_or(false, |t| t._attributes.get(*attribute).is_some()),
Self::AttributeValue(attribute, value) => {
check_attribute(node, attribute, value, |attr, value| attr == value)
}
Self::AttributeValueEndsWith(attribute, value) => {
check_attribute(node, attribute, value, |attr, value| attr.ends_with(value))
}
Self::AttributeValueStartsWith(attribute, value) => {
check_attribute(node, attribute, value, |attr, value| {
attr.starts_with(value)
})
}
Self::AttributeValueSubstring(attribute, value) => {
check_attribute(node, attribute, value, |attr, value| attr.contains(value))
}
Self::AttributeValueWhitespacedContains(attribute, value) => {
check_attribute(node, attribute, value, |attr, value| {
attr.split_whitespace().any(|x| x == value)
})
}
_ => false,
}
}
}
fn check_attribute<F>(node: &Node, attribute: &[u8], value: &[u8], callback: F) -> bool
where
F: Fn(&str, &str) -> bool,
{
node.as_tag().map_or(false, |t| {
t._attributes
.get(attribute)
.flatten()
.map_or(false, |attr| {
callback(&attr.as_utf8_str(), &String::from_utf8_lossy(value))
})
})
}