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

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

Issue 814113004: Pull args, intl, logging, shelf, and source_maps out of the SDK. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Also csslib. Created 6 years 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 /// Internals to the tree builders.
2 library treebuilder;
3
4 import 'dart:collection';
5 import 'package:html5lib/dom.dart';
6 import 'package:html5lib/parser.dart' show getElementNameTuple;
7 import 'package:source_span/source_span.dart';
8 import 'constants.dart';
9 import 'list_proxy.dart';
10 import 'token.dart';
11 import 'utils.dart';
12
13 // The scope markers are inserted when entering object elements,
14 // marquees, table cells, and table captions, and are used to prevent formatting
15 // from "leaking" into tables, object elements, and marquees.
16 const Node Marker = null;
17
18 // TODO(jmesserly): this should extend ListBase<Element>, but my simple attempt
19 // didn't work.
20 class ActiveFormattingElements extends ListProxy<Element> {
21 ActiveFormattingElements() : super();
22
23 // Override the "add" method.
24 // TODO(jmesserly): I'd rather not override this; can we do this in the
25 // calling code instead?
26 void add(Element node) {
27 int equalCount = 0;
28 if (node != Marker) {
29 for (var element in reversed) {
30 if (element == Marker) {
31 break;
32 }
33 if (_nodesEqual(element, node)) {
34 equalCount += 1;
35 }
36 if (equalCount == 3) {
37 remove(element);
38 break;
39 }
40 }
41 }
42 super.add(node);
43 }
44 }
45
46 // TODO(jmesserly): this should exist in corelib...
47 bool _mapEquals(Map a, Map b) {
48 if (a.length != b.length) return false;
49 if (a.length == 0) return true;
50
51 for (var keyA in a.keys) {
52 var valB = b[keyA];
53 if (valB == null && !b.containsKey(keyA)) {
54 return false;
55 }
56
57 if (a[keyA] != valB) {
58 return false;
59 }
60 }
61 return true;
62 }
63
64
65 bool _nodesEqual(Element node1, Element node2) {
66 return getElementNameTuple(node1) == getElementNameTuple(node2) &&
67 _mapEquals(node1.attributes, node2.attributes);
68 }
69
70 /// Basic treebuilder implementation.
71 class TreeBuilder {
72 final String defaultNamespace;
73
74 Document document;
75
76 final List<Element> openElements = <Element>[];
77
78 final activeFormattingElements = new ActiveFormattingElements();
79
80 Node headPointer;
81
82 Node formPointer;
83
84 /// Switch the function used to insert an element from the
85 /// normal one to the misnested table one and back again
86 bool insertFromTable;
87
88 TreeBuilder(bool namespaceHTMLElements)
89 : defaultNamespace = namespaceHTMLElements ? Namespaces.html : null {
90 reset();
91 }
92
93 void reset() {
94 openElements.clear();
95 activeFormattingElements.clear();
96
97 //XXX - rename these to headElement, formElement
98 headPointer = null;
99 formPointer = null;
100
101 insertFromTable = false;
102
103 document = new Document();
104 }
105
106 bool elementInScope(target, {String variant}) {
107 //If we pass a node in we match that. if we pass a string
108 //match any node with that name
109 bool exactNode = target is Node;
110
111 List listElements1 = scopingElements;
112 List listElements2 = const [];
113 bool invert = false;
114 if (variant != null) {
115 switch (variant) {
116 case "button":
117 listElements2 = const [const Pair(Namespaces.html, "button")];
118 break;
119 case "list":
120 listElements2 = const [const Pair(Namespaces.html, "ol"),
121 const Pair(Namespaces.html, "ul")];
122 break;
123 case "table":
124 listElements1 = const [const Pair(Namespaces.html, "html"),
125 const Pair(Namespaces.html, "table")];
126 break;
127 case "select":
128 listElements1 = const [const Pair(Namespaces.html, "optgroup"),
129 const Pair(Namespaces.html, "option")];
130 invert = true;
131 break;
132 default:
133 throw new StateError('We should never reach this point');
134 }
135 }
136
137 for (var node in openElements.reversed) {
138 if (!exactNode && node.localName == target ||
139 exactNode && node == target) {
140 return true;
141 } else if (invert !=
142 (listElements1.contains(getElementNameTuple(node)) ||
143 listElements2.contains(getElementNameTuple(node)))) {
144 return false;
145 }
146 }
147
148 throw new StateError('We should never reach this point');
149 }
150
151 void reconstructActiveFormattingElements() {
152 // Within this algorithm the order of steps described in the
153 // specification is not quite the same as the order of steps in the
154 // code. It should still do the same though.
155
156 // Step 1: stop the algorithm when there's nothing to do.
157 if (activeFormattingElements.length == 0) {
158 return;
159 }
160
161 // Step 2 and step 3: we start with the last element. So i is -1.
162 int i = activeFormattingElements.length - 1;
163 var entry = activeFormattingElements[i];
164 if (entry == Marker || openElements.contains(entry)) {
165 return;
166 }
167
168 // Step 6
169 while (entry != Marker && !openElements.contains(entry)) {
170 if (i == 0) {
171 //This will be reset to 0 below
172 i = -1;
173 break;
174 }
175 i -= 1;
176 // Step 5: let entry be one earlier in the list.
177 entry = activeFormattingElements[i];
178 }
179
180 while (true) {
181 // Step 7
182 i += 1;
183
184 // Step 8
185 entry = activeFormattingElements[i];
186
187 // TODO(jmesserly): optimize this. No need to create a token.
188 var cloneToken = new StartTagToken(
189 entry.localName,
190 namespace: entry.namespaceUri,
191 data: new LinkedHashMap.from(entry.attributes))
192 ..span = entry.sourceSpan;
193
194 // Step 9
195 var element = insertElement(cloneToken);
196
197 // Step 10
198 activeFormattingElements[i] = element;
199
200 // Step 11
201 if (element == activeFormattingElements.last) {
202 break;
203 }
204 }
205 }
206
207 void clearActiveFormattingElements() {
208 var entry = activeFormattingElements.removeLast();
209 while (activeFormattingElements.length > 0 && entry != Marker) {
210 entry = activeFormattingElements.removeLast();
211 }
212 }
213
214 /// Check if an element exists between the end of the active
215 /// formatting elements and the last marker. If it does, return it, else
216 /// return null.
217 Element elementInActiveFormattingElements(String name) {
218 for (var item in activeFormattingElements.reversed) {
219 // Check for Marker first because if it's a Marker it doesn't have a
220 // name attribute.
221 if (item == Marker) {
222 break;
223 } else if (item.localName == name) {
224 return item;
225 }
226 }
227 return null;
228 }
229
230 void insertRoot(Token token) {
231 var element = createElement(token);
232 openElements.add(element);
233 document.nodes.add(element);
234 }
235
236 void insertDoctype(DoctypeToken token) {
237 var doctype = new DocumentType(token.name, token.publicId, token.systemId)
238 ..sourceSpan = token.span;
239 document.nodes.add(doctype);
240 }
241
242 void insertComment(StringToken token, [Node parent]) {
243 if (parent == null) {
244 parent = openElements.last;
245 }
246 parent.nodes.add(new Comment(token.data)..sourceSpan = token.span);
247 }
248
249 /// Create an element but don't insert it anywhere
250 Element createElement(StartTagToken token) {
251 var name = token.name;
252 var namespace = token.namespace;
253 if (namespace == null) namespace = defaultNamespace;
254 var element = document.createElementNS(namespace, name)
255 ..attributes = token.data
256 ..sourceSpan = token.span;
257 return element;
258 }
259
260 Element insertElement(StartTagToken token) {
261 if (insertFromTable) return insertElementTable(token);
262 return insertElementNormal(token);
263 }
264
265 Element insertElementNormal(StartTagToken token) {
266 var name = token.name;
267 var namespace = token.namespace;
268 if (namespace == null) namespace = defaultNamespace;
269 var element = document.createElementNS(namespace, name)
270 ..attributes = token.data
271 ..sourceSpan = token.span;
272 openElements.last.nodes.add(element);
273 openElements.add(element);
274 return element;
275 }
276
277 Element insertElementTable(token) {
278 /// Create an element and insert it into the tree
279 var element = createElement(token);
280 if (!tableInsertModeElements.contains(openElements.last.localName)) {
281 return insertElementNormal(token);
282 } else {
283 // We should be in the InTable mode. This means we want to do
284 // special magic element rearranging
285 var nodePos = getTableMisnestedNodePosition();
286 if (nodePos[1] == null) {
287 // TODO(jmesserly): I don't think this is reachable. If insertFromTable
288 // is true, there will be a <table> element open, and it always has a
289 // parent pointer.
290 nodePos[0].nodes.add(element);
291 } else {
292 nodePos[0].insertBefore(element, nodePos[1]);
293 }
294 openElements.add(element);
295 }
296 return element;
297 }
298
299 /// Insert text data.
300 void insertText(String data, FileSpan span) {
301 var parent = openElements.last;
302
303 if (!insertFromTable || insertFromTable &&
304 !tableInsertModeElements.contains(openElements.last.localName)) {
305 _insertText(parent, data, span);
306 } else {
307 // We should be in the InTable mode. This means we want to do
308 // special magic element rearranging
309 var nodePos = getTableMisnestedNodePosition();
310 _insertText(nodePos[0], data, span, nodePos[1]);
311 }
312 }
313
314 /// Insert [data] as text in the current node, positioned before the
315 /// start of node [refNode] or to the end of the node's text.
316 static void _insertText(Node parent, String data, FileSpan span,
317 [Element refNode]) {
318 var nodes = parent.nodes;
319 if (refNode == null) {
320 if (nodes.length > 0 && nodes.last is Text) {
321 Text last = nodes.last;
322 last.data = '${last.data}$data';
323
324 if (span != null) {
325 last.sourceSpan = span.file.span(last.sourceSpan.start.offset,
326 span.end.offset);
327 }
328 } else {
329 nodes.add(new Text(data)..sourceSpan = span);
330 }
331 } else {
332 int index = nodes.indexOf(refNode);
333 if (index > 0 && nodes[index - 1] is Text) {
334 Text last = nodes[index - 1];
335 last.data = '${last.data}$data';
336 } else {
337 nodes.insert(index, new Text(data)..sourceSpan = span);
338 }
339 }
340 }
341
342 /// Get the foster parent element, and sibling to insert before
343 /// (or null) when inserting a misnested table node
344 List<Node> getTableMisnestedNodePosition() {
345 // The foster parent element is the one which comes before the most
346 // recently opened table element
347 // XXX - this is really inelegant
348 Node lastTable = null;
349 Node fosterParent = null;
350 var insertBefore = null;
351 for (var elm in openElements.reversed) {
352 if (elm.localName == "table") {
353 lastTable = elm;
354 break;
355 }
356 }
357 if (lastTable != null) {
358 // XXX - we should really check that this parent is actually a
359 // node here
360 if (lastTable.parentNode != null) {
361 fosterParent = lastTable.parentNode;
362 insertBefore = lastTable;
363 } else {
364 fosterParent = openElements[openElements.indexOf(lastTable) - 1];
365 }
366 } else {
367 fosterParent = openElements[0];
368 }
369 return [fosterParent, insertBefore];
370 }
371
372 void generateImpliedEndTags([String exclude]) {
373 var name = openElements.last.localName;
374 // XXX td, th and tr are not actually needed
375 if (name != exclude && const ["dd", "dt", "li", "option", "optgroup", "p",
376 "rp", "rt"].contains(name)) {
377 openElements.removeLast();
378 // XXX This is not entirely what the specification says. We should
379 // investigate it more closely.
380 generateImpliedEndTags(exclude);
381 }
382 }
383
384 /// Return the final tree.
385 Document getDocument() => document;
386
387 /// Return the final fragment.
388 DocumentFragment getFragment() {
389 //XXX assert innerHTML
390 var fragment = new DocumentFragment();
391 openElements[0].reparentChildren(fragment);
392 return fragment;
393 }
394 }
OLDNEW
« no previous file with comments | « pkg/third_party/html5lib/lib/src/tokenizer.dart ('k') | pkg/third_party/html5lib/lib/src/utils.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698