use std::convert::TryFrom;
use std::fmt;
pub use cssparser::ToCss;
use html5ever::{LocalName, Namespace};
use precomputed_hash::PrecomputedHash;
use selectors::{
matching,
parser::{self, ParseRelative, SelectorList, SelectorParseErrorKind},
};
#[cfg(feature = "serde")]
use serde::{de::Visitor, Deserialize, Serialize};
use crate::error::SelectorErrorKind;
use crate::ElementRef;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Selector {
selectors: SelectorList<Simple>,
}
impl Selector {
pub fn parse(selectors: &'_ str) -> Result<Self, SelectorErrorKind> {
let mut parser_input = cssparser::ParserInput::new(selectors);
let mut parser = cssparser::Parser::new(&mut parser_input);
SelectorList::parse(&Parser, &mut parser, ParseRelative::No)
.map(|selectors| Self { selectors })
.map_err(SelectorErrorKind::from)
}
pub fn matches(&self, element: &ElementRef) -> bool {
self.matches_with_scope(element, None)
}
pub fn matches_with_scope(&self, element: &ElementRef, scope: Option<ElementRef>) -> bool {
self.matches_with_scope_and_cache(element, scope, &mut Default::default())
}
pub(crate) fn matches_with_scope_and_cache(
&self,
element: &ElementRef,
scope: Option<ElementRef>,
caches: &mut matching::SelectorCaches,
) -> bool {
let mut context = matching::MatchingContext::new(
matching::MatchingMode::Normal,
None,
caches,
matching::QuirksMode::NoQuirks,
matching::NeedsSelectorFlags::No,
matching::MatchingForInvalidation::No,
);
context.scope_element = scope.map(|x| selectors::Element::opaque(&x));
self.selectors
.slice()
.iter()
.any(|s| matching::matches_selector(s, 0, None, element, &mut context))
}
}
impl ToCss for Selector {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
self.selectors.to_css(dest)
}
}
#[cfg(feature = "serde")]
impl Serialize for Selector {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_css_string())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Selector {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_str(SelectorVisitor)
}
}
#[cfg(feature = "serde")]
struct SelectorVisitor;
#[cfg(feature = "serde")]
impl Visitor<'_> for SelectorVisitor {
type Value = Selector;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a css selector string")
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
Selector::parse(v).map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Copy, Debug)]
pub struct Parser;
impl<'i> parser::Parser<'i> for Parser {
type Impl = Simple;
type Error = SelectorParseErrorKind<'i>;
fn parse_is_and_where(&self) -> bool {
true
}
fn parse_has(&self) -> bool {
true
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Simple;
impl parser::SelectorImpl for Simple {
type AttrValue = CssString;
type Identifier = CssLocalName;
type LocalName = CssLocalName;
type NamespacePrefix = CssLocalName;
type NamespaceUrl = Namespace;
type BorrowedNamespaceUrl = Namespace;
type BorrowedLocalName = CssLocalName;
type NonTSPseudoClass = NonTSPseudoClass;
type PseudoElement = PseudoElement;
type ExtraMatchingData<'a> = ();
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CssString(pub String);
impl<'a> From<&'a str> for CssString {
fn from(val: &'a str) -> Self {
Self(val.to_owned())
}
}
impl AsRef<str> for CssString {
fn as_ref(&self) -> &str {
&self.0
}
}
impl ToCss for CssString {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
cssparser::serialize_string(&self.0, dest)
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct CssLocalName(pub LocalName);
impl<'a> From<&'a str> for CssLocalName {
fn from(val: &'a str) -> Self {
Self(val.into())
}
}
impl ToCss for CssLocalName {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
dest.write_str(&self.0)
}
}
impl PrecomputedHash for CssLocalName {
fn precomputed_hash(&self) -> u32 {
self.0.precomputed_hash()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NonTSPseudoClass {}
impl parser::NonTSPseudoClass for NonTSPseudoClass {
type Impl = Simple;
fn is_active_or_hover(&self) -> bool {
false
}
fn is_user_action_state(&self) -> bool {
false
}
}
impl ToCss for NonTSPseudoClass {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
dest.write_str("")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PseudoElement {}
impl parser::PseudoElement for PseudoElement {
type Impl = Simple;
}
impl ToCss for PseudoElement {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
dest.write_str("")
}
}
impl<'i> TryFrom<&'i str> for Selector {
type Error = SelectorErrorKind<'i>;
fn try_from(s: &'i str) -> Result<Self, Self::Error> {
Selector::parse(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryInto;
#[test]
fn selector_conversions() {
let s = "#testid.testclass";
let _sel: Selector = s.try_into().unwrap();
let s = s.to_owned();
let _sel: Selector = (*s).try_into().unwrap();
}
#[test]
#[should_panic]
fn invalid_selector_conversions() {
let s = "<failing selector>";
let _sel: Selector = s.try_into().unwrap();
}
#[test]
fn has_selector() {
let s = ":has(a)";
let _sel: Selector = s.try_into().unwrap();
}
#[test]
fn is_selector() {
let s = ":is(a)";
let _sel: Selector = s.try_into().unwrap();
}
#[test]
fn where_selector() {
let s = ":where(a)";
let _sel: Selector = s.try_into().unwrap();
}
}