use crate::{Element, NodeId, NodeRef, Tag};
use super::{AttrOp, AttrSel, Comb, Compound, Nth, Selector, Simple};
pub(crate) struct Ctx {
pub(super) scope: Option<NodeId>,
pub(super) anchor: Option<NodeId>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Res {
Matched,
RestartFromLaterSibling,
RestartFromDescendant,
NotMatchedGlobally,
}
pub(super) fn match_complex(parts: &[Compound], i: usize, el: NodeRef<'_>, cx: &Ctx) -> Res {
let part = &parts[i];
if !part.tag_may_match(el) || !match_compound(&part.simples, el, cx) {
return Res::RestartFromLaterSibling;
}
if i + 1 == parts.len() {
return Res::Matched;
}
let comb = part.comb;
let not_found = match comb {
Comb::Next | Comb::Later => Res::RestartFromDescendant,
Comb::Child | Comb::Descendant => Res::NotMatchedGlobally,
};
let mut cur = el;
loop {
let next = match comb {
Comb::Child | Comb::Descendant => cur.parent().filter(NodeRef::is_element),
Comb::Next | Comb::Later => prev_element(cur),
};
let Some(next) = next else {
return not_found;
};
let r = if parts[i + 1].tag_may_match(next) {
match_complex(parts, i + 1, next, cx)
} else {
Res::RestartFromLaterSibling
};
match (r, comb) {
(Res::Matched | Res::NotMatchedGlobally, _) | (_, Comb::Next) => return r,
(_, Comb::Child) => return Res::RestartFromDescendant,
(Res::RestartFromDescendant, Comb::Later) => return r,
_ => {}
}
cur = next;
}
}
impl Compound {
#[inline]
pub(super) fn tag_may_match(&self, el: NodeRef<'_>) -> bool {
match self.tag {
Some(t) => matches!(el.tag(), Some(x) if x == t || x == Tag::Other),
None => true,
}
}
}
fn match_compound(simples: &[Simple], el: NodeRef<'_>, cx: &Ctx) -> bool {
let Some(e) = el.as_element() else {
return false;
};
simples.iter().all(|s| match_simple(s, el, &e, cx))
}
fn match_simple(s: &Simple, el: NodeRef<'_>, e: &Element<'_>, cx: &Ctx) -> bool {
match s {
Simple::Type(tag, name) => type_matches(e, *tag, name),
Simple::Id(id) => e.attr("id") == Some(&**id),
Simple::Class(c) => e
.attr("class")
.is_some_and(|v| v.split_ascii_whitespace().any(|x| x == &**c)),
Simple::Attr(a) => e.attr(&a.name).is_some_and(|v| attr_matches(a, v)),
Simple::Nth(n) => nth_matches(n, el, e, cx),
Simple::Empty => !el.children().any(|c| c.is_element() || c.is_text()),
Simple::Root => el.parent().is_some_and(|p| p.is_document()),
Simple::Scope => match cx.scope {
Some(id) => el.id() == id,
None => el.parent().is_some_and(|p| p.is_document()),
},
Simple::Anchor => cx.anchor == Some(el.id()),
Simple::Not(sel) => !sel.matches_in(el, cx),
Simple::Is(sel) => sel.matches_in(el, cx),
Simple::Has(sel) => has_matches(sel, el, cx),
}
}
fn type_matches(e: &Element<'_>, tag: Tag, name: &str) -> bool {
let t = e.tag();
if t == tag {
tag != Tag::Other || e.name() == name
} else {
t == Tag::Other && e.name() == name
}
}
fn attr_matches(a: &AttrSel, v: &str) -> bool {
let (v, want) = (v.as_bytes(), a.value.as_bytes());
let eq = |x: &[u8], y: &[u8]| {
if a.ci {
x.eq_ignore_ascii_case(y)
} else {
x == y
}
};
match a.op {
AttrOp::Exists => true,
AttrOp::Equals => eq(v, want),
AttrOp::Includes => v
.split(|c| matches!(c, b' ' | b'\t' | b'\n' | b'\x0c' | b'\r'))
.any(|w| eq(w, want)),
AttrOp::Dash => {
eq(v, want)
|| (v.len() > want.len() && v[want.len()] == b'-' && eq(&v[..want.len()], want))
}
AttrOp::Prefix => v.len() >= want.len() && eq(&v[..want.len()], want),
AttrOp::Suffix => v.len() >= want.len() && eq(&v[v.len() - want.len()..], want),
AttrOp::Substring => {
if a.ci {
v.windows(want.len()).any(|w| w.eq_ignore_ascii_case(want))
} else {
memchr::memmem::find(v, want).is_some()
}
}
AttrOp::Never => false,
}
}
fn nth_matches(n: &Nth, el: NodeRef<'_>, e: &Element<'_>, cx: &Ctx) -> bool {
if let Some(of) = &n.of
&& !of.matches_in(el, cx)
{
return false;
}
let tag = e.tag();
let counts = |s: NodeRef<'_>| {
let Some(st) = s.tag() else {
return false;
};
if n.of_type {
st == tag
&& (tag != Tag::Other || s.as_element().is_some_and(|se| se.name() == e.name()))
} else if let Some(of) = &n.of {
of.matches_in(s, cx)
} else {
true
}
};
let mut pos = 1i64;
let mut cur = el;
loop {
let next = if n.last {
cur.next_sibling()
} else {
cur.prev_sibling()
};
let Some(s) = next else { break };
if counts(s) {
pos += 1;
}
cur = s;
}
let (a, b) = (i64::from(n.a), i64::from(n.b));
if a == 0 {
pos == b
} else {
let d = pos - b;
d % a == 0 && d / a >= 0
}
}
fn has_matches(sel: &Selector, el: NodeRef<'_>, cx: &Ctx) -> bool {
let inner = Ctx {
scope: cx.scope,
anchor: Some(el.id()),
};
sel.list.iter().any(|c| {
let leading = c.parts[c.parts.len() - 2].comb;
let hit = |n: NodeRef<'_>| {
n.is_element() && match_complex(&c.parts, 0, n, &inner) == Res::Matched
};
let single = c.parts.len() == 2;
match leading {
Comb::Child if single => el.children().any(hit),
Comb::Next if single => next_element(el).is_some_and(hit),
Comb::Later if single => {
let mut s = el.next_sibling();
while let Some(n) = s {
if hit(n) {
return true;
}
s = n.next_sibling();
}
false
}
Comb::Descendant | Comb::Child => el.descendants().skip(1).any(hit),
Comb::Next | Comb::Later => {
let mut s = el.next_sibling();
while let Some(n) = s {
if n.descendants().any(hit) {
return true;
}
s = n.next_sibling();
}
false
}
}
})
}
fn next_element<'a>(n: NodeRef<'a>) -> Option<NodeRef<'a>> {
let mut cur = n.next_sibling();
while let Some(c) = cur {
if c.is_element() {
return Some(c);
}
cur = c.next_sibling();
}
None
}
fn prev_element<'a>(n: NodeRef<'a>) -> Option<NodeRef<'a>> {
let mut cur = n.prev_sibling();
while let Some(c) = cur {
if c.is_element() {
return Some(c);
}
cur = c.prev_sibling();
}
None
}