use std::fmt::{self, Write as _};
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 => {
let mut cond = String::new();
push_sibling_count(&mut cond, "preceding-sibling::*", Bound::Exactly, 0);
cond.push_str(" and ");
push_sibling_count(&mut cond, "following-sibling::*", Bound::Exactly, 0);
xpath.add_condition(cond);
Ok(())
}
NthType::OnlyOfType => {
let nodetest = xpath.same_type_nodetest().ok_or_else(|| {
Error::unsupported("`:only-of-type` on the universal selector `*`")
})?;
let mut cond = String::new();
for (i, last) in [false, true].into_iter().enumerate() {
if i > 0 {
cond.push_str(" and ");
}
let siblings = Siblings::new(last, &nodetest, None);
push_sibling_count(&mut cond, siblings, Bound::Exactly, 0);
}
xpath.add_condition(cond);
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(Condition::join_or);
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 {
if let Some(check) = current_element_check {
xpath.push_condition(check);
}
xpath.add_condition("0");
return Ok(());
}
let siblings = Siblings::new(
last,
nodetest,
current_element_check.as_ref().map(|c| c.expr.as_str()),
);
let mut cond = String::with_capacity(2 * siblings.len() + 96);
if a == 0 {
push_sibling_count(&mut cond, siblings, Bound::Exactly, b_min_1);
} else {
if a > 0 {
if b_min_1 >= a {
push_sibling_count(&mut cond, siblings, Bound::AtLeast, b_min_1);
}
} else {
push_sibling_count(&mut cond, siblings, Bound::AtMost, b_min_1);
}
if a.abs() != 1 {
if !cond.is_empty() {
cond.push_str(" and ");
}
let b_neg = (-b_min_1).rem_euclid(a.abs());
let written = if b_neg == 0 {
write!(cond, "count({siblings}) mod {a} = 0")
} else {
write!(cond, "(count({siblings}) + {b_neg}) mod {a} = 0")
};
written.expect("writing to a String cannot fail");
}
}
if let Some(check) = current_element_check {
xpath.push_condition(check);
}
if !cond.is_empty() {
xpath.add_condition(cond);
}
Ok(())
}
}
#[derive(Clone, Copy)]
enum Bound {
Exactly,
AtLeast,
AtMost,
}
#[derive(Clone, Copy)]
struct Siblings<'a> {
axis: &'static str,
nodetest: &'a str,
filter: Option<&'a str>,
}
impl<'a> Siblings<'a> {
const fn new(last: bool, nodetest: &'a str, filter: Option<&'a str>) -> Self {
let axis = if last {
"following-sibling::"
} else {
"preceding-sibling::"
};
Self {
axis,
nodetest,
filter,
}
}
fn len(&self) -> usize {
self.axis.len() + self.nodetest.len() + self.filter.map_or(0, |f| f.len() + 2)
}
}
impl fmt::Display for Siblings<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.axis)?;
f.write_str(self.nodetest)?;
if let Some(filter) = self.filter {
f.write_str("[")?;
f.write_str(filter)?;
f.write_str("]")?;
}
Ok(())
}
}
fn push_sibling_count(out: &mut String, siblings: impl fmt::Display, bound: Bound, k: i64) {
debug_assert!(k >= 0, "a sibling count is never negative");
let written = match bound {
Bound::Exactly if k == 0 => write!(out, "not({siblings}[1])"),
Bound::Exactly => write!(out, "{siblings}[{k}] and not({siblings}[{}])", k + 1),
Bound::AtLeast => {
debug_assert!(k > 0, "at least zero siblings is no test at all");
write!(out, "{siblings}[{k}]")
}
Bound::AtMost => write!(out, "not({siblings}[{}])", k + 1),
};
written.expect("writing to a String cannot fail");
}