use selectors::parser::{NthSelectorData, NthType, Selector};
use super::Translator;
use super::error::Error;
use super::xpath_expr::{Condition, XPathExpr};
use crate::parser::CssToXpathImpl;
impl Translator {
pub(crate) fn apply_nth(
&self,
xpath: &mut XPathExpr,
data: &NthSelectorData,
selector_list: Option<&[Selector<CssToXpathImpl>]>,
) -> Result<(), Error> {
let a = data.an_plus_b.0;
let b = data.an_plus_b.1;
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 `*`".into())
})?;
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,
"*",
selector_list,
),
NthType::OfType | NthType::LastOfType => {
let nodetest = xpath.same_type_nodetest().ok_or_else(|| {
Error::Unsupported(
"an of-type pseudo-class on the universal selector `*`".into(),
)
})?;
self.xpath_nth_child(
xpath,
a,
b,
data.ty == NthType::LastOfType,
&nodetest,
selector_list,
)
}
}
}
fn xpath_nth_child(
&self,
xpath: &mut XPathExpr,
a: i32,
b: i32,
last: bool,
nodetest: &str,
selector_list: Option<&[Selector<CssToXpathImpl>]>,
) -> Result<(), Error> {
let a = i64::from(a);
let b = i64::from(b);
let b_min_1 = b - 1;
let current_element_check = match selector_list {
Some(list) => self
.arg_conditions(list, ":nth-child(... of S)")?
.filter(|conditions| !conditions.is_empty())
.map(|conditions| Condition::join_or(&conditions)),
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(())
}
}