Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(306)

Side by Side Diff: pkg/third_party/html5lib/lib/src/query_selector.dart

Issue 268623002: [html5lib] implement querySelector/querySelectorAll (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 /// Query selector implementation for html5lib's DOM.
2 library html5lib.src.query;
3
4 import 'package:csslib/parser.dart' as css;
5 import 'package:csslib/parser.dart' show TokenKind;
6 import 'package:csslib/visitor.dart'; // the CSSOM
7 import 'package:html5lib/dom.dart';
8 import 'package:html5lib/src/constants.dart' show isWhitespaceCC;
9
10 bool matches(Node node, String selector) =>
11 new SelectorEvaluator().matches(node, _parseSelectorList(selector));
12
13 Element querySelector(Node node, String selector) =>
14 new SelectorEvaluator().querySelector(node, _parseSelectorList(selector));
15
16 List<Element> querySelectorAll(Node node, String selector) {
17 var results = [];
18 new SelectorEvaluator()
19 .querySelectorAll(node, _parseSelectorList(selector), results);
20 return results;
21 }
22
23 // http://dev.w3.org/csswg/selectors-4/#grouping
24 SelectorGroup _parseSelectorList(String selector) {
25 var errors = [];
26 var group = css.parseSelectorGroup(selector, errors: errors);
27 if (group == null || errors.isNotEmpty) {
28 throw new FormatException("'$selector' is not a valid selector: $errors");
29 }
30 return group;
31 }
32
33 class SelectorEvaluator extends Visitor {
34 /// The current HTML element to match against.
35 Element _element;
36
37 bool matches(Element element, SelectorGroup selector) {
38 _element = element;
39 return visitSelectorGroup(selector);
40 }
41
42 Element querySelector(Node root, SelectorGroup selector) {
43 for (var node in root.nodes) {
44 if (node is! Element) continue;
45 if (matches(node, selector)) return node;
46 var result = querySelector(node, selector);
47 if (result != null) return result;
48 }
49 return null;
50 }
51
52 void querySelectorAll(Node root, SelectorGroup selector,
53 List<Element> results) {
54
55 for (var node in root.nodes) {
56 if (node is! Element) continue;
57 if (matches(node, selector)) results.add(node);
58 querySelectorAll(node, selector, results);
59 }
60 }
61
62
63 bool visitSelectorGroup(SelectorGroup group) =>
64 group.selectors.any(visitSelector);
65
66 bool visitSelector(Selector selector) {
67 var old = _element;
68 var result = true;
69
70 // Note: evaluate selectors right-to-left as it's more efficient.
71 int combinator = null;
72 for (var s in selector.simpleSelectorSequences.reversed) {
73 if (combinator == null) {
74 result = s.simpleSelector.visit(this);
75 } else if (combinator == TokenKind.COMBINATOR_DESCENDANT) {
76 // descendant combinator
77 // http://dev.w3.org/csswg/selectors-4/#descendant-combinators
78 do {
79 _element = _element.parent;
80 } while (_element != null && !s.simpleSelector.visit(this));
81
82 if (_element == null) result = false;
83 } else if (combinator == TokenKind.COMBINATOR_TILDE) {
84 // Following-sibling combinator
85 // http://dev.w3.org/csswg/selectors-4/#general-sibling-combinators
86 do {
87 _element = _element.previousElementSibling;
88 } while (_element != null && !s.simpleSelector.visit(this));
89
90 if (_element == null) result = false;
91 }
92
93 if (!result) break;
94
95 switch (s.combinator) {
96 case TokenKind.COMBINATOR_PLUS:
97 // Next-sibling combinator
98 // http://dev.w3.org/csswg/selectors-4/#adjacent-sibling-combinators
99 _element = _element.previousElementSibling;
100 break;
101 case TokenKind.COMBINATOR_GREATER:
102 // Child combinator
103 // http://dev.w3.org/csswg/selectors-4/#child-combinators
104 _element = _element.parent;
105 break;
106 case TokenKind.COMBINATOR_DESCENDANT:
107 case TokenKind.COMBINATOR_TILDE:
108 // We need to iterate through all siblings or parents.
109 // For now, just remember what the combinator was.
110 combinator = s.combinator;
111 break;
112 case TokenKind.COMBINATOR_NONE: break;
113 default: throw _unsupported(selector);
114 }
115
116 if (_element == null) {
117 result = false;
118 break;
119 }
120 }
121
122 _element = old;
123 return result;
124 }
125
126 _unimplemented(SimpleSelector selector) =>
127 new UnimplementedError("'$selector' selector of type "
128 "${selector.runtimeType} is not implemented");
129
130 _unsupported(selector) =>
131 new FormatException("'$selector' is not a valid selector");
132
133 bool visitPseudoClassSelector(PseudoClassSelector selector) {
134 switch (selector.name) {
135 // http://dev.w3.org/csswg/selectors-4/#structural-pseudos
136
137 // http://dev.w3.org/csswg/selectors-4/#the-root-pseudo
138 case 'root':
139 // TODO(jmesserly): fix when we have a .ownerDocument pointer
140 // return _element == _element.ownerDocument.rootElement;
141 return _element.localName == 'html' && _element.parentNode == null;
142
143 // http://dev.w3.org/csswg/selectors-4/#the-empty-pseudo
144 case 'empty':
145 return _element.nodes.any((n) => !(n is Element ||
146 n is Text && n.text.isNotEmpty));
147
148 // http://dev.w3.org/csswg/selectors-4/#the-blank-pseudo
149 case 'blank':
150 return _element.nodes.any((n) => !(n is Element ||
151 n is Text && n.text.runes.any((r) => !isWhitespaceCC(r))));
152
153 // http://dev.w3.org/csswg/selectors-4/#the-first-child-pseudo
154 case 'first-child':
155 return _element.previousElementSibling == null;
156
157 // http://dev.w3.org/csswg/selectors-4/#the-last-child-pseudo
158 case 'last-child':
159 return _element.nextElementSibling == null;
160
161 // http://dev.w3.org/csswg/selectors-4/#the-only-child-pseudo
162 case 'only-child':
163 return _element.previousElementSibling == null &&
164 _element.nextElementSibling == null;
165
166 // http://dev.w3.org/csswg/selectors-4/#link
167 case 'link':
168 return _element.attributes['href'] != null;
169
170 case 'visited':
171 // Always return false since we aren't a browser. This is allowed per:
172 // http://dev.w3.org/csswg/selectors-4/#visited-pseudo
173 return false;
174 }
175
176 // :before, :after, :first-letter/line can't match DOM elements.
177 if (_isLegacyPsuedoClass(selector.name)) return false;
178
179 throw _unimplemented(selector);
180 }
181
182
183 bool visitPseudoElementSelector(PseudoElementSelector selector) {
184 // :before, :after, :first-letter/line can't match DOM elements.
185 if (_isLegacyPsuedoClass(selector.name)) return false;
186
187 throw _unimplemented(selector);
188 }
189
190 static bool _isLegacyPsuedoClass(String name) {
191 switch (name) {
192 case 'before': case 'after': case 'first-line': case 'first-letter':
193 return true;
194 default: return false;
195 }
196 }
197
198 bool visitPseudoElementFunctionSelector(PseudoElementFunctionSelector s) =>
199 throw _unimplemented(s);
200
201 bool visitPseudoClassFunctionSelector(PseudoClassFunctionSelector selector) {
202 switch (selector.name) {
203 // http://dev.w3.org/csswg/selectors-4/#child-index
204
205 // http://dev.w3.org/csswg/selectors-4/#the-nth-child-pseudo
206 case 'nth-child':
207 // TODO(jmesserly): support An+B syntax too.
208 var exprs = selector.expression.expressions;
209 if (exprs.length == 1 && exprs[0] is LiteralTerm) {
210 LiteralTerm literal = exprs[0];
211 var parent = _element.parentNode;
212 return parent != null && literal.value > 0 &&
213 parent.nodes.indexOf(_element) == literal.value;
214 }
215 break;
216
217 // http://dev.w3.org/csswg/selectors-4/#the-lang-pseudo
218 case 'lang':
219 // TODO(jmesserly): shouldn't need to get the raw text here, but csslib
220 // gets confused by the "-" in the expression, such as in "es-AR".
221 var toMatch = selector.expression.span.text;
222 var lang = _getInheritedLanguage(_element);
223 // TODO(jmesserly): implement wildcards in level 4
224 return lang != null && lang.startsWith(toMatch);
225 }
226 throw _unimplemented(selector);
227 }
228
229 static String _getInheritedLanguage(Node node) {
230 while (node != null) {
231 var lang = node.attributes['lang'];
232 if (lang != null) return lang;
233 node = node.parent;
234 }
235 return null;
236 }
237
238 bool visitNamespaceSelector(NamespaceSelector selector) {
239 // Match element tag name
240 if (!selector.nameAsSimpleSelector.visit(this)) return false;
241
242 if (selector.isNamespaceWildcard) return true;
243
244 if (selector.namespace == '') return _element.namespaceUri == null;
245
246 throw _unimplemented(selector);
247 }
248
249 bool visitElementSelector(ElementSelector selector) =>
250 selector.isWildcard || _element.localName == selector.name.toLowerCase();
251
252 bool visitIdSelector(IdSelector selector) => _element.id == selector.name;
253
254 bool visitClassSelector(ClassSelector selector) =>
255 _element.classes.contains(selector.name);
256
257 // TODO(jmesserly): negation should support any selectors in level 4,
258 // not just simple selectors.
259 // http://dev.w3.org/csswg/selectors-4/#negation
260 bool visitNegationSelector(NegationSelector selector) =>
261 !selector.negationArg.visit(this);
262
263 bool visitAttributeSelector(AttributeSelector selector) {
264 // Match name first
265 var value = _element.attributes[selector.name.toLowerCase()];
266 if (value == null) return false;
267
268 if (selector.operatorKind == TokenKind.NO_MATCH) return true;
269
270 var select = '${selector.value}';
271 switch (selector.operatorKind) {
272 case TokenKind.EQUALS:
273 return value == select;
274 case TokenKind.INCLUDES:
275 return value.split(' ').any((v) => v.isNotEmpty && v == select);
276 case TokenKind.DASH_MATCH:
277 return value.startsWith(select) &&
278 (value.length == select.length || value[select.length] == '-');
279 case TokenKind.PREFIX_MATCH:
280 return value.startsWith(select);
281 case TokenKind.SUFFIX_MATCH:
282 return value.endsWith(select);
283 case TokenKind.SUBSTRING_MATCH:
284 return value.contains(select);
285 default: throw _unsupported(selector);
286 }
287 }
288 }
OLDNEW
« no previous file with comments | « pkg/third_party/html5lib/lib/src/css_class_set.dart ('k') | pkg/third_party/html5lib/lib/src/treebuilder.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698