Simplify rust-selectors API for attribute selectors

This commit is contained in:
Simon Sapin 2017-05-17 19:41:21 +02:00
parent 2ca2c2d2be
commit 83c7824fda
15 changed files with 447 additions and 480 deletions

View file

@ -0,0 +1,114 @@
/* 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 parser::SelectorImpl;
use std::ascii::AsciiExt;
pub enum AttrSelectorOperation<'a, Impl: SelectorImpl> where Impl::AttrValue: 'a {
Exists,
WithValue {
operator: AttrSelectorOperator,
case_sensitivity: CaseSensitivity,
expected_value: &'a Impl::AttrValue,
}
}
impl<'a, Impl: SelectorImpl> AttrSelectorOperation<'a, Impl> {
pub fn eval_str(&self, element_attr_value: &str) -> bool
where Impl::AttrValue: AsRef<str> {
match *self {
AttrSelectorOperation::Exists => true,
AttrSelectorOperation::WithValue { operator, case_sensitivity, expected_value } => {
operator.eval_str(element_attr_value, expected_value.as_ref(), case_sensitivity)
}
}
}
}
#[derive(Eq, PartialEq, Clone, Copy)]
pub enum AttrSelectorOperator {
Equal, // =
Includes, // ~=
DashMatch, // |=
Prefix, // ^=
Substring, // *=
Suffix, // $=
}
impl AttrSelectorOperator {
pub fn eval_str(self, element_attr_value: &str, attr_selector_value: &str,
case_sensitivity: CaseSensitivity) -> bool {
let e = element_attr_value.as_bytes();
let s = attr_selector_value.as_bytes();
let case = case_sensitivity;
match self {
AttrSelectorOperator::Equal => {
case.eq(e, s)
}
AttrSelectorOperator::Prefix => {
e.len() >= s.len() && case.eq(&e[..s.len()], s)
}
AttrSelectorOperator::Suffix => {
e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s)
}
AttrSelectorOperator::Substring => {
case.contains(element_attr_value, attr_selector_value)
}
AttrSelectorOperator::Includes => {
element_attr_value.split(SELECTOR_WHITESPACE)
.any(|part| case.eq(part.as_bytes(), s))
}
AttrSelectorOperator::DashMatch => {
case.eq(e, s) || (
e.get(s.len()) == Some(&b'-') &&
case.eq(&e[..s.len()], s)
)
}
}
}
}
/// The definition of whitespace per CSS Selectors Level 3 § 4.
pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C'];
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub enum CaseSensitivity {
CaseSensitive, // Selectors spec says language-defined, but HTML says sensitive.
AsciiCaseInsensitive,
}
impl CaseSensitivity {
pub fn eq(self, a: &[u8], b: &[u8]) -> bool {
match self {
CaseSensitivity::CaseSensitive => a == b,
CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b)
}
}
pub fn contains(self, haystack: &str, needle: &str) -> bool {
match self {
CaseSensitivity::CaseSensitive => haystack.contains(needle),
CaseSensitivity::AsciiCaseInsensitive => {
if let Some((&n_first_byte, n_rest)) = needle.as_bytes().split_first() {
haystack.bytes().enumerate().any(|(i, byte)| {
if !byte.eq_ignore_ascii_case(&n_first_byte) {
return false
}
let after_this_byte = &haystack.as_bytes()[i + 1..];
match after_this_byte.get(..n_rest.len()) {
None => false,
Some(haystack_slice) => {
haystack_slice.eq_ignore_ascii_case(n_rest)
}
}
})
} else {
// any_str.contains("") == true,
// though these cases should be handled with *NeverMatches and never go here.
true
}
}
}
}
}

View file

@ -11,6 +11,7 @@ extern crate precomputed_hash;
extern crate smallvec;
pub mod arcslice;
pub mod attr;
pub mod bloom;
pub mod matching;
pub mod parser;
@ -21,4 +22,3 @@ pub mod visitor;
pub use parser::{SelectorImpl, Parser, SelectorList};
pub use tree::Element;
pub use tree::{MatchAttr, MatchAttrGeneric};

View file

@ -1,9 +1,11 @@
/* 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 attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity};
use bloom::BloomFilter;
use parser::{CaseSensitivity, Combinator, ComplexSelector, Component, LocalName};
use parser::{Selector, SelectorInner, SelectorIter, SelectorImpl};
use parser::{Combinator, ComplexSelector, Component, LocalName, NamespaceConstraint};
use parser::{Selector, SelectorInner, SelectorIter, SelectorImpl, AttrSelector};
use std::borrow::Borrow;
use tree::Element;
@ -410,28 +412,56 @@ fn matches_simple_selector<E, F>(
element.has_class(class)
}
Component::AttrExists(ref attr) => {
element.match_attr_has(attr)
let (ns, n) = unpack_attr_selector(element, attr);
element.attr_matches(ns, n, &AttrSelectorOperation::Exists)
}
Component::AttrEqual(ref attr, ref value, case_sensitivity) => {
match case_sensitivity {
CaseSensitivity::CaseSensitive => element.match_attr_equals(attr, value),
CaseSensitivity::AsciiCaseInsensitive => element.match_attr_equals_ignore_ascii_case(attr, value),
}
let (ns, n) = unpack_attr_selector(element, attr);
element.attr_matches(ns, n, &AttrSelectorOperation::WithValue {
operator: AttrSelectorOperator::Equal,
case_sensitivity: case_sensitivity,
expected_value: value
})
}
Component::AttrIncludes(ref attr, ref value) => {
element.match_attr_includes(attr, value)
let (ns, n) = unpack_attr_selector(element, attr);
element.attr_matches(ns, n, &AttrSelectorOperation::WithValue {
operator: AttrSelectorOperator::Includes,
case_sensitivity: CaseSensitivity::CaseSensitive,
expected_value: value
})
}
Component::AttrDashMatch(ref attr, ref value) => {
element.match_attr_dash(attr, value)
let (ns, n) = unpack_attr_selector(element, attr);
element.attr_matches(ns, n, &AttrSelectorOperation::WithValue {
operator: AttrSelectorOperator::DashMatch,
case_sensitivity: CaseSensitivity::CaseSensitive,
expected_value: value
})
}
Component::AttrPrefixMatch(ref attr, ref value) => {
element.match_attr_prefix(attr, value)
let (ns, n) = unpack_attr_selector(element, attr);
element.attr_matches(ns, n, &AttrSelectorOperation::WithValue {
operator: AttrSelectorOperator::Prefix,
case_sensitivity: CaseSensitivity::CaseSensitive,
expected_value: value
})
}
Component::AttrSubstringMatch(ref attr, ref value) => {
element.match_attr_substring(attr, value)
let (ns, n) = unpack_attr_selector(element, attr);
element.attr_matches(ns, n, &AttrSelectorOperation::WithValue {
operator: AttrSelectorOperator::Substring,
case_sensitivity: CaseSensitivity::CaseSensitive,
expected_value: value
})
}
Component::AttrSuffixMatch(ref attr, ref value) => {
element.match_attr_suffix(attr, value)
let (ns, n) = unpack_attr_selector(element, attr);
element.attr_matches(ns, n, &AttrSelectorOperation::WithValue {
operator: AttrSelectorOperator::Suffix,
case_sensitivity: CaseSensitivity::CaseSensitive,
expected_value: value
})
}
Component::AttrIncludesNeverMatch(..) |
Component::AttrPrefixNeverMatch(..) |
@ -489,6 +519,18 @@ fn matches_simple_selector<E, F>(
}
}
fn unpack_attr_selector<'a, E>(element: &E, attr: &'a AttrSelector<E::Impl>)
-> (&'a NamespaceConstraint<E::Impl>,
&'a <E::Impl as SelectorImpl>::LocalName)
where E: Element {
let name = if element.is_html_element_in_html_document() {
&attr.lower_name
} else {
&attr.name
};
(&attr.namespace, name)
}
#[inline]
fn matches_generic_nth_child<E, F>(element: &E,
a: i32,

View file

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use arcslice::ArcSlice;
use attr::{CaseSensitivity, SELECTOR_WHITESPACE};
use cssparser::{Token, Parser as CssParser, parse_nth, ToCss, serialize_identifier, CssStringWriter};
use precomputed_hash::PrecomputedHash;
use smallvec::SmallVec;
@ -13,7 +14,6 @@ use std::fmt::{self, Display, Debug, Write};
use std::iter::Rev;
use std::ops::Add;
use std::slice;
use tree::SELECTOR_WHITESPACE;
use visitor::SelectorVisitor;
/// A trait that represents a pseudo-element.
@ -560,7 +560,6 @@ pub enum Component<Impl: SelectorImpl> {
OnlyOfType,
NonTSPseudoClass(Impl::NonTSPseudoClass),
PseudoElement(Impl::PseudoElement),
// ...
}
impl<Impl: SelectorImpl> Component<Impl> {
@ -605,13 +604,6 @@ impl<Impl: SelectorImpl> Component<Impl> {
}
}
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub enum CaseSensitivity {
CaseSensitive, // Selectors spec says language-defined, but HTML says sensitive.
AsciiCaseInsensitive,
}
#[derive(Eq, PartialEq, Clone)]
pub struct LocalName<Impl: SelectorImpl> {
pub name: Impl::LocalName,

View file

@ -5,122 +5,13 @@
//! Traits that nodes must implement. Breaks the otherwise-cyclic dependency between layout and
//! style.
use attr::AttrSelectorOperation;
use matching::{ElementSelectorFlags, MatchingContext};
use parser::{AttrSelector, SelectorImpl};
use std::ascii::AsciiExt;
use parser::{NamespaceConstraint, SelectorImpl};
/// The definition of whitespace per CSS Selectors Level 3 § 4.
pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C'];
// Attribute matching routines. Consumers with simple implementations can implement
// MatchAttrGeneric instead.
pub trait MatchAttr {
pub trait Element: Sized {
type Impl: SelectorImpl;
fn match_attr_has(
&self,
attr: &AttrSelector<Self::Impl>) -> bool;
fn match_attr_equals(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
fn match_attr_equals_ignore_ascii_case(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
fn match_attr_includes(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
fn match_attr_dash(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
fn match_attr_prefix(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
fn match_attr_substring(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
fn match_attr_suffix(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
}
pub trait MatchAttrGeneric {
type Impl: SelectorImpl;
fn match_attr<F>(&self, attr: &AttrSelector<Self::Impl>, test: F) -> bool where F: Fn(&str) -> bool;
}
impl<T> MatchAttr for T where T: MatchAttrGeneric, T::Impl: SelectorImpl<AttrValue = String> {
type Impl = T::Impl;
fn match_attr_has(&self, attr: &AttrSelector<Self::Impl>) -> bool {
self.match_attr(attr, |_| true)
}
fn match_attr_equals(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
self.match_attr(attr, |v| v == value)
}
fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector<Self::Impl>,
value: &String) -> bool {
self.match_attr(attr, |v| v.eq_ignore_ascii_case(value))
}
fn match_attr_includes(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
self.match_attr(attr, |attr_value| {
attr_value.split(SELECTOR_WHITESPACE).any(|v| v == value)
})
}
fn match_attr_dash(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
self.match_attr(attr, |attr_value| {
// The attribute must start with the pattern.
if !attr_value.starts_with(value) {
return false
}
// If the strings are the same, we're done.
if attr_value.len() == value.len() {
return true
}
// The attribute is long than the pattern, so the next character must be '-'.
attr_value.as_bytes()[value.len()] == '-' as u8
})
}
fn match_attr_prefix(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
self.match_attr(attr, |attr_value| {
attr_value.starts_with(value)
})
}
fn match_attr_substring(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
self.match_attr(attr, |attr_value| {
attr_value.contains(value)
})
}
fn match_attr_suffix(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
self.match_attr(attr, |attr_value| {
attr_value.ends_with(value)
})
}
}
pub trait Element: MatchAttr + Sized {
fn parent_element(&self) -> Option<Self>;
/// The parent of a given pseudo-element, after matching a pseudo-element
@ -147,6 +38,12 @@ pub trait Element: MatchAttr + Sized {
fn get_local_name(&self) -> &<Self::Impl as SelectorImpl>::BorrowedLocalName;
fn get_namespace(&self) -> &<Self::Impl as SelectorImpl>::BorrowedNamespaceUrl;
fn attr_matches(&self,
ns: &NamespaceConstraint<Self::Impl>,
local_name: &<Self::Impl as SelectorImpl>::LocalName,
operation: &AttrSelectorOperation<Self::Impl>)
-> bool;
fn match_non_ts_pseudo_class<F>(&self,
pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
context: &mut MatchingContext,