| 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 |