#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Selector {
pub(crate) selectors: Vec<ComplexSelector>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ComplexSelector {
pub(crate) parts: Vec<SelectorPart>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SelectorPart {
pub(crate) combinator: Option<Combinator>,
pub(crate) compound: Compound,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Combinator {
Descendant,
Child,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Compound {
pub(crate) name: Option<LocalName>,
pub(crate) explicit_universal: bool,
pub(crate) id: Option<Box<str>>,
pub(crate) classes: Vec<Box<str>>,
pub(crate) attributes: Vec<AttributeSelector>,
pub(crate) nth: Vec<Nth>,
pub(crate) negations: Vec<Self>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct AttributeSelector {
pub(crate) name: Box<str>,
pub(crate) operator: Option<AttributeOperator>,
pub(crate) value: Box<str>,
pub(crate) case: CaseSensitivity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum AttributeOperator {
Equals,
Includes,
DashMatch,
Prefix,
Suffix,
Substring,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CaseSensitivity {
CaseSensitive,
AsciiCaseInsensitive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Nth {
pub(crate) ty: NthType,
pub(crate) a: i32,
pub(crate) b: i32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum NthType {
Child,
OfType,
}
impl Nth {
pub(crate) fn matches_index(self, index: usize) -> bool {
let Ok(i) = i64::try_from(index) else {
return false;
};
let a = i64::from(self.a);
let b = i64::from(self.b);
let Some(diff) = i.checked_sub(b) else {
return false;
};
if a == 0 {
return diff == 0;
}
diff % a == 0 && diff / a >= 0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct LocalName {
name: Box<str>,
packed: Option<u64>,
}
impl LocalName {
pub(crate) fn new(name: &str) -> Self {
let lower = name.to_ascii_lowercase();
let packed = pack_name(lower.as_bytes());
Self {
name: lower.into_boxed_str(),
packed,
}
}
pub(crate) fn as_str(&self) -> &str {
&self.name
}
pub(crate) fn matches(&self, raw: &str) -> bool {
self.matches_bytes(raw.as_bytes())
}
pub(crate) fn matches_bytes(&self, raw: &[u8]) -> bool {
if let Some(packed) = self.packed
&& let Some(other) = pack_name(raw)
{
return packed == other;
}
raw.eq_ignore_ascii_case(self.name.as_bytes())
}
}
impl AttributeSelector {
pub(crate) fn matches_value(&self, actual: &[u8]) -> bool {
let expected = self.value.as_bytes();
let ci = matches!(self.case, CaseSensitivity::AsciiCaseInsensitive);
match self.operator {
None => true,
Some(AttributeOperator::Equals) => bytes_eq(actual, expected, ci),
Some(AttributeOperator::Includes) => {
!expected.is_empty()
&& !expected.iter().any(u8::is_ascii_whitespace)
&& actual
.split(u8::is_ascii_whitespace)
.any(|token| bytes_eq(token, expected, ci))
}
Some(AttributeOperator::DashMatch) => {
bytes_eq(actual, expected, ci)
|| (actual.len() > expected.len()
&& bytes_starts_with(actual, expected, ci)
&& actual.get(expected.len()) == Some(&b'-'))
}
Some(AttributeOperator::Prefix) => {
!expected.is_empty() && bytes_starts_with(actual, expected, ci)
}
Some(AttributeOperator::Suffix) => {
!expected.is_empty() && bytes_ends_with(actual, expected, ci)
}
Some(AttributeOperator::Substring) => {
!expected.is_empty() && bytes_contains(actual, expected, ci)
}
}
}
}
fn bytes_eq(a: &[u8], b: &[u8], case_insensitive: bool) -> bool {
if case_insensitive {
a.eq_ignore_ascii_case(b)
} else {
a == b
}
}
fn bytes_starts_with(haystack: &[u8], needle: &[u8], case_insensitive: bool) -> bool {
haystack
.get(..needle.len())
.is_some_and(|head| bytes_eq(head, needle, case_insensitive))
}
fn bytes_ends_with(haystack: &[u8], needle: &[u8], case_insensitive: bool) -> bool {
haystack
.len()
.checked_sub(needle.len())
.and_then(|start| haystack.get(start..))
.is_some_and(|tail| bytes_eq(tail, needle, case_insensitive))
}
fn bytes_contains(haystack: &[u8], needle: &[u8], case_insensitive: bool) -> bool {
if needle.len() > haystack.len() {
return false;
}
if !case_insensitive {
return haystack
.windows(needle.len())
.any(|window| window == needle);
}
(0..=haystack.len() - needle.len()).any(|i| {
haystack
.get(i..i + needle.len())
.is_some_and(|window| window.eq_ignore_ascii_case(needle))
})
}
fn pack_name(bytes: &[u8]) -> Option<u64> {
if bytes.len() > 8 {
return None;
}
let mut packed = 0u64;
for &b in bytes {
if !b.is_ascii() {
return None;
}
packed = (packed << 8) | u64::from(b.to_ascii_lowercase());
}
Some(packed)
}