use super::{
document::{Document, NameSet},
element::Element,
node::{NodeData, NodeId},
selectors::{ancestor_names, ParseError, SelectorAnchor, Selectors},
Specificity,
};
use selectors::context::SelectorCaches;
#[inline]
pub(crate) fn select<'a, 'c>(
document: &'a Document,
selectors: &str,
caches: &'c mut SelectorCaches,
) -> Result<Select<'a, 'c>, ParseError> {
Selectors::compile(selectors).map(|selectors| {
let source = if !document.has_indexes() {
ElementSource::Slice(document.elements.iter())
} else if !selectors
.iter()
.any(|selector| document.has_required_names(selector))
{
ElementSource::Slice([].iter())
} else {
match selectors.anchor() {
SelectorAnchor::Id(id) => {
ElementSource::Slice(document.get_by_id(id.as_inner()).iter())
}
SelectorAnchor::Class(class) => {
ElementSource::Slice(document.get_by_class(class.as_inner()).iter())
}
SelectorAnchor::Tag(tag) => {
ElementSource::Slice(document.get_by_tag(tag.as_inner()).iter())
}
SelectorAnchor::Attribute(name) => {
ElementSource::Slice(document.get_by_attribute(name.as_inner()).iter())
}
SelectorAnchor::Root => {
ElementSource::Roots(document[NodeId::document_id()].first_child)
}
SelectorAnchor::None => ElementSource::Slice(document.elements.iter()),
}
};
let ancestors = match selectors.0.as_slice() {
[single] if document.has_indexes() => {
Some(ancestor_names(single)).filter(|names| !names.is_empty())
}
_ => None,
};
Select {
document,
caches,
source,
selectors,
ancestors,
}
})
}
enum ElementSource<'a> {
Slice(std::slice::Iter<'a, NodeId>),
Roots(Option<NodeId>),
}
pub(crate) struct Select<'a, 'c> {
document: &'a Document,
caches: &'c mut SelectorCaches,
source: ElementSource<'a>,
selectors: Selectors,
ancestors: Option<NameSet>,
}
impl<'a> Select<'a, '_> {
#[inline]
pub(crate) fn specificity(&self) -> Specificity {
Specificity::new(self.selectors.0[0].specificity())
}
#[inline]
fn find_match(&mut self, may_match: impl Fn(&Document, NodeId) -> bool) -> Option<Element<'a>> {
loop {
let element_id = match &mut self.source {
ElementSource::Slice(iter) => *iter.next()?,
ElementSource::Roots(next) => loop {
let node_id = (*next)?;
let node = &self.document[node_id];
*next = node.next_sibling;
if matches!(node.data, NodeData::Element { .. }) {
break node_id;
}
},
};
let NodeData::Element { element, .. } = &self.document[element_id].data else {
unreachable!("Element ids always point to element nodes")
};
if !may_match(self.document, element_id) {
continue;
}
let element = Element::new(self.document, element_id, element);
for selector in self.selectors.iter() {
if element.matches(selector, self.caches) {
return Some(element);
}
}
}
}
}
impl<'a> Iterator for Select<'a, '_> {
type Item = Element<'a>;
#[inline]
fn next(&mut self) -> Option<Element<'a>> {
match self.ancestors {
None => self.find_match(|_, _| true),
Some(required) => self.find_match(|document, element_id| {
document
.ancestor_names(element_id)
.is_none_or(|names| names.contains_all(&required))
}),
}
}
}