servo/components/selectors/parser.rs
2017-02-07 23:01:06 -08:00

1473 lines
54 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use cssparser::{Token, Parser as CssParser, parse_nth, ToCss, serialize_identifier, CssStringWriter};
use std::ascii::AsciiExt;
use std::borrow::{Borrow, Cow};
use std::cmp;
use std::fmt::{self, Display, Debug, Write};
use std::hash::Hash;
use std::ops::Add;
use std::sync::Arc;
use tree::SELECTOR_WHITESPACE;
macro_rules! with_all_bounds {
(
[ $( $InSelector: tt )* ]
[ $( $CommonBounds: tt )* ]
[ $( $FromStr: tt )* ]
) => {
fn from_cow_str<T>(cow: Cow<str>) -> T where T: $($FromStr)* {
match cow {
Cow::Borrowed(s) => T::from(s),
Cow::Owned(s) => T::from(s),
}
}
fn from_ascii_lowercase<T>(s: &str) -> T where T: $($FromStr)* {
if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') {
let mut string = s.to_owned();
string[first_uppercase..].make_ascii_lowercase();
T::from(string)
} else {
T::from(s)
}
}
/// This trait allows to define the parser implementation in regards
/// of pseudo-classes/elements
pub trait SelectorImpl: Sized {
type AttrValue: $($InSelector)*;
type Identifier: $($InSelector)*;
type ClassName: $($InSelector)*;
type LocalName: $($InSelector)* + Borrow<Self::BorrowedLocalName>;
type NamespaceUrl: $($CommonBounds)* + Default + Borrow<Self::BorrowedNamespaceUrl>;
type NamespacePrefix: $($InSelector)* + Default;
type BorrowedNamespaceUrl: ?Sized + Eq;
type BorrowedLocalName: ?Sized + Eq + Hash;
/// non tree-structural pseudo-classes
/// (see: https://drafts.csswg.org/selectors/#structural-pseudos)
type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss;
/// pseudo-elements
type PseudoElement: $($CommonBounds)* + Sized + ToCss;
/// Declares if the following "attribute exists" selector is considered
/// "common" enough to be shareable. If that's not the case, when matching
/// over an element, the relation
/// AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE would be set.
fn attr_exists_selector_is_shareable(_attr_selector: &AttrSelector<Self>) -> bool {
false
}
/// Declares if the following "equals" attribute selector is considered
/// "common" enough to be shareable.
fn attr_equals_selector_is_shareable(_attr_selector: &AttrSelector<Self>,
_value: &Self::AttrValue)
-> bool {
false
}
}
}
}
macro_rules! with_bounds {
( [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ]) => {
with_all_bounds! {
[$($CommonBounds)* + $($FromStr)* + Display]
[$($CommonBounds)*]
[$($FromStr)*]
}
}
}
with_bounds! {
[Clone + Eq + Hash]
[From<String> + for<'a> From<&'a str>]
}
pub trait Parser {
type Impl: SelectorImpl;
/// This function can return an "Err" pseudo-element in order to support CSS2.1
/// pseudo-elements.
fn parse_non_ts_pseudo_class(&self, _name: Cow<str>)
-> Result<<Self::Impl as SelectorImpl>::NonTSPseudoClass, ()> {
Err(())
}
fn parse_non_ts_functional_pseudo_class
(&self, _name: Cow<str>, _arguments: &mut CssParser)
-> Result<<Self::Impl as SelectorImpl>::NonTSPseudoClass, ()>
{
Err(())
}
fn parse_pseudo_element(&self, _name: Cow<str>)
-> Result<<Self::Impl as SelectorImpl>::PseudoElement, ()> {
Err(())
}
fn default_namespace(&self) -> Option<<Self::Impl as SelectorImpl>::NamespaceUrl> {
None
}
fn namespace_for_prefix(&self, _prefix: &<Self::Impl as SelectorImpl>::NamespacePrefix)
-> Option<<Self::Impl as SelectorImpl>::NamespaceUrl> {
None
}
}
#[derive(PartialEq, Clone, Debug)]
pub struct SelectorList<Impl: SelectorImpl>(pub Vec<Selector<Impl>>);
impl<Impl: SelectorImpl> SelectorList<Impl> {
/// Parse a comma-separated list of Selectors.
/// https://drafts.csswg.org/selectors/#grouping
///
/// Return the Selectors or Err if there is an invalid selector.
pub fn parse<P>(parser: &P, input: &mut CssParser) -> Result<Self, ()>
where P: Parser<Impl=Impl> {
input.parse_comma_separated(|input| parse_selector(parser, input))
.map(SelectorList)
}
}
#[derive(PartialEq, Clone)]
pub struct Selector<Impl: SelectorImpl> {
pub complex_selector: Arc<ComplexSelector<Impl>>,
pub pseudo_element: Option<Impl::PseudoElement>,
pub specificity: u32,
}
fn affects_sibling<Impl: SelectorImpl>(simple_selector: &SimpleSelector<Impl>) -> bool {
match *simple_selector {
SimpleSelector::Negation(ref negated) => {
negated.iter().any(|ref selector| selector.affects_siblings())
}
SimpleSelector::FirstChild |
SimpleSelector::LastChild |
SimpleSelector::OnlyChild |
SimpleSelector::NthChild(..) |
SimpleSelector::NthLastChild(..) |
SimpleSelector::NthOfType(..) |
SimpleSelector::NthLastOfType(..) |
SimpleSelector::FirstOfType |
SimpleSelector::LastOfType |
SimpleSelector::OnlyOfType => true,
_ => false,
}
}
fn matches_non_common_style_affecting_attribute<Impl: SelectorImpl>(simple_selector: &SimpleSelector<Impl>) -> bool {
match *simple_selector {
SimpleSelector::Negation(ref negated) => {
negated.iter().any(|ref selector| selector.matches_non_common_style_affecting_attribute())
}
SimpleSelector::AttrEqual(ref attr, ref val, _) => {
!Impl::attr_equals_selector_is_shareable(attr, val)
}
SimpleSelector::AttrExists(ref attr) => {
!Impl::attr_exists_selector_is_shareable(attr)
}
SimpleSelector::AttrIncludes(..) |
SimpleSelector::AttrDashMatch(..) |
SimpleSelector::AttrPrefixMatch(..) |
SimpleSelector::AttrSuffixMatch(..) |
SimpleSelector::AttrSubstringMatch(..) => true,
// This deliberately includes Attr*NeverMatch
// which never match regardless of element attributes.
_ => false,
}
}
impl<Impl: SelectorImpl> Selector<Impl> {
/// Whether this selector, if matching on a set of siblings, could affect
/// other sibling's style.
pub fn affects_siblings(&self) -> bool {
self.complex_selector.affects_siblings()
}
pub fn matches_non_common_style_affecting_attribute(&self) -> bool {
self.complex_selector.matches_non_common_style_affecting_attribute()
}
}
impl<Impl: SelectorImpl> ComplexSelector<Impl> {
/// Whether this complex selector, if matching on a set of siblings,
/// could affect other sibling's style.
pub fn affects_siblings(&self) -> bool {
match self.next {
Some((_, Combinator::NextSibling)) |
Some((_, Combinator::LaterSibling)) => return true,
_ => {},
}
match self.compound_selector.last() {
Some(ref selector) => affects_sibling(selector),
None => false,
}
}
pub fn matches_non_common_style_affecting_attribute(&self) -> bool {
match self.compound_selector.last() {
Some(ref selector) => matches_non_common_style_affecting_attribute(selector),
None => false,
}
}
}
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct ComplexSelector<Impl: SelectorImpl> {
pub compound_selector: Vec<SimpleSelector<Impl>>,
pub next: Option<(Arc<ComplexSelector<Impl>>, Combinator)>, // c.next is left of c
}
#[derive(Eq, PartialEq, Clone, Copy, Debug, Hash)]
pub enum Combinator {
Child, // >
Descendant, // space
NextSibling, // +
LaterSibling, // ~
}
#[derive(Eq, PartialEq, Clone, Hash)]
pub enum SimpleSelector<Impl: SelectorImpl> {
ID(Impl::Identifier),
Class(Impl::ClassName),
LocalName(LocalName<Impl>),
Namespace(Namespace<Impl>),
// Attribute selectors
AttrExists(AttrSelector<Impl>), // [foo]
AttrEqual(AttrSelector<Impl>, Impl::AttrValue, CaseSensitivity), // [foo=bar]
AttrIncludes(AttrSelector<Impl>, Impl::AttrValue), // [foo~=bar]
AttrDashMatch(AttrSelector<Impl>, Impl::AttrValue), // [foo|=bar]
AttrPrefixMatch(AttrSelector<Impl>, Impl::AttrValue), // [foo^=bar]
AttrSubstringMatch(AttrSelector<Impl>, Impl::AttrValue), // [foo*=bar]
AttrSuffixMatch(AttrSelector<Impl>, Impl::AttrValue), // [foo$=bar]
AttrIncludesNeverMatch(AttrSelector<Impl>, Impl::AttrValue), // empty value or with whitespace
AttrPrefixNeverMatch(AttrSelector<Impl>, Impl::AttrValue), // empty value
AttrSubstringNeverMatch(AttrSelector<Impl>, Impl::AttrValue), // empty value
AttrSuffixNeverMatch(AttrSelector<Impl>, Impl::AttrValue), // empty value
// Pseudo-classes
Negation(Vec<Arc<ComplexSelector<Impl>>>),
FirstChild, LastChild, OnlyChild,
Root,
Empty,
NthChild(i32, i32),
NthLastChild(i32, i32),
NthOfType(i32, i32),
NthLastOfType(i32, i32),
FirstOfType,
LastOfType,
OnlyOfType,
NonTSPseudoClass(Impl::NonTSPseudoClass),
// ...
}
#[derive(Eq, PartialEq, Clone, Hash, Copy, Debug)]
pub enum CaseSensitivity {
CaseSensitive, // Selectors spec says language-defined, but HTML says sensitive.
CaseInsensitive,
}
#[derive(Eq, PartialEq, Clone, Hash)]
pub struct LocalName<Impl: SelectorImpl> {
pub name: Impl::LocalName,
pub lower_name: Impl::LocalName,
}
#[derive(Eq, PartialEq, Clone, Hash)]
pub struct AttrSelector<Impl: SelectorImpl> {
pub name: Impl::LocalName,
pub lower_name: Impl::LocalName,
pub namespace: NamespaceConstraint<Impl>,
}
#[derive(Eq, PartialEq, Clone, Hash, Debug)]
pub enum NamespaceConstraint<Impl: SelectorImpl> {
Any,
Specific(Namespace<Impl>),
}
/// FIXME(SimonSapin): should Hash only hash the URL? What is it used for?
#[derive(Eq, PartialEq, Clone, Hash)]
pub struct Namespace<Impl: SelectorImpl> {
pub prefix: Option<Impl::NamespacePrefix>,
pub url: Impl::NamespaceUrl,
}
impl<Impl: SelectorImpl> Default for Namespace<Impl> {
fn default() -> Self {
Namespace {
prefix: None,
url: Impl::NamespaceUrl::default(), // empty string
}
}
}
impl<Impl: SelectorImpl> Debug for Selector<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Selector(")?;
self.to_css(f)?;
write!(f, ", specificity = 0x{:x})", self.specificity)
}
}
impl<Impl: SelectorImpl> Debug for ComplexSelector<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
}
impl<Impl: SelectorImpl> Debug for SimpleSelector<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
}
impl<Impl: SelectorImpl> Debug for AttrSelector<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
}
impl<Impl: SelectorImpl> Debug for Namespace<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
}
impl<Impl: SelectorImpl> Debug for LocalName<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
}
impl<Impl: SelectorImpl> ToCss for SelectorList<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
let mut iter = self.0.iter();
let first = iter.next()
.expect("Empty SelectorList, should contain at least one selector");
first.to_css(dest)?;
for selector in iter {
dest.write_str(", ")?;
selector.to_css(dest)?;
}
Ok(())
}
}
impl<Impl: SelectorImpl> ToCss for Selector<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
self.complex_selector.to_css(dest)?;
if let Some(ref pseudo) = self.pseudo_element {
pseudo.to_css(dest)?;
}
Ok(())
}
}
impl<Impl: SelectorImpl> ToCss for ComplexSelector<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
if let Some((ref next, ref combinator)) = self.next {
next.to_css(dest)?;
combinator.to_css(dest)?;
}
for simple in &self.compound_selector {
simple.to_css(dest)?;
}
Ok(())
}
}
impl ToCss for Combinator {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
Combinator::Child => dest.write_str(" > "),
Combinator::Descendant => dest.write_str(" "),
Combinator::NextSibling => dest.write_str(" + "),
Combinator::LaterSibling => dest.write_str(" ~ "),
}
}
}
impl<Impl: SelectorImpl> ToCss for SimpleSelector<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
use self::SimpleSelector::*;
match *self {
ID(ref s) => {
dest.write_char('#')?;
display_to_css_identifier(s, dest)
}
Class(ref s) => {
dest.write_char('.')?;
display_to_css_identifier(s, dest)
}
LocalName(ref s) => s.to_css(dest),
Namespace(ref ns) => ns.to_css(dest),
// Attribute selectors
AttrExists(ref a) => {
dest.write_char('[')?;
a.to_css(dest)?;
dest.write_char(']')
}
AttrEqual(ref a, ref v, case) => {
attr_selector_to_css(a, " = ", v, match case {
CaseSensitivity::CaseSensitive => None,
CaseSensitivity::CaseInsensitive => Some(" i"),
}, dest)
}
AttrDashMatch(ref a, ref v) => attr_selector_to_css(a, " |= ", v, None, dest),
AttrIncludesNeverMatch(ref a, ref v) |
AttrIncludes(ref a, ref v) => attr_selector_to_css(a, " ~= ", v, None, dest),
AttrPrefixNeverMatch(ref a, ref v) |
AttrPrefixMatch(ref a, ref v) => attr_selector_to_css(a, " ^= ", v, None, dest),
AttrSubstringNeverMatch(ref a, ref v) |
AttrSubstringMatch(ref a, ref v) => attr_selector_to_css(a, " *= ", v, None, dest),
AttrSuffixNeverMatch(ref a, ref v) |
AttrSuffixMatch(ref a, ref v) => attr_selector_to_css(a, " $= ", v, None, dest),
// Pseudo-classes
Negation(ref args) => {
dest.write_str(":not(")?;
let mut args = args.iter();
let first = args.next().unwrap();
first.to_css(dest)?;
for arg in args {
dest.write_str(", ")?;
arg.to_css(dest)?;
}
dest.write_str(")")
}
FirstChild => dest.write_str(":first-child"),
LastChild => dest.write_str(":last-child"),
OnlyChild => dest.write_str(":only-child"),
Root => dest.write_str(":root"),
Empty => dest.write_str(":empty"),
FirstOfType => dest.write_str(":first-of-type"),
LastOfType => dest.write_str(":last-of-type"),
OnlyOfType => dest.write_str(":only-of-type"),
NthChild(a, b) => write!(dest, ":nth-child({}n{:+})", a, b),
NthLastChild(a, b) => write!(dest, ":nth-last-child({}n{:+})", a, b),
NthOfType(a, b) => write!(dest, ":nth-of-type({}n{:+})", a, b),
NthLastOfType(a, b) => write!(dest, ":nth-last-of-type({}n{:+})", a, b),
NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest),
}
}
}
impl<Impl: SelectorImpl> ToCss for AttrSelector<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
if let NamespaceConstraint::Specific(ref ns) = self.namespace {
ns.to_css(dest)?;
}
display_to_css_identifier(&self.name, dest)
}
}
impl<Impl: SelectorImpl> ToCss for Namespace<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
if let Some(ref prefix) = self.prefix {
display_to_css_identifier(prefix, dest)?;
dest.write_char('|')?;
}
Ok(())
}
}
impl<Impl: SelectorImpl> ToCss for LocalName<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
display_to_css_identifier(&self.name, dest)
}
}
fn attr_selector_to_css<Impl, W>(attr: &AttrSelector<Impl>,
operator: &str,
value: &Impl::AttrValue,
modifier: Option<&str>,
dest: &mut W)
-> fmt::Result
where Impl: SelectorImpl, W: fmt::Write
{
dest.write_char('[')?;
attr.to_css(dest)?;
dest.write_str(operator)?;
dest.write_char('"')?;
write!(CssStringWriter::new(dest), "{}", value)?;
dest.write_char('"')?;
if let Some(m) = modifier {
dest.write_str(m)?;
}
dest.write_char(']')
}
/// Serialize the output of Display as a CSS identifier
fn display_to_css_identifier<T: Display, W: fmt::Write>(x: &T, dest: &mut W) -> fmt::Result {
// FIXME(SimonSapin): it is possible to avoid this heap allocation
// by creating a stream adapter like cssparser::CssStringWriter
// that holds and writes to `&mut W` and itself implements `fmt::Write`.
//
// I havent done this yet because it would require somewhat complex and fragile state machine
// to support in `fmt::Write::write_char` cases that,
// in `serialize_identifier` (which has the full value as a `&str` slice),
// can be expressed as
// `string.starts_with("--")`, `string == "-"`, `string.starts_with("-")`, etc.
//
// And I dont even know if this would be a performance win: jemalloc is good at what it does
// and the state machine might be slower than `serialize_identifier` as currently written.
let string = x.to_string();
serialize_identifier(&string, dest)
}
const MAX_10BIT: u32 = (1u32 << 10) - 1;
#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
struct Specificity {
id_selectors: u32,
class_like_selectors: u32,
element_selectors: u32,
}
impl Add for Specificity {
type Output = Specificity;
fn add(self, rhs: Specificity) -> Specificity {
Specificity {
id_selectors: self.id_selectors + rhs.id_selectors,
class_like_selectors:
self.class_like_selectors + rhs.class_like_selectors,
element_selectors:
self.element_selectors + rhs.element_selectors,
}
}
}
impl Default for Specificity {
fn default() -> Specificity {
Specificity {
id_selectors: 0,
class_like_selectors: 0,
element_selectors: 0,
}
}
}
impl From<u32> for Specificity {
fn from(value: u32) -> Specificity {
assert!(value <= MAX_10BIT << 20 | MAX_10BIT << 10 | MAX_10BIT);
Specificity {
id_selectors: value >> 20,
class_like_selectors: (value >> 10) & MAX_10BIT,
element_selectors: value & MAX_10BIT,
}
}
}
impl From<Specificity> for u32 {
fn from(specificity: Specificity) -> u32 {
cmp::min(specificity.id_selectors, MAX_10BIT) << 20
| cmp::min(specificity.class_like_selectors, MAX_10BIT) << 10
| cmp::min(specificity.element_selectors, MAX_10BIT)
}
}
fn specificity<Impl>(complex_selector: &ComplexSelector<Impl>,
pseudo_element: Option<&Impl::PseudoElement>)
-> u32
where Impl: SelectorImpl {
let mut specificity = complex_selector_specificity(complex_selector);
if pseudo_element.is_some() {
specificity.element_selectors += 1;
}
specificity.into()
}
fn complex_selector_specificity<Impl>(mut selector: &ComplexSelector<Impl>)
-> Specificity
where Impl: SelectorImpl {
fn compound_selector_specificity<Impl>(compound_selector: &[SimpleSelector<Impl>],
specificity: &mut Specificity)
where Impl: SelectorImpl {
for simple_selector in compound_selector.iter() {
match *simple_selector {
SimpleSelector::LocalName(..) =>
specificity.element_selectors += 1,
SimpleSelector::ID(..) =>
specificity.id_selectors += 1,
SimpleSelector::Class(..) |
SimpleSelector::AttrExists(..) |
SimpleSelector::AttrEqual(..) |
SimpleSelector::AttrIncludes(..) |
SimpleSelector::AttrDashMatch(..) |
SimpleSelector::AttrPrefixMatch(..) |
SimpleSelector::AttrSubstringMatch(..) |
SimpleSelector::AttrSuffixMatch(..) |
SimpleSelector::AttrIncludesNeverMatch(..) |
SimpleSelector::AttrPrefixNeverMatch(..) |
SimpleSelector::AttrSubstringNeverMatch(..) |
SimpleSelector::AttrSuffixNeverMatch(..) |
SimpleSelector::FirstChild | SimpleSelector::LastChild |
SimpleSelector::OnlyChild | SimpleSelector::Root |
SimpleSelector::Empty |
SimpleSelector::NthChild(..) |
SimpleSelector::NthLastChild(..) |
SimpleSelector::NthOfType(..) |
SimpleSelector::NthLastOfType(..) |
SimpleSelector::FirstOfType | SimpleSelector::LastOfType |
SimpleSelector::OnlyOfType |
SimpleSelector::NonTSPseudoClass(..) =>
specificity.class_like_selectors += 1,
SimpleSelector::Namespace(..) => (),
SimpleSelector::Negation(ref negated) => {
let max =
negated.iter().map(|s| complex_selector_specificity(&s))
.max().unwrap();
*specificity = *specificity + max;
}
}
}
}
let mut specificity = Default::default();
compound_selector_specificity(&selector.compound_selector,
&mut specificity);
loop {
match selector.next {
None => break,
Some((ref next_selector, _)) => {
selector = &**next_selector;
compound_selector_specificity(&selector.compound_selector,
&mut specificity)
}
}
}
specificity
}
/// Build up a Selector.
/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ;
///
/// `Err` means invalid selector.
fn parse_selector<P, Impl>(parser: &P, input: &mut CssParser) -> Result<Selector<Impl>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
let (complex, pseudo_element) =
parse_complex_selector_and_pseudo_element(parser, input)?;
Ok(Selector {
specificity: specificity(&complex, pseudo_element.as_ref()),
complex_selector: Arc::new(complex),
pseudo_element: pseudo_element,
})
}
fn parse_complex_selector_and_pseudo_element<P, Impl>(
parser: &P,
input: &mut CssParser)
-> Result<(ComplexSelector<Impl>, Option<Impl::PseudoElement>), ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
let (first, mut pseudo_element) = parse_compound_selector(parser, input)?;
let mut complex = ComplexSelector { compound_selector: first, next: None };
'outer_loop: while pseudo_element.is_none() {
let combinator;
let mut any_whitespace = false;
loop {
let position = input.position();
match input.next_including_whitespace() {
Err(()) => break 'outer_loop,
Ok(Token::WhiteSpace(_)) => any_whitespace = true,
Ok(Token::Delim('>')) => {
combinator = Combinator::Child;
break
}
Ok(Token::Delim('+')) => {
combinator = Combinator::NextSibling;
break
}
Ok(Token::Delim('~')) => {
combinator = Combinator::LaterSibling;
break
}
Ok(_) => {
input.reset(position);
if any_whitespace {
combinator = Combinator::Descendant;
break
} else {
break 'outer_loop
}
}
}
}
let (compound_selector, pseudo) = parse_compound_selector(parser, input)?;
complex = ComplexSelector {
compound_selector: compound_selector,
next: Some((Arc::new(complex), combinator))
};
pseudo_element = pseudo;
}
Ok((complex, pseudo_element))
}
fn parse_complex_selector<P, Impl>(
parser: &P,
input: &mut CssParser)
-> Result<ComplexSelector<Impl>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
let (complex, pseudo_element) =
parse_complex_selector_and_pseudo_element(parser, input)?;
if pseudo_element.is_some() {
return Err(())
}
Ok(complex)
}
/// * `Err(())`: Invalid selector, abort
/// * `Ok(None)`: Not a type selector, could be something else. `input` was not consumed.
/// * `Ok(Some(vec))`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`)
fn parse_type_selector<P, Impl>(parser: &P, input: &mut CssParser)
-> Result<Option<Vec<SimpleSelector<Impl>>>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
match parse_qualified_name(parser, input, /* in_attr_selector = */ false)? {
None => Ok(None),
Some((namespace, local_name)) => {
let mut compound_selector = vec!();
match namespace {
NamespaceConstraint::Specific(ns) => {
compound_selector.push(SimpleSelector::Namespace(ns))
},
NamespaceConstraint::Any => (),
}
match local_name {
Some(name) => {
compound_selector.push(SimpleSelector::LocalName(LocalName {
lower_name: from_ascii_lowercase(&name),
name: from_cow_str(name),
}))
}
None => (),
}
Ok(Some(compound_selector))
}
}
}
#[derive(Debug)]
enum SimpleSelectorParseResult<Impl: SelectorImpl> {
SimpleSelector(SimpleSelector<Impl>),
PseudoElement(Impl::PseudoElement),
}
/// * `Err(())`: Invalid selector, abort
/// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed.
/// * `Ok(Some((namespace, local_name)))`: `None` for the local name means a `*` universal selector
fn parse_qualified_name<'i, 't, P, Impl>
(parser: &P, input: &mut CssParser<'i, 't>,
in_attr_selector: bool)
-> Result<Option<(NamespaceConstraint<Impl>, Option<Cow<'i, str>>)>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
let default_namespace = |local_name| {
let namespace = match parser.default_namespace() {
Some(url) => NamespaceConstraint::Specific(Namespace {
prefix: None,
url: url
}),
None => NamespaceConstraint::Any,
};
Ok(Some((namespace, local_name)))
};
let explicit_namespace = |input: &mut CssParser<'i, 't>, namespace| {
match input.next_including_whitespace() {
Ok(Token::Delim('*')) if !in_attr_selector => {
Ok(Some((namespace, None)))
},
Ok(Token::Ident(local_name)) => {
Ok(Some((namespace, Some(local_name))))
},
_ => Err(()),
}
};
let position = input.position();
match input.next_including_whitespace() {
Ok(Token::Ident(value)) => {
let position = input.position();
match input.next_including_whitespace() {
Ok(Token::Delim('|')) => {
let prefix = from_cow_str(value);
let result = parser.namespace_for_prefix(&prefix);
let url = result.ok_or(())?;
explicit_namespace(input, NamespaceConstraint::Specific(Namespace {
prefix: Some(prefix),
url: url
}))
},
_ => {
input.reset(position);
if in_attr_selector {
Ok(Some((NamespaceConstraint::Specific(Default::default()), Some(value))))
} else {
default_namespace(Some(value))
}
}
}
},
Ok(Token::Delim('*')) => {
let position = input.position();
match input.next_including_whitespace() {
Ok(Token::Delim('|')) => explicit_namespace(input, NamespaceConstraint::Any),
_ => {
input.reset(position);
if in_attr_selector {
Err(())
} else {
default_namespace(None)
}
},
}
},
Ok(Token::Delim('|')) => {
explicit_namespace(input, NamespaceConstraint::Specific(Default::default()))
}
_ => {
input.reset(position);
Ok(None)
}
}
}
fn parse_attribute_selector<P, Impl>(parser: &P, input: &mut CssParser)
-> Result<SimpleSelector<Impl>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
let attr = match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? {
None => return Err(()),
Some((_, None)) => unreachable!(),
Some((namespace, Some(local_name))) => AttrSelector {
namespace: namespace,
lower_name: from_ascii_lowercase(&local_name),
name: from_cow_str(local_name),
},
};
match input.next() {
// [foo]
Err(()) => Ok(SimpleSelector::AttrExists(attr)),
// [foo=bar]
Ok(Token::Delim('=')) => {
let value = input.expect_ident_or_string()?;
let flags = parse_attribute_flags(input)?;
Ok(SimpleSelector::AttrEqual(attr, from_cow_str(value), flags))
}
// [foo~=bar]
Ok(Token::IncludeMatch) => {
let value = input.expect_ident_or_string()?;
if value.is_empty() || value.contains(SELECTOR_WHITESPACE) {
Ok(SimpleSelector::AttrIncludesNeverMatch(attr, from_cow_str(value)))
} else {
Ok(SimpleSelector::AttrIncludes(attr, from_cow_str(value)))
}
}
// [foo|=bar]
Ok(Token::DashMatch) => {
let value = input.expect_ident_or_string()?;
Ok(SimpleSelector::AttrDashMatch(attr, from_cow_str(value)))
}
// [foo^=bar]
Ok(Token::PrefixMatch) => {
let value = input.expect_ident_or_string()?;
if value.is_empty() {
Ok(SimpleSelector::AttrPrefixNeverMatch(attr, from_cow_str(value)))
} else {
Ok(SimpleSelector::AttrPrefixMatch(attr, from_cow_str(value)))
}
}
// [foo*=bar]
Ok(Token::SubstringMatch) => {
let value = input.expect_ident_or_string()?;
if value.is_empty() {
Ok(SimpleSelector::AttrSubstringNeverMatch(attr, from_cow_str(value)))
} else {
Ok(SimpleSelector::AttrSubstringMatch(attr, from_cow_str(value)))
}
}
// [foo$=bar]
Ok(Token::SuffixMatch) => {
let value = input.expect_ident_or_string()?;
if value.is_empty() {
Ok(SimpleSelector::AttrSuffixNeverMatch(attr, from_cow_str(value)))
} else {
Ok(SimpleSelector::AttrSuffixMatch(attr, from_cow_str(value)))
}
}
_ => Err(())
}
}
fn parse_attribute_flags(input: &mut CssParser) -> Result<CaseSensitivity, ()> {
match input.next() {
Err(()) => Ok(CaseSensitivity::CaseSensitive),
Ok(Token::Ident(ref value)) if value.eq_ignore_ascii_case("i") => {
Ok(CaseSensitivity::CaseInsensitive)
}
_ => Err(())
}
}
/// Level 3: Parse **one** simple_selector. (Though we might insert a second
/// implied "<defaultns>|*" type selector.)
fn parse_negation<P, Impl>(parser: &P,
input: &mut CssParser)
-> Result<SimpleSelector<Impl>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
input.parse_comma_separated(|input| parse_complex_selector(parser, input).map(Arc::new))
.map(SimpleSelector::Negation)
}
/// simple_selector_sequence
/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]*
/// | [ HASH | class | attrib | pseudo | negation ]+
///
/// `Err(())` means invalid selector
fn parse_compound_selector<P, Impl>(
parser: &P,
input: &mut CssParser)
-> Result<(Vec<SimpleSelector<Impl>>, Option<Impl::PseudoElement>), ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
// Consume any leading whitespace.
loop {
let position = input.position();
if !matches!(input.next_including_whitespace(), Ok(Token::WhiteSpace(_))) {
input.reset(position);
break
}
}
let mut empty = true;
let mut compound_selector = match parse_type_selector(parser, input)? {
None => {
match parser.default_namespace() {
// If there was no explicit type selector, but there is a
// default namespace, there is an implicit "<defaultns>|*" type
// selector.
Some(url) => vec![SimpleSelector::Namespace(Namespace {
prefix: None,
url: url
})],
None => vec![],
}
}
Some(s) => { empty = false; s }
};
let mut pseudo_element = None;
loop {
match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? {
None => break,
Some(SimpleSelectorParseResult::SimpleSelector(s)) => {
compound_selector.push(s);
empty = false
}
Some(SimpleSelectorParseResult::PseudoElement(p)) => {
pseudo_element = Some(p);
empty = false;
break
}
}
}
if empty {
// An empty selector is invalid.
Err(())
} else {
Ok((compound_selector, pseudo_element))
}
}
fn parse_functional_pseudo_class<P, Impl>(parser: &P,
input: &mut CssParser,
name: Cow<str>,
inside_negation: bool)
-> Result<SimpleSelector<Impl>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
match_ignore_ascii_case! { &name,
"nth-child" => return parse_nth_pseudo_class(input, SimpleSelector::NthChild),
"nth-of-type" => return parse_nth_pseudo_class(input, SimpleSelector::NthOfType),
"nth-last-child" => return parse_nth_pseudo_class(input, SimpleSelector::NthLastChild),
"nth-last-of-type" => return parse_nth_pseudo_class(input, SimpleSelector::NthLastOfType),
"not" => {
if inside_negation {
return Err(())
} else {
return parse_negation(parser, input)
}
},
_ => {}
}
P::parse_non_ts_functional_pseudo_class(parser, name, input)
.map(SimpleSelector::NonTSPseudoClass)
}
fn parse_nth_pseudo_class<Impl, F>(input: &mut CssParser, selector: F)
-> Result<SimpleSelector<Impl>, ()>
where Impl: SelectorImpl, F: FnOnce(i32, i32) -> SimpleSelector<Impl> {
let (a, b) = parse_nth(input)?;
Ok(selector(a, b))
}
/// Parse a simple selector other than a type selector.
///
/// * `Err(())`: Invalid selector, abort
/// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed.
/// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element
fn parse_one_simple_selector<P, Impl>(parser: &P,
input: &mut CssParser,
inside_negation: bool)
-> Result<Option<SimpleSelectorParseResult<Impl>>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
let start_position = input.position();
match input.next_including_whitespace() {
Ok(Token::IDHash(id)) => {
let id = SimpleSelector::ID(from_cow_str(id));
Ok(Some(SimpleSelectorParseResult::SimpleSelector(id)))
}
Ok(Token::Delim('.')) => {
match input.next_including_whitespace() {
Ok(Token::Ident(class)) => {
let class = SimpleSelector::Class(from_cow_str(class));
Ok(Some(SimpleSelectorParseResult::SimpleSelector(class)))
}
_ => Err(()),
}
}
Ok(Token::SquareBracketBlock) => {
let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?;
Ok(Some(SimpleSelectorParseResult::SimpleSelector(attr)))
}
Ok(Token::Colon) => {
match input.next_including_whitespace() {
Ok(Token::Ident(name)) => {
// Supported CSS 2.1 pseudo-elements only.
// ** Do not add to this list! **
if name.eq_ignore_ascii_case("before") ||
name.eq_ignore_ascii_case("after") ||
name.eq_ignore_ascii_case("first-line") ||
name.eq_ignore_ascii_case("first-letter") {
let pseudo_element = P::parse_pseudo_element(parser, name)?;
Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo_element)))
} else {
let pseudo_class = parse_simple_pseudo_class(parser, name)?;
Ok(Some(SimpleSelectorParseResult::SimpleSelector(pseudo_class)))
}
}
Ok(Token::Function(name)) => {
let pseudo = input.parse_nested_block(|input| {
parse_functional_pseudo_class(parser, input, name, inside_negation)
})?;
Ok(Some(SimpleSelectorParseResult::SimpleSelector(pseudo)))
}
Ok(Token::Colon) => {
match input.next_including_whitespace() {
Ok(Token::Ident(name)) => {
let pseudo = P::parse_pseudo_element(parser, name)?;
Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo)))
}
_ => Err(())
}
}
_ => Err(())
}
}
_ => {
input.reset(start_position);
Ok(None)
}
}
}
fn parse_simple_pseudo_class<P, Impl>(parser: &P, name: Cow<str>) -> Result<SimpleSelector<Impl>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
(match_ignore_ascii_case! { &name,
"first-child" => Ok(SimpleSelector::FirstChild),
"last-child" => Ok(SimpleSelector::LastChild),
"only-child" => Ok(SimpleSelector::OnlyChild),
"root" => Ok(SimpleSelector::Root),
"empty" => Ok(SimpleSelector::Empty),
"first-of-type" => Ok(SimpleSelector::FirstOfType),
"last-of-type" => Ok(SimpleSelector::LastOfType),
"only-of-type" => Ok(SimpleSelector::OnlyOfType),
_ => Err(())
}).or_else(|()| {
P::parse_non_ts_pseudo_class(parser, name).map(|pc| SimpleSelector::NonTSPseudoClass(pc))
})
}
// NB: pub module in order to access the DummyParser
#[cfg(test)]
pub mod tests {
use cssparser::{Parser as CssParser, ToCss, serialize_identifier};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use super::*;
#[derive(PartialEq, Clone, Debug, Hash, Eq)]
pub enum PseudoClass {
Hover,
Lang(String),
}
#[derive(Eq, PartialEq, Clone, Debug, Hash)]
pub enum PseudoElement {
Before,
After,
}
impl ToCss for PseudoClass {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
PseudoClass::Hover => dest.write_str(":hover"),
PseudoClass::Lang(ref lang) => {
dest.write_str(":lang(")?;
serialize_identifier(lang, dest)?;
dest.write_char(')')
}
}
}
}
impl ToCss for PseudoElement {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
PseudoElement::Before => dest.write_str("::before"),
PseudoElement::After => dest.write_str("::after"),
}
}
}
#[derive(PartialEq, Debug)]
pub struct DummySelectorImpl;
#[derive(Default)]
pub struct DummyParser {
default_ns: Option<String>,
ns_prefixes: HashMap<String, String>,
}
impl SelectorImpl for DummySelectorImpl {
type AttrValue = String;
type Identifier = String;
type ClassName = String;
type LocalName = String;
type NamespaceUrl = String;
type NamespacePrefix = String;
type BorrowedLocalName = str;
type BorrowedNamespaceUrl = str;
type NonTSPseudoClass = PseudoClass;
type PseudoElement = PseudoElement;
}
impl Parser for DummyParser {
type Impl = DummySelectorImpl;
fn parse_non_ts_pseudo_class(&self, name: Cow<str>)
-> Result<PseudoClass, ()> {
match_ignore_ascii_case! { &name,
"hover" => Ok(PseudoClass::Hover),
_ => Err(())
}
}
fn parse_non_ts_functional_pseudo_class(&self, name: Cow<str>,
parser: &mut CssParser)
-> Result<PseudoClass, ()> {
match_ignore_ascii_case! { &name,
"lang" => Ok(PseudoClass::Lang(try!(parser.expect_ident_or_string()).into_owned())),
_ => Err(())
}
}
fn parse_pseudo_element(&self, name: Cow<str>)
-> Result<PseudoElement, ()> {
match_ignore_ascii_case! { &name,
"before" => Ok(PseudoElement::Before),
"after" => Ok(PseudoElement::After),
_ => Err(())
}
}
fn default_namespace(&self) -> Option<String> {
self.default_ns.clone()
}
fn namespace_for_prefix(&self, prefix: &str) -> Option<String> {
self.ns_prefixes.get(prefix).cloned()
}
}
fn parse(input: &str) -> Result<SelectorList<DummySelectorImpl>, ()> {
parse_ns(input, &DummyParser::default())
}
fn parse_ns(input: &str, parser: &DummyParser)
-> Result<SelectorList<DummySelectorImpl>, ()> {
let result = SelectorList::parse(parser, &mut CssParser::new(input));
if let Ok(ref selectors) = result {
assert_eq!(selectors.0.len(), 1);
assert_eq!(selectors.0[0].to_css_string(), input);
}
result
}
fn specificity(a: u32, b: u32, c: u32) -> u32 {
a << 20 | b << 10 | c
}
#[test]
fn test_empty() {
let list = SelectorList::parse(&DummyParser::default(), &mut CssParser::new(":empty"));
assert!(list.is_ok());
}
const MATHML: &'static str = "http://www.w3.org/1998/Math/MathML";
const SVG: &'static str = "http://www.w3.org/2000/svg";
#[test]
fn test_parsing() {
assert_eq!(parse(""), Err(())) ;
assert_eq!(parse(":lang(4)"), Err(())) ;
assert_eq!(parse(":lang(en US)"), Err(())) ;
assert_eq!(parse("EeÉ"), Ok(SelectorList(vec!(Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::LocalName(LocalName {
name: String::from("EeÉ"),
lower_name: String::from("eeÉ") })),
next: None,
}),
pseudo_element: None,
specificity: specificity(0, 0, 1),
}))));
assert_eq!(parse(".foo:lang(en-US)"), Ok(SelectorList(vec!(Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec![
SimpleSelector::Class(String::from("foo")),
SimpleSelector::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned()))
],
next: None,
}),
pseudo_element: None,
specificity: specificity(0, 2, 0),
}))));
assert_eq!(parse("#bar"), Ok(SelectorList(vec!(Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::ID(String::from("bar"))),
next: None,
}),
pseudo_element: None,
specificity: specificity(1, 0, 0),
}))));
assert_eq!(parse("e.foo#bar"), Ok(SelectorList(vec!(Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::LocalName(LocalName {
name: String::from("e"),
lower_name: String::from("e") }),
SimpleSelector::Class(String::from("foo")),
SimpleSelector::ID(String::from("bar"))),
next: None,
}),
pseudo_element: None,
specificity: specificity(1, 1, 1),
}))));
assert_eq!(parse("e.foo #bar"), Ok(SelectorList(vec!(Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::ID(String::from("bar"))),
next: Some((Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::LocalName(LocalName {
name: String::from("e"),
lower_name: String::from("e") }),
SimpleSelector::Class(String::from("foo"))),
next: None,
}), Combinator::Descendant)),
}),
pseudo_element: None,
specificity: specificity(1, 1, 1),
}))));
// Default namespace does not apply to attribute selectors
// https://github.com/mozilla/servo/pull/1652
let mut parser = DummyParser::default();
assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList(vec!(Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::AttrExists(AttrSelector {
name: String::from("Foo"),
lower_name: String::from("foo"),
namespace: NamespaceConstraint::Specific(Namespace {
prefix: None,
url: "".into(),
}),
})),
next: None,
}),
pseudo_element: None,
specificity: specificity(0, 1, 0),
}))));
assert_eq!(parse_ns("svg|circle", &parser), Err(()));
parser.ns_prefixes.insert("svg".into(), SVG.into());
assert_eq!(parse_ns("svg|circle", &parser), Ok(SelectorList(vec![Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec![
SimpleSelector::Namespace(Namespace {
prefix: Some("svg".into()),
url: SVG.into(),
}),
SimpleSelector::LocalName(LocalName {
name: String::from("circle"),
lower_name: String::from("circle")
})
],
next: None,
}),
pseudo_element: None,
specificity: specificity(0, 0, 1),
}])));
// Default namespace does not apply to attribute selectors
// https://github.com/mozilla/servo/pull/1652
// but it does apply to implicit type selectors
// https://github.com/servo/rust-selectors/pull/82
parser.default_ns = Some(MATHML.into());
assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList(vec!(Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec![
SimpleSelector::Namespace(Namespace {
prefix: None,
url: MATHML.into(),
}),
SimpleSelector::AttrExists(AttrSelector {
name: String::from("Foo"),
lower_name: String::from("foo"),
namespace: NamespaceConstraint::Specific(Namespace {
prefix: None,
url: "".into(),
}),
}),
],
next: None,
}),
pseudo_element: None,
specificity: specificity(0, 1, 0),
}))));
// Default namespace does apply to type selectors
assert_eq!(parse_ns("e", &parser), Ok(SelectorList(vec!(Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec!(
SimpleSelector::Namespace(Namespace {
prefix: None,
url: MATHML.into(),
}),
SimpleSelector::LocalName(LocalName {
name: String::from("e"),
lower_name: String::from("e") }),
),
next: None,
}),
pseudo_element: None,
specificity: specificity(0, 0, 1),
}))));
assert_eq!(parse("[attr |= \"foo\"]"), Ok(SelectorList(vec![Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec![
SimpleSelector::AttrDashMatch(AttrSelector {
name: String::from("attr"),
lower_name: String::from("attr"),
namespace: NamespaceConstraint::Specific(Namespace {
prefix: None,
url: "".into(),
}),
}, "foo".to_owned())
],
next: None,
}),
pseudo_element: None,
specificity: specificity(0, 1, 0),
}])));
// https://github.com/mozilla/servo/issues/1723
assert_eq!(parse("::before"), Ok(SelectorList(vec!(Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec!(),
next: None,
}),
pseudo_element: Some(PseudoElement::Before),
specificity: specificity(0, 0, 1),
}))));
// https://github.com/servo/servo/issues/15335
assert_eq!(parse(":: before"), Err(()));
assert_eq!(parse("div ::after"), Ok(SelectorList(vec!(Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec!(),
next: Some((Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::LocalName(LocalName {
name: String::from("div"),
lower_name: String::from("div") })),
next: None,
}), Combinator::Descendant)),
}),
pseudo_element: Some(PseudoElement::After),
specificity: specificity(0, 0, 2),
}))));
assert_eq!(parse("#d1 > .ok"), Ok(SelectorList(vec![Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec![
SimpleSelector::Class(String::from("ok")),
],
next: Some((Arc::new(ComplexSelector {
compound_selector: vec![
SimpleSelector::ID(String::from("d1")),
],
next: None,
}), Combinator::Child)),
}),
pseudo_element: None,
specificity: (1 << 20) + (1 << 10) + (0 << 0),
}])));
assert_eq!(parse(":not(.babybel, #provel.old)"), Ok(SelectorList(vec!(Selector {
complex_selector: Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::Negation(
vec!(
Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::Class(String::from("babybel"))),
next: None
}),
Arc::new(ComplexSelector {
compound_selector: vec!(
SimpleSelector::ID(String::from("provel")),
SimpleSelector::Class(String::from("old")),
),
next: None
}),
)
)),
next: None,
}),
pseudo_element: None,
specificity: specificity(1, 1, 0),
}))));
}
}