use super::{CssSelectorSet, Opt, SelectorSet};
use crate::output::CssBuf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Pseudo {
name: String,
arg: Arg,
element: bool,
}
impl Pseudo {
pub(crate) fn no_placeholder(&self) -> Opt<Self> {
let arg = match &self.arg {
Arg::Selector(s) => {
match (s.no_placeholder(), self.name_in(&["not"])) {
(Opt::Some(t), _) => {
if self.name_in(&["is"]) {
match t.no_leading_combinator() {
Opt::Some(t) => Arg::Selector(t),
Opt::Any => return Opt::Any,
Opt::None => return Opt::None,
}
} else {
Arg::Selector(t)
}
}
(Opt::Any, false) | (Opt::None, true) => return Opt::Any,
(Opt::None, false) | (Opt::Any, true) => {
return Opt::None
}
}
}
arg => arg.clone(),
};
Opt::Some(Self {
arg,
name: self.name.clone(),
element: self.element,
})
}
pub(crate) fn is_superselector(&self, b: &Self) -> bool {
if self.is_element() != b.is_element() || self.name != b.name {
return false;
}
if self.name_in(&["not"]) {
b.arg.is_superselector(&self.arg) } else if self.name_in(&["current"]) {
self.arg == b.arg
} else {
self.arg.is_superselector(&b.arg)
}
}
pub(super) fn is_element(&self) -> bool {
self.element || is_pseudo_element(&self.name)
}
pub(super) fn is_hover(&self) -> bool {
self.name == "hover"
}
pub(super) fn is_host(&self) -> bool {
self.name_in(&["host", "host-context"])
}
pub(super) fn is_rootish(&self) -> bool {
self.name_in(&["host", "host-context", "root", "scope"])
}
pub(super) fn has_backref(&self) -> bool {
if let Arg::Selector(s) = &self.arg {
s.has_backref()
} else {
false
}
}
pub(super) fn resolve_ref(mut self, ctx: &CssSelectorSet) -> Self {
self.arg = match self.arg {
Arg::Selector(s) => Arg::Selector(s.resolve_ref(ctx)),
x => x,
};
self
}
pub(super) fn replace(
mut self,
original: &SelectorSet,
replacement: &SelectorSet,
) -> Self {
if self.name_in(&[
"is",
"matches",
"not",
"any",
"where",
"has",
"host",
"host-context",
]) {
self.arg = match self.arg {
Arg::Selector(s) => {
Arg::Selector(s.replace(original, replacement).unwrap())
}
x => x,
};
}
self
}
pub(super) fn write_to(&self, buf: &mut CssBuf) {
buf.add_char(':');
if self.element {
buf.add_char(':');
}
buf.add_str(&self.name);
if self.name_in(&[
"nth-child",
"nth-last-child",
"nth-last-of-type",
"nth-of-type",
]) {
let mut t = CssBuf::new(buf.format());
self.arg.write_to(&mut t);
buf.add_str(
&String::from_utf8_lossy(&t.take()).replacen(" + ", "+", 1),
);
} else {
self.arg.write_to(buf);
}
}
fn name_in(&self, names: &[&str]) -> bool {
name_in(&self.name, names)
}
}
fn is_pseudo_element(n: &str) -> bool {
let pse = [
"after",
"before",
"file-selector-button",
"first-letter",
"first-line",
"grammar-error",
"marker",
"placeholder",
"selection",
"spelling-error",
"target-text",
];
pse.binary_search(&n).is_ok()
}
fn name_in(name: &str, known: &[&str]) -> bool {
if name.starts_with('-') {
known.iter().any(|end| {
name.strip_suffix(end).map_or(false, |s| s.ends_with('-'))
})
} else {
known.iter().any(|known| name == *known)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Arg {
Selector(SelectorSet),
Other(String),
None,
}
impl Arg {
fn is_superselector(&self, b: &Self) -> bool {
match (self, b) {
(Self::Selector(a), Self::Selector(b)) => a.is_superselector(b),
(Self::Other(a), Self::Other(b)) => a == b,
(Self::None, Self::None) => true,
_ => false,
}
}
pub(super) fn write_to(&self, buf: &mut CssBuf) {
match self {
Self::Selector(s) => {
buf.add_char('(');
s.write_to(buf);
buf.add_char(')');
}
Self::Other(a) => {
buf.add_char('(');
buf.add_str(a);
buf.add_char(')');
}
Self::None => (),
}
}
}
pub(super) mod parser {
use super::super::parser::selector_set;
use super::{Arg, Pseudo};
use crate::parser::css::strings::{
css_string_nohash, custom_value_inner,
};
use crate::parser::{PResult, Span};
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::combinator::{map, value};
use nom::sequence::delimited;
use nom::Parser as _;
pub(crate) fn pseudo(input: Span) -> PResult<Pseudo> {
map(
(
alt((value(true, tag("::")), value(false, tag(":")))),
css_string_nohash,
alt((
map(
delimited(tag("("), selector_set, tag(")")),
Arg::Selector,
),
map(
delimited(tag("("), custom_value_inner, tag(")")),
Arg::Other,
),
map(tag(""), |_| Arg::None),
)),
),
|(element, name, arg)| Pseudo { name, arg, element },
)
.parse(input)
}
}