gecko-dev/servo/components/selectors/attr.rs
Simon Sapin cf341df19a servo: Merge #19162 - Allow unused imports for AsciiExt in style code (from emilio:ascii-ext); r=emilio
See #19128, this part is cherry-picked so Gecko can build with rust nightly.

Source-Repo: https://github.com/servo/servo
Source-Revision: e7a654dd13f589e127193267bcb576ffd661c11d

--HG--
extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear
extra : subtree_revision : 0ee67ddf598b1ea99e92f6a285a960f86b1cb971
2017-11-09 06:49:29 -06:00

193 lines
6.5 KiB
Rust

/* 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::ToCss;
use parser::SelectorImpl;
#[allow(unused_imports)] use std::ascii::AsciiExt;
use std::fmt;
#[derive(Clone, Eq, PartialEq)]
pub struct AttrSelectorWithNamespace<Impl: SelectorImpl> {
pub namespace: NamespaceConstraint<(Impl::NamespacePrefix, Impl::NamespaceUrl)>,
pub local_name: Impl::LocalName,
pub local_name_lower: Impl::LocalName,
pub operation: ParsedAttrSelectorOperation<Impl::AttrValue>,
pub never_matches: bool,
}
impl<Impl: SelectorImpl> AttrSelectorWithNamespace<Impl> {
pub fn namespace(&self) -> NamespaceConstraint<&Impl::NamespaceUrl> {
match self.namespace {
NamespaceConstraint::Any => NamespaceConstraint::Any,
NamespaceConstraint::Specific((_, ref url)) => {
NamespaceConstraint::Specific(url)
}
}
}
}
#[derive(Clone, Eq, PartialEq)]
pub enum NamespaceConstraint<NamespaceUrl> {
Any,
/// Empty string for no namespace
Specific(NamespaceUrl),
}
#[derive(Clone, Eq, PartialEq)]
pub enum ParsedAttrSelectorOperation<AttrValue> {
Exists,
WithValue {
operator: AttrSelectorOperator,
case_sensitivity: ParsedCaseSensitivity,
expected_value: AttrValue,
}
}
#[derive(Clone, Eq, PartialEq)]
pub enum AttrSelectorOperation<AttrValue> {
Exists,
WithValue {
operator: AttrSelectorOperator,
case_sensitivity: CaseSensitivity,
expected_value: AttrValue,
}
}
impl<AttrValue> AttrSelectorOperation<AttrValue> {
pub fn eval_str(&self, element_attr_value: &str) -> bool where AttrValue: AsRef<str> {
match *self {
AttrSelectorOperation::Exists => true,
AttrSelectorOperation::WithValue { operator, case_sensitivity, ref expected_value } => {
operator.eval_str(element_attr_value, expected_value.as_ref(), case_sensitivity)
}
}
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum AttrSelectorOperator {
Equal,
Includes,
DashMatch,
Prefix,
Substring,
Suffix,
}
impl ToCss for AttrSelectorOperator {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
// https://drafts.csswg.org/cssom/#serializing-selectors
// See "attribute selector".
dest.write_str(match *self {
AttrSelectorOperator::Equal => "=",
AttrSelectorOperator::Includes => "~=",
AttrSelectorOperator::DashMatch => "|=",
AttrSelectorOperator::Prefix => "^=",
AttrSelectorOperator::Substring => "*=",
AttrSelectorOperator::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(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ParsedCaseSensitivity {
CaseSensitive,
AsciiCaseInsensitive,
AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument,
}
impl ParsedCaseSensitivity {
pub fn to_unconditional(self, is_html_element_in_html_document: bool) -> CaseSensitivity {
match self {
ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument
if is_html_element_in_html_document => {
CaseSensitivity::AsciiCaseInsensitive
}
ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {
CaseSensitivity::CaseSensitive
}
ParsedCaseSensitivity::CaseSensitive => CaseSensitivity::CaseSensitive,
ParsedCaseSensitivity::AsciiCaseInsensitive => CaseSensitivity::AsciiCaseInsensitive,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CaseSensitivity {
CaseSensitive,
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
}
}
}
}
}