]>
Commit | Line | Data |
---|---|---|
ea8adc8c XL |
1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this | |
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | |
4 | ||
5 | use cssparser::ToCss; | |
6 | use parser::SelectorImpl; | |
7 | use std::ascii::AsciiExt; | |
8 | use std::fmt; | |
9 | ||
10 | #[derive(Eq, PartialEq, Clone)] | |
11 | pub struct AttrSelectorWithNamespace<Impl: SelectorImpl> { | |
12 | pub namespace: NamespaceConstraint<(Impl::NamespacePrefix, Impl::NamespaceUrl)>, | |
13 | pub local_name: Impl::LocalName, | |
14 | pub local_name_lower: Impl::LocalName, | |
15 | pub operation: ParsedAttrSelectorOperation<Impl::AttrValue>, | |
16 | pub never_matches: bool, | |
17 | } | |
18 | ||
19 | impl<Impl: SelectorImpl> AttrSelectorWithNamespace<Impl> { | |
20 | pub fn namespace(&self) -> NamespaceConstraint<&Impl::NamespaceUrl> { | |
21 | match self.namespace { | |
22 | NamespaceConstraint::Any => NamespaceConstraint::Any, | |
23 | NamespaceConstraint::Specific((_, ref url)) => { | |
24 | NamespaceConstraint::Specific(url) | |
25 | } | |
26 | } | |
27 | } | |
28 | } | |
29 | ||
30 | #[derive(Eq, PartialEq, Clone)] | |
31 | pub enum NamespaceConstraint<NamespaceUrl> { | |
32 | Any, | |
33 | ||
34 | /// Empty string for no namespace | |
35 | Specific(NamespaceUrl), | |
36 | } | |
37 | ||
38 | #[derive(Eq, PartialEq, Clone)] | |
39 | pub enum ParsedAttrSelectorOperation<AttrValue> { | |
40 | Exists, | |
41 | WithValue { | |
42 | operator: AttrSelectorOperator, | |
43 | case_sensitivity: ParsedCaseSensitivity, | |
44 | expected_value: AttrValue, | |
45 | } | |
46 | } | |
47 | ||
48 | #[derive(Eq, PartialEq, Clone)] | |
49 | pub enum AttrSelectorOperation<AttrValue> { | |
50 | Exists, | |
51 | WithValue { | |
52 | operator: AttrSelectorOperator, | |
53 | case_sensitivity: CaseSensitivity, | |
54 | expected_value: AttrValue, | |
55 | } | |
56 | } | |
57 | ||
58 | impl<AttrValue> AttrSelectorOperation<AttrValue> { | |
59 | pub fn eval_str(&self, element_attr_value: &str) -> bool where AttrValue: AsRef<str> { | |
60 | match *self { | |
61 | AttrSelectorOperation::Exists => true, | |
62 | AttrSelectorOperation::WithValue { operator, case_sensitivity, ref expected_value } => { | |
63 | operator.eval_str(element_attr_value, expected_value.as_ref(), case_sensitivity) | |
64 | } | |
65 | } | |
66 | } | |
67 | } | |
68 | ||
69 | #[derive(Eq, PartialEq, Clone, Copy)] | |
70 | pub enum AttrSelectorOperator { | |
71 | Equal, | |
72 | Includes, | |
73 | DashMatch, | |
74 | Prefix, | |
75 | Substring, | |
76 | Suffix, | |
77 | } | |
78 | ||
79 | impl ToCss for AttrSelectorOperator { | |
80 | fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { | |
81 | dest.write_str(match *self { | |
82 | AttrSelectorOperator::Equal => " = ", | |
83 | AttrSelectorOperator::Includes => " ~= ", | |
84 | AttrSelectorOperator::DashMatch => " |= ", | |
85 | AttrSelectorOperator::Prefix => " ^= ", | |
86 | AttrSelectorOperator::Substring => " *= ", | |
87 | AttrSelectorOperator::Suffix => " $= ", | |
88 | }) | |
89 | } | |
90 | } | |
91 | ||
92 | impl AttrSelectorOperator { | |
93 | pub fn eval_str(self, element_attr_value: &str, attr_selector_value: &str, | |
94 | case_sensitivity: CaseSensitivity) -> bool { | |
95 | let e = element_attr_value.as_bytes(); | |
96 | let s = attr_selector_value.as_bytes(); | |
97 | let case = case_sensitivity; | |
98 | match self { | |
99 | AttrSelectorOperator::Equal => { | |
100 | case.eq(e, s) | |
101 | } | |
102 | AttrSelectorOperator::Prefix => { | |
103 | e.len() >= s.len() && case.eq(&e[..s.len()], s) | |
104 | } | |
105 | AttrSelectorOperator::Suffix => { | |
106 | e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s) | |
107 | } | |
108 | AttrSelectorOperator::Substring => { | |
109 | case.contains(element_attr_value, attr_selector_value) | |
110 | } | |
111 | AttrSelectorOperator::Includes => { | |
112 | element_attr_value.split(SELECTOR_WHITESPACE) | |
113 | .any(|part| case.eq(part.as_bytes(), s)) | |
114 | } | |
115 | AttrSelectorOperator::DashMatch => { | |
116 | case.eq(e, s) || ( | |
117 | e.get(s.len()) == Some(&b'-') && | |
118 | case.eq(&e[..s.len()], s) | |
119 | ) | |
120 | } | |
121 | } | |
122 | } | |
123 | } | |
124 | ||
125 | /// The definition of whitespace per CSS Selectors Level 3 ยง 4. | |
126 | pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C']; | |
127 | ||
128 | #[derive(Eq, PartialEq, Clone, Copy, Debug)] | |
129 | pub enum ParsedCaseSensitivity { | |
130 | CaseSensitive, // Selectors spec says language-defined, but HTML says sensitive. | |
131 | AsciiCaseInsensitive, | |
132 | AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument, | |
133 | } | |
134 | ||
135 | impl ParsedCaseSensitivity { | |
136 | pub fn to_unconditional(self, is_html_element_in_html_document: bool) -> CaseSensitivity { | |
137 | match self { | |
138 | ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument | |
139 | if is_html_element_in_html_document => { | |
140 | CaseSensitivity::AsciiCaseInsensitive | |
141 | } | |
142 | ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => { | |
143 | CaseSensitivity::CaseSensitive | |
144 | } | |
145 | ParsedCaseSensitivity::CaseSensitive => CaseSensitivity::CaseSensitive, | |
146 | ParsedCaseSensitivity::AsciiCaseInsensitive => CaseSensitivity::AsciiCaseInsensitive, | |
147 | } | |
148 | } | |
149 | } | |
150 | ||
151 | #[derive(Eq, PartialEq, Clone, Copy, Debug)] | |
152 | pub enum CaseSensitivity { | |
153 | CaseSensitive, // Selectors spec says language-defined, but HTML says sensitive. | |
154 | AsciiCaseInsensitive, | |
155 | } | |
156 | ||
157 | impl CaseSensitivity { | |
158 | pub fn eq(self, a: &[u8], b: &[u8]) -> bool { | |
159 | match self { | |
160 | CaseSensitivity::CaseSensitive => a == b, | |
161 | CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b), | |
162 | } | |
163 | } | |
164 | ||
165 | pub fn contains(self, haystack: &str, needle: &str) -> bool { | |
166 | match self { | |
167 | CaseSensitivity::CaseSensitive => haystack.contains(needle), | |
168 | CaseSensitivity::AsciiCaseInsensitive => { | |
169 | if let Some((&n_first_byte, n_rest)) = needle.as_bytes().split_first() { | |
170 | haystack.bytes().enumerate().any(|(i, byte)| { | |
171 | if !byte.eq_ignore_ascii_case(&n_first_byte) { | |
172 | return false | |
173 | } | |
174 | let after_this_byte = &haystack.as_bytes()[i + 1..]; | |
175 | match after_this_byte.get(..n_rest.len()) { | |
176 | None => false, | |
177 | Some(haystack_slice) => { | |
178 | haystack_slice.eq_ignore_ascii_case(n_rest) | |
179 | } | |
180 | } | |
181 | }) | |
182 | } else { | |
183 | // any_str.contains("") == true, | |
184 | // though these cases should be handled with *NeverMatches and never go here. | |
185 | true | |
186 | } | |
187 | } | |
188 | } | |
189 | } | |
190 | } |