OLD | NEW |
1 /** Internals to the tree builders. */ | 1 /// Internals to the tree builders. |
2 library treebuilder; | 2 library treebuilder; |
3 | 3 |
4 import 'dart:collection'; | 4 import 'dart:collection'; |
5 import 'package:html5lib/dom.dart'; | 5 import 'package:html5lib/dom.dart'; |
6 import 'package:source_maps/span.dart' show FileSpan; | 6 import 'package:source_maps/span.dart' show FileSpan; |
7 import 'constants.dart'; | 7 import 'constants.dart'; |
8 import 'list_proxy.dart'; | 8 import 'list_proxy.dart'; |
9 import 'token.dart'; | 9 import 'token.dart'; |
10 import 'utils.dart'; | 10 import 'utils.dart'; |
11 | 11 |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
59 } | 59 } |
60 return true; | 60 return true; |
61 } | 61 } |
62 | 62 |
63 | 63 |
64 bool _nodesEqual(Node node1, Node node2) { | 64 bool _nodesEqual(Node node1, Node node2) { |
65 return node1.nameTuple == node2.nameTuple && | 65 return node1.nameTuple == node2.nameTuple && |
66 _mapEquals(node1.attributes, node2.attributes); | 66 _mapEquals(node1.attributes, node2.attributes); |
67 } | 67 } |
68 | 68 |
69 /** Basic treebuilder implementation. */ | 69 /// Basic treebuilder implementation. |
70 class TreeBuilder { | 70 class TreeBuilder { |
71 final String defaultNamespace; | 71 final String defaultNamespace; |
72 | 72 |
73 Document document; | 73 Document document; |
74 | 74 |
75 final openElements = <Node>[]; | 75 final openElements = <Node>[]; |
76 | 76 |
77 final activeFormattingElements = new ActiveFormattingElements(); | 77 final activeFormattingElements = new ActiveFormattingElements(); |
78 | 78 |
79 Node headPointer; | 79 Node headPointer; |
80 | 80 |
81 Node formPointer; | 81 Node formPointer; |
82 | 82 |
83 /** | 83 /// Switch the function used to insert an element from the |
84 * Switch the function used to insert an element from the | 84 /// normal one to the misnested table one and back again |
85 * normal one to the misnested table one and back again | |
86 */ | |
87 bool insertFromTable; | 85 bool insertFromTable; |
88 | 86 |
89 TreeBuilder(bool namespaceHTMLElements) | 87 TreeBuilder(bool namespaceHTMLElements) |
90 : defaultNamespace = namespaceHTMLElements ? Namespaces.html : null { | 88 : defaultNamespace = namespaceHTMLElements ? Namespaces.html : null { |
91 reset(); | 89 reset(); |
92 } | 90 } |
93 | 91 |
94 void reset() { | 92 void reset() { |
95 openElements.clear(); | 93 openElements.clear(); |
96 activeFormattingElements.clear(); | 94 activeFormattingElements.clear(); |
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
205 } | 203 } |
206 } | 204 } |
207 | 205 |
208 void clearActiveFormattingElements() { | 206 void clearActiveFormattingElements() { |
209 var entry = activeFormattingElements.removeLast(); | 207 var entry = activeFormattingElements.removeLast(); |
210 while (activeFormattingElements.length > 0 && entry != Marker) { | 208 while (activeFormattingElements.length > 0 && entry != Marker) { |
211 entry = activeFormattingElements.removeLast(); | 209 entry = activeFormattingElements.removeLast(); |
212 } | 210 } |
213 } | 211 } |
214 | 212 |
215 /** | 213 /// Check if an element exists between the end of the active |
216 * Check if an element exists between the end of the active | 214 /// formatting elements and the last marker. If it does, return it, else |
217 * formatting elements and the last marker. If it does, return it, else | 215 /// return null. |
218 * return null. | |
219 */ | |
220 Node elementInActiveFormattingElements(String name) { | 216 Node elementInActiveFormattingElements(String name) { |
221 for (Node item in activeFormattingElements.reversed) { | 217 for (Node item in activeFormattingElements.reversed) { |
222 // Check for Marker first because if it's a Marker it doesn't have a | 218 // Check for Marker first because if it's a Marker it doesn't have a |
223 // name attribute. | 219 // name attribute. |
224 if (item == Marker) { | 220 if (item == Marker) { |
225 break; | 221 break; |
226 } else if (item.tagName == name) { | 222 } else if (item.tagName == name) { |
227 return item; | 223 return item; |
228 } | 224 } |
229 } | 225 } |
(...skipping 12 matching lines...) Expand all Loading... |
242 document.nodes.add(doctype); | 238 document.nodes.add(doctype); |
243 } | 239 } |
244 | 240 |
245 void insertComment(StringToken token, [Node parent]) { | 241 void insertComment(StringToken token, [Node parent]) { |
246 if (parent == null) { | 242 if (parent == null) { |
247 parent = openElements.last; | 243 parent = openElements.last; |
248 } | 244 } |
249 parent.nodes.add(new Comment(token.data)..sourceSpan = token.span); | 245 parent.nodes.add(new Comment(token.data)..sourceSpan = token.span); |
250 } | 246 } |
251 | 247 |
252 /** Create an element but don't insert it anywhere */ | 248 /// Create an element but don't insert it anywhere |
253 Element createElement(StartTagToken token) { | 249 Element createElement(StartTagToken token) { |
254 var name = token.name; | 250 var name = token.name; |
255 var namespace = token.namespace; | 251 var namespace = token.namespace; |
256 if (namespace == null) namespace = defaultNamespace; | 252 if (namespace == null) namespace = defaultNamespace; |
257 var element = new Element(name, namespace) | 253 var element = new Element(name, namespace) |
258 ..attributes = token.data | 254 ..attributes = token.data |
259 ..sourceSpan = token.span; | 255 ..sourceSpan = token.span; |
260 return element; | 256 return element; |
261 } | 257 } |
262 | 258 |
263 Element insertElement(StartTagToken token) { | 259 Element insertElement(StartTagToken token) { |
264 if (insertFromTable) return insertElementTable(token); | 260 if (insertFromTable) return insertElementTable(token); |
265 return insertElementNormal(token); | 261 return insertElementNormal(token); |
266 } | 262 } |
267 | 263 |
268 Element insertElementNormal(StartTagToken token) { | 264 Element insertElementNormal(StartTagToken token) { |
269 var name = token.name; | 265 var name = token.name; |
270 var namespace = token.namespace; | 266 var namespace = token.namespace; |
271 if (namespace == null) namespace = defaultNamespace; | 267 if (namespace == null) namespace = defaultNamespace; |
272 var element = new Element(name, namespace) | 268 var element = new Element(name, namespace) |
273 ..attributes = token.data | 269 ..attributes = token.data |
274 ..sourceSpan = token.span; | 270 ..sourceSpan = token.span; |
275 openElements.last.nodes.add(element); | 271 openElements.last.nodes.add(element); |
276 openElements.add(element); | 272 openElements.add(element); |
277 return element; | 273 return element; |
278 } | 274 } |
279 | 275 |
280 Element insertElementTable(token) { | 276 Element insertElementTable(token) { |
281 /** Create an element and insert it into the tree */ | 277 /// Create an element and insert it into the tree |
282 var element = createElement(token); | 278 var element = createElement(token); |
283 if (!tableInsertModeElements.contains(openElements.last.tagName)) { | 279 if (!tableInsertModeElements.contains(openElements.last.tagName)) { |
284 return insertElementNormal(token); | 280 return insertElementNormal(token); |
285 } else { | 281 } else { |
286 // We should be in the InTable mode. This means we want to do | 282 // We should be in the InTable mode. This means we want to do |
287 // special magic element rearranging | 283 // special magic element rearranging |
288 var nodePos = getTableMisnestedNodePosition(); | 284 var nodePos = getTableMisnestedNodePosition(); |
289 if (nodePos[1] == null) { | 285 if (nodePos[1] == null) { |
290 // TODO(jmesserly): I don't think this is reachable. If insertFromTable | 286 // TODO(jmesserly): I don't think this is reachable. If insertFromTable |
291 // is true, there will be a <table> element open, and it always has a | 287 // is true, there will be a <table> element open, and it always has a |
292 // parent pointer. | 288 // parent pointer. |
293 nodePos[0].nodes.add(element); | 289 nodePos[0].nodes.add(element); |
294 } else { | 290 } else { |
295 nodePos[0].insertBefore(element, nodePos[1]); | 291 nodePos[0].insertBefore(element, nodePos[1]); |
296 } | 292 } |
297 openElements.add(element); | 293 openElements.add(element); |
298 } | 294 } |
299 return element; | 295 return element; |
300 } | 296 } |
301 | 297 |
302 /** Insert text data. */ | 298 /// Insert text data. |
303 void insertText(String data, FileSpan span) { | 299 void insertText(String data, FileSpan span) { |
304 var parent = openElements.last; | 300 var parent = openElements.last; |
305 | 301 |
306 if (!insertFromTable || insertFromTable && | 302 if (!insertFromTable || insertFromTable && |
307 !tableInsertModeElements.contains(openElements.last.tagName)) { | 303 !tableInsertModeElements.contains(openElements.last.tagName)) { |
308 _insertText(parent, data, span); | 304 _insertText(parent, data, span); |
309 } else { | 305 } else { |
310 // We should be in the InTable mode. This means we want to do | 306 // We should be in the InTable mode. This means we want to do |
311 // special magic element rearranging | 307 // special magic element rearranging |
312 var nodePos = getTableMisnestedNodePosition(); | 308 var nodePos = getTableMisnestedNodePosition(); |
313 _insertText(nodePos[0], data, span, nodePos[1]); | 309 _insertText(nodePos[0], data, span, nodePos[1]); |
314 } | 310 } |
315 } | 311 } |
316 | 312 |
317 /** | 313 /// Insert [data] as text in the current node, positioned before the |
318 * Insert [data] as text in the current node, positioned before the | 314 /// start of node [refNode] or to the end of the node's text. |
319 * start of node [refNode] or to the end of the node's text. | |
320 */ | |
321 static void _insertText(Node parent, String data, FileSpan span, | 315 static void _insertText(Node parent, String data, FileSpan span, |
322 [Element refNode]) { | 316 [Element refNode]) { |
323 var nodes = parent.nodes; | 317 var nodes = parent.nodes; |
324 if (refNode == null) { | 318 if (refNode == null) { |
325 if (nodes.length > 0 && nodes.last is Text) { | 319 if (nodes.length > 0 && nodes.last is Text) { |
326 Text last = nodes.last; | 320 Text last = nodes.last; |
327 last.data = '${last.data}$data'; | 321 last.data = '${last.data}$data'; |
328 | 322 |
329 if (span != null) { | 323 if (span != null) { |
330 last.sourceSpan = span.file.span(last.sourceSpan.start.offset, | 324 last.sourceSpan = span.file.span(last.sourceSpan.start.offset, |
331 span.end.offset); | 325 span.end.offset); |
332 } | 326 } |
333 } else { | 327 } else { |
334 nodes.add(new Text(data)..sourceSpan = span); | 328 nodes.add(new Text(data)..sourceSpan = span); |
335 } | 329 } |
336 } else { | 330 } else { |
337 int index = nodes.indexOf(refNode); | 331 int index = nodes.indexOf(refNode); |
338 if (index > 0 && nodes[index - 1] is Text) { | 332 if (index > 0 && nodes[index - 1] is Text) { |
339 Text last = nodes[index - 1]; | 333 Text last = nodes[index - 1]; |
340 last.data = '${last.data}$data'; | 334 last.data = '${last.data}$data'; |
341 } else { | 335 } else { |
342 nodes.insert(index, new Text(data)..sourceSpan = span); | 336 nodes.insert(index, new Text(data)..sourceSpan = span); |
343 } | 337 } |
344 } | 338 } |
345 } | 339 } |
346 | 340 |
347 /** | 341 /// Get the foster parent element, and sibling to insert before |
348 * Get the foster parent element, and sibling to insert before | 342 /// (or null) when inserting a misnested table node |
349 * (or null) when inserting a misnested table node | |
350 */ | |
351 List<Node> getTableMisnestedNodePosition() { | 343 List<Node> getTableMisnestedNodePosition() { |
352 // The foster parent element is the one which comes before the most | 344 // The foster parent element is the one which comes before the most |
353 // recently opened table element | 345 // recently opened table element |
354 // XXX - this is really inelegant | 346 // XXX - this is really inelegant |
355 Node lastTable = null; | 347 Node lastTable = null; |
356 Node fosterParent = null; | 348 Node fosterParent = null; |
357 var insertBefore = null; | 349 var insertBefore = null; |
358 for (Node elm in openElements.reversed) { | 350 for (Node elm in openElements.reversed) { |
359 if (elm.tagName == "table") { | 351 if (elm.tagName == "table") { |
360 lastTable = elm; | 352 lastTable = elm; |
(...skipping 20 matching lines...) Expand all Loading... |
381 // XXX td, th and tr are not actually needed | 373 // XXX td, th and tr are not actually needed |
382 if (name != exclude && const ["dd", "dt", "li", "option", "optgroup", "p", | 374 if (name != exclude && const ["dd", "dt", "li", "option", "optgroup", "p", |
383 "rp", "rt"].contains(name)) { | 375 "rp", "rt"].contains(name)) { |
384 openElements.removeLast(); | 376 openElements.removeLast(); |
385 // XXX This is not entirely what the specification says. We should | 377 // XXX This is not entirely what the specification says. We should |
386 // investigate it more closely. | 378 // investigate it more closely. |
387 generateImpliedEndTags(exclude); | 379 generateImpliedEndTags(exclude); |
388 } | 380 } |
389 } | 381 } |
390 | 382 |
391 /** Return the final tree. */ | 383 /// Return the final tree. |
392 Document getDocument() => document; | 384 Document getDocument() => document; |
393 | 385 |
394 /** Return the final fragment. */ | 386 /// Return the final fragment. |
395 DocumentFragment getFragment() { | 387 DocumentFragment getFragment() { |
396 //XXX assert innerHTML | 388 //XXX assert innerHTML |
397 var fragment = new DocumentFragment(); | 389 var fragment = new DocumentFragment(); |
398 openElements[0].reparentChildren(fragment); | 390 openElements[0].reparentChildren(fragment); |
399 return fragment; | 391 return fragment; |
400 } | 392 } |
401 } | 393 } |
OLD | NEW |