use crate::ast::{
AttributeSelector, Combinator, CompoundSelector, QueryAst, SelectorSequence, SimpleSelector,
};
pub trait QueryTreeAdapter {
type NodeId: Copy + Eq;
fn children_of(&self, node: Self::NodeId) -> Vec<Self::NodeId>;
fn matches_type(&self, _node: Self::NodeId, _type_name: &str) -> bool {
false
}
fn matches_id(&self, _node: Self::NodeId, _id: &str) -> bool {
false
}
fn matches_guid(&self, _node: Self::NodeId, _guid: &str) -> bool {
false
}
fn matches_name(&self, _node: Self::NodeId, _name: &str) -> bool {
false
}
fn matches_class(&self, _node: Self::NodeId, _class_name: &str) -> bool {
false
}
fn matches_attribute(&self, _node: Self::NodeId, attribute: &AttributeSelector) -> bool {
if attribute.name == "name" {
return attribute
.value
.as_deref()
.map(|name| self.matches_name(_node, name))
.unwrap_or(false);
}
false
}
}
#[derive(Debug, Default)]
pub struct QueryEvaluator;
impl QueryEvaluator {
pub fn evaluate<T: QueryTreeAdapter>(
tree: &T,
root: T::NodeId,
ast: &QueryAst,
) -> Vec<T::NodeId> {
let mut out = Vec::new();
for sequence in &ast.selector_groups {
let mut path: Vec<T::NodeId> = Vec::new();
collect(tree, root, sequence, &mut path, &mut out);
}
out
}
fn matches_compound<T: QueryTreeAdapter>(
tree: &T,
node: T::NodeId,
compound: &CompoundSelector,
) -> bool {
compound
.simple_selectors
.iter()
.all(|selector| Self::matches_simple(tree, node, selector))
}
fn matches_simple<T: QueryTreeAdapter>(
tree: &T,
node: T::NodeId,
selector: &SimpleSelector,
) -> bool {
match selector {
SimpleSelector::Universal => true,
SimpleSelector::Type(type_name) => tree.matches_type(node, type_name),
SimpleSelector::Id(id) => tree.matches_id(node, id),
SimpleSelector::Guid(guid) => tree.matches_guid(node, guid),
SimpleSelector::Name(name) => tree.matches_name(node, name),
SimpleSelector::Class(class_name) => tree.matches_class(node, class_name),
SimpleSelector::Attribute(attribute) => tree.matches_attribute(node, attribute),
}
}
}
fn collect<T: QueryTreeAdapter>(
tree: &T,
node: T::NodeId,
sequence: &SelectorSequence,
path: &mut Vec<T::NodeId>,
out: &mut Vec<T::NodeId>,
) {
path.push(node);
if matches_with_path::<T>(tree, sequence, path) {
out.push(node);
}
for child in tree.children_of(node) {
collect(tree, child, sequence, path, out);
}
path.pop();
}
fn matches_with_path<T: QueryTreeAdapter>(
tree: &T,
sequence: &SelectorSequence,
path: &[T::NodeId],
) -> bool {
let segs = &sequence.segments;
if segs.is_empty() || path.is_empty() {
return false;
}
let last = segs.len() - 1;
let candidate = path[path.len() - 1];
if !QueryEvaluator::matches_compound(tree, candidate, &segs[last].compound) {
return false;
}
let mut path_cursor: isize = path.len() as isize - 1;
for i in (0..last).rev() {
let combinator = segs[i + 1].combinator.unwrap_or(Combinator::Descendant);
match combinator {
Combinator::Child => {
path_cursor -= 1;
if path_cursor < 0 {
return false;
}
if !QueryEvaluator::matches_compound(
tree,
path[path_cursor as usize],
&segs[i].compound,
) {
return false;
}
}
Combinator::Descendant => {
path_cursor -= 1;
let mut found = false;
while path_cursor >= 0 {
if QueryEvaluator::matches_compound(
tree,
path[path_cursor as usize],
&segs[i].compound,
) {
found = true;
break;
}
path_cursor -= 1;
}
if !found {
return false;
}
}
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{CompoundSelector, SelectorSegment, SelectorSequence, SimpleSelector};
struct ToyTree {
nodes: Vec<(Option<usize>, &'static str, &'static str)>,
}
impl QueryTreeAdapter for ToyTree {
type NodeId = usize;
fn children_of(&self, node: usize) -> Vec<usize> {
self.nodes
.iter()
.enumerate()
.filter(|(_, (p, _, _))| *p == Some(node))
.map(|(i, _)| i)
.collect()
}
fn matches_type(&self, node: usize, type_name: &str) -> bool {
self.nodes[node].1 == type_name
}
fn matches_name(&self, node: usize, name: &str) -> bool {
self.nodes[node].2 == name
}
}
fn type_seg(t: &'static str, comb: Option<Combinator>) -> SelectorSegment {
SelectorSegment {
combinator: comb,
compound: CompoundSelector {
simple_selectors: vec![SimpleSelector::Type(t.into())],
},
}
}
fn build_tree() -> ToyTree {
ToyTree {
nodes: vec![
(None, "Root", "root"),
(Some(0), "A", "a"),
(Some(1), "B", "b"),
(Some(2), "C", "c"),
(Some(0), "A", "a2"),
(Some(4), "C", "c2"),
],
}
}
#[test]
fn child_combinator_filters_indirect_descendants() {
let tree = build_tree();
let ast = QueryAst {
selector_groups: vec![SelectorSequence {
segments: vec![type_seg("A", None), type_seg("C", Some(Combinator::Child))],
}],
};
let matches = QueryEvaluator::evaluate(&tree, 0, &ast);
assert_eq!(matches, vec![5]);
}
#[test]
fn descendant_combinator_matches_at_any_depth() {
let tree = build_tree();
let ast = QueryAst {
selector_groups: vec![SelectorSequence {
segments: vec![
type_seg("A", None),
type_seg("C", Some(Combinator::Descendant)),
],
}],
};
let mut matches = QueryEvaluator::evaluate(&tree, 0, &ast);
matches.sort();
assert_eq!(matches, vec![3, 5]);
}
#[test]
fn single_segment_matches_all_descendants() {
let tree = build_tree();
let ast = QueryAst {
selector_groups: vec![SelectorSequence {
segments: vec![type_seg("A", None)],
}],
};
let mut matches = QueryEvaluator::evaluate(&tree, 0, &ast);
matches.sort();
assert_eq!(matches, vec![1, 4]);
}
}