use selectors::parser::{NthSelectorData, NthType, Selector};
use super::Translator;
use super::error::Error;
use super::xpath_expr::{Condition, XPathExpr};
use crate::parser::CssToXpathImpl;
pub const MAX_NTH_OF_DEPTH: usize = 8;
pub const MAX_NTH_OF_BYTES: usize = 1 << 20;
#[derive(Clone, Copy)]
struct OfList<'a> {
selectors: &'a [Selector<CssToXpathImpl>],
depth: usize,
}
impl Translator {
pub(crate) fn apply_nth(
&self,
xpath: &mut XPathExpr,
data: &NthSelectorData,
selector_list: Option<&[Selector<CssToXpathImpl>]>,
of_depth: usize,
) -> Result<(), Error> {
let a = data.an_plus_b.0;
let b = data.an_plus_b.1;
let of = selector_list.map(|selectors| OfList {
selectors,
depth: of_depth,
});
match data.ty {
NthType::OnlyChild => {
xpath.add_condition(
"count(preceding-sibling::*) = 0 and count(following-sibling::*) = 0",
);
Ok(())
}
NthType::OnlyOfType => {
let nodetest = xpath.same_type_nodetest().ok_or_else(|| {
Error::unsupported("`:only-of-type` on the universal selector `*`")
})?;
xpath.add_condition(&format!(
"count(preceding-sibling::{nodetest}) = 0 \
and count(following-sibling::{nodetest}) = 0"
));
Ok(())
}
NthType::Child | NthType::LastChild => self.xpath_nth_child(
xpath,
a,
b,
data.ty == NthType::LastChild,
"*",
of,
),
NthType::OfType | NthType::LastOfType => {
let nodetest = xpath.same_type_nodetest().ok_or_else(|| {
Error::unsupported("an of-type pseudo-class on the universal selector `*`")
})?;
self.xpath_nth_child(
xpath,
a,
b,
data.ty == NthType::LastOfType,
&nodetest,
of,
)
}
}
}
fn xpath_nth_child(
&self,
xpath: &mut XPathExpr,
a: i32,
b: i32,
last: bool,
nodetest: &str,
of: Option<OfList<'_>>,
) -> Result<(), Error> {
let a = i64::from(a);
let b = i64::from(b);
let b_min_1 = b - 1;
let current_element_check = match of {
Some(of) => {
if of.depth >= MAX_NTH_OF_DEPTH {
return Err(Error::unsupported(format!(
"`An+B of S` selector lists nested more than \
{MAX_NTH_OF_DEPTH} levels deep"
)));
}
let check = self
.arg_conditions(of.selectors, ":nth-child(... of S)", of.depth + 1)?
.and_then(|conditions| Condition::join_or(&conditions));
if check
.as_ref()
.is_some_and(|c| c.expr.len() > MAX_NTH_OF_BYTES)
{
return Err(Error::unsupported(format!(
"an `An+B of S` selector list translating to more than \
{MAX_NTH_OF_BYTES} bytes"
)));
}
check
}
None => None,
};
if a == 1 && b_min_1 <= 0 {
if let Some(check) = current_element_check {
xpath.push_condition(check);
}
return Ok(());
}
if a <= 0 && b_min_1 < 0 {
xpath.add_condition("0");
if let Some(check) = current_element_check {
xpath.push_condition(check);
}
return Ok(());
}
let selector_predicate = match current_element_check {
Some(ref check) => format!("[{}]", check.expr),
None => String::new(),
};
let axis = if last { "following" } else { "preceding" };
let siblings_count = format!("count({axis}-sibling::{nodetest}{selector_predicate})");
if a == 0 {
xpath.add_condition(&format!("{siblings_count} = {b_min_1}"));
if let Some(check) = current_element_check {
xpath.push_condition(check);
}
return Ok(());
}
let mut expr: Vec<String> = Vec::new();
if a > 0 {
if b_min_1 > 0 {
expr.push(format!("{siblings_count} >= {b_min_1}"));
}
} else {
expr.push(format!("{siblings_count} <= {b_min_1}"));
}
if a.abs() != 1 {
let mut left = siblings_count;
let b_neg = (-b_min_1).rem_euclid(a.abs());
if b_neg != 0 {
left = format!("({left} + {b_neg})");
}
expr.push(format!("{left} mod {a} = 0"));
}
if !expr.is_empty() {
xpath.add_condition(&expr.join(" and "));
}
if let Some(check) = current_element_check {
xpath.push_condition(check);
}
Ok(())
}
}