| OLD | NEW |
| (Empty) |
| 1 // This code was auto-generated, is not intended to be edited, and is subject to | |
| 2 // significant change. Please see the README file for more information. | |
| 3 library engine.html; | |
| 4 import 'dart:collection'; | |
| 5 import 'java_core.dart'; | |
| 6 import 'java_engine.dart'; | |
| 7 import 'source.dart'; | |
| 8 import 'element.dart' show HtmlElementImpl; | |
| 9 import 'engine.dart' show AnalysisEngine; | |
| 10 /** | |
| 11 * Instances of the class `Token` represent a token that was scanned from the in
put. Each | |
| 12 * token knows which token follows it, acting as the head of a linked list of to
kens. | |
| 13 * | |
| 14 * @coverage dart.engine.html | |
| 15 */ | |
| 16 class Token { | |
| 17 | |
| 18 /** | |
| 19 * The offset from the beginning of the file to the first character in the tok
en. | |
| 20 */ | |
| 21 int offset = 0; | |
| 22 | |
| 23 /** | |
| 24 * The previous token in the token stream. | |
| 25 */ | |
| 26 Token previous; | |
| 27 | |
| 28 /** | |
| 29 * The next token in the token stream. | |
| 30 */ | |
| 31 Token next; | |
| 32 | |
| 33 /** | |
| 34 * The type of the token. | |
| 35 */ | |
| 36 TokenType type; | |
| 37 | |
| 38 /** | |
| 39 * The lexeme represented by this token. | |
| 40 */ | |
| 41 String lexeme; | |
| 42 | |
| 43 /** | |
| 44 * Initialize a newly created token. | |
| 45 * | |
| 46 * @param type the token type (not `null`) | |
| 47 * @param offset the offset from the beginning of the file to the first charac
ter in the token | |
| 48 */ | |
| 49 Token.con1(TokenType type, int offset) : this.con2(type, offset, type.lexeme); | |
| 50 | |
| 51 /** | |
| 52 * Initialize a newly created token. | |
| 53 * | |
| 54 * @param type the token type (not `null`) | |
| 55 * @param offset the offset from the beginning of the file to the first charac
ter in the token | |
| 56 * @param value the lexeme represented by this token (not `null`) | |
| 57 */ | |
| 58 Token.con2(TokenType type, int offset, String value) { | |
| 59 this.type = type; | |
| 60 this.lexeme = StringUtilities.intern(value); | |
| 61 this.offset = offset; | |
| 62 } | |
| 63 | |
| 64 /** | |
| 65 * Return the offset from the beginning of the file to the character after las
t character of the | |
| 66 * token. | |
| 67 * | |
| 68 * @return the offset from the beginning of the file to the first character af
ter last character | |
| 69 * of the token | |
| 70 */ | |
| 71 int get end => offset + length; | |
| 72 | |
| 73 /** | |
| 74 * Return the number of characters in the node's source range. | |
| 75 * | |
| 76 * @return the number of characters in the node's source range | |
| 77 */ | |
| 78 int get length => lexeme.length; | |
| 79 | |
| 80 /** | |
| 81 * Return `true` if this token is a synthetic token. A synthetic token is a to
ken that was | |
| 82 * introduced by the parser in order to recover from an error in the code. Syn
thetic tokens always | |
| 83 * have a length of zero (`0`). | |
| 84 * | |
| 85 * @return `true` if this token is a synthetic token | |
| 86 */ | |
| 87 bool get isSynthetic => length == 0; | |
| 88 | |
| 89 /** | |
| 90 * Set the next token in the token stream to the given token. This has the sid
e-effect of setting | |
| 91 * this token to be the previous token for the given token. | |
| 92 * | |
| 93 * @param token the next token in the token stream | |
| 94 * @return the token that was passed in | |
| 95 */ | |
| 96 Token setNext(Token token) { | |
| 97 next = token; | |
| 98 token.previous = this; | |
| 99 return token; | |
| 100 } | |
| 101 String toString() => lexeme; | |
| 102 } | |
| 103 /** | |
| 104 * Instances of `HtmlParseResult` hold the result of parsing an HTML file. | |
| 105 * | |
| 106 * @coverage dart.engine.html | |
| 107 */ | |
| 108 class HtmlParseResult extends HtmlScanResult { | |
| 109 | |
| 110 /** | |
| 111 * The unit containing the parsed information (not `null`). | |
| 112 */ | |
| 113 HtmlUnit htmlUnit; | |
| 114 HtmlParseResult(int modificationTime, Token token, List<int> lineStarts, HtmlU
nit unit) : super(modificationTime, token, lineStarts) { | |
| 115 this.htmlUnit = unit; | |
| 116 } | |
| 117 } | |
| 118 /** | |
| 119 * Instances of the class `RecursiveXmlVisitor` implement an XML visitor that wi
ll recursively | |
| 120 * visit all of the nodes in an XML structure. For example, using an instance of
this class to visit | |
| 121 * a [XmlTagNode] will also cause all of the contained [XmlAttributeNode]s and | |
| 122 * [XmlTagNode]s to be visited. | |
| 123 * | |
| 124 * Subclasses that override a visit method must either invoke the overridden vis
it method or must | |
| 125 * explicitly ask the visited node to visit its children. Failure to do so will
cause the children | |
| 126 * of the visited node to not be visited. | |
| 127 * | |
| 128 * @coverage dart.engine.html | |
| 129 */ | |
| 130 class RecursiveXmlVisitor<R> implements XmlVisitor<R> { | |
| 131 R visitHtmlUnit(HtmlUnit node) { | |
| 132 node.visitChildren(this); | |
| 133 return null; | |
| 134 } | |
| 135 R visitXmlAttributeNode(XmlAttributeNode node) { | |
| 136 node.visitChildren(this); | |
| 137 return null; | |
| 138 } | |
| 139 R visitXmlTagNode(XmlTagNode node) { | |
| 140 node.visitChildren(this); | |
| 141 return null; | |
| 142 } | |
| 143 } | |
| 144 /** | |
| 145 * The abstract class `XmlNode` defines behavior common to all XML/HTML nodes. | |
| 146 * | |
| 147 * @coverage dart.engine.html | |
| 148 */ | |
| 149 abstract class XmlNode { | |
| 150 | |
| 151 /** | |
| 152 * The parent of the node, or `null` if the node is the root of an AST structu
re. | |
| 153 */ | |
| 154 XmlNode _parent; | |
| 155 | |
| 156 /** | |
| 157 * Use the given visitor to visit this node. | |
| 158 * | |
| 159 * @param visitor the visitor that will visit this node | |
| 160 * @return the value returned by the visitor as a result of visiting this node | |
| 161 */ | |
| 162 accept(XmlVisitor visitor); | |
| 163 | |
| 164 /** | |
| 165 * Return the first token included in this node's source range. | |
| 166 * | |
| 167 * @return the first token or `null` if none | |
| 168 */ | |
| 169 Token get beginToken; | |
| 170 | |
| 171 /** | |
| 172 * Return the offset of the character immediately following the last character
of this node's | |
| 173 * source range. This is equivalent to `node.getOffset() + node.getLength()`.
For an html | |
| 174 * unit this will be equal to the length of the unit's source. | |
| 175 * | |
| 176 * @return the offset of the character just past the node's source range | |
| 177 */ | |
| 178 int get end => offset + length; | |
| 179 | |
| 180 /** | |
| 181 * Return the last token included in this node's source range. | |
| 182 * | |
| 183 * @return the last token or `null` if none | |
| 184 */ | |
| 185 Token get endToken; | |
| 186 | |
| 187 /** | |
| 188 * Return the number of characters in the node's source range. | |
| 189 * | |
| 190 * @return the number of characters in the node's source range | |
| 191 */ | |
| 192 int get length { | |
| 193 Token beginToken = this.beginToken; | |
| 194 Token endToken = this.endToken; | |
| 195 if (beginToken == null || endToken == null) { | |
| 196 return -1; | |
| 197 } | |
| 198 return endToken.offset + endToken.length - beginToken.offset; | |
| 199 } | |
| 200 | |
| 201 /** | |
| 202 * Return the offset from the beginning of the file to the first character in
the node's source | |
| 203 * range. | |
| 204 * | |
| 205 * @return the offset from the beginning of the file to the first character in
the node's source | |
| 206 * range | |
| 207 */ | |
| 208 int get offset { | |
| 209 Token beginToken = this.beginToken; | |
| 210 if (beginToken == null) { | |
| 211 return -1; | |
| 212 } | |
| 213 return this.beginToken.offset; | |
| 214 } | |
| 215 | |
| 216 /** | |
| 217 * Return this node's parent node, or `null` if this node is the root of an AS
T structure. | |
| 218 * | |
| 219 * Note that the relationship between an AST node and its parent node may chan
ge over the lifetime | |
| 220 * of a node. | |
| 221 * | |
| 222 * @return the parent of this node, or `null` if none | |
| 223 */ | |
| 224 XmlNode get parent => _parent; | |
| 225 String toString() { | |
| 226 PrintStringWriter writer = new PrintStringWriter(); | |
| 227 accept(new ToSourceVisitor(writer)); | |
| 228 return writer.toString(); | |
| 229 } | |
| 230 | |
| 231 /** | |
| 232 * Use the given visitor to visit all of the children of this node. The childr
en will be visited | |
| 233 * in source order. | |
| 234 * | |
| 235 * @param visitor the visitor that will be used to visit the children of this
node | |
| 236 */ | |
| 237 void visitChildren(XmlVisitor visitor); | |
| 238 | |
| 239 /** | |
| 240 * Make this node the parent of the given child nodes. | |
| 241 * | |
| 242 * @param children the nodes that will become the children of this node | |
| 243 * @return the nodes that were made children of this node | |
| 244 */ | |
| 245 List becomeParentOf(List children) { | |
| 246 if (children != null) { | |
| 247 for (JavaIterator iter = new JavaIterator(children); iter.hasNext;) { | |
| 248 XmlNode node = iter.next(); | |
| 249 node.parent = this; | |
| 250 } | |
| 251 return new List.from(children); | |
| 252 } | |
| 253 return children; | |
| 254 } | |
| 255 | |
| 256 /** | |
| 257 * Make this node the parent of the given child node. | |
| 258 * | |
| 259 * @param child the node that will become a child of this node | |
| 260 * @return the node that was made a child of this node | |
| 261 */ | |
| 262 XmlNode becomeParentOf2(XmlNode child) { | |
| 263 if (child != null) { | |
| 264 XmlNode node = child; | |
| 265 node.parent = this; | |
| 266 } | |
| 267 return child; | |
| 268 } | |
| 269 | |
| 270 /** | |
| 271 * This method exists for debugging purposes only. | |
| 272 */ | |
| 273 void appendIdentifier(JavaStringBuilder builder, XmlNode node) { | |
| 274 if (node is XmlTagNode) { | |
| 275 builder.append(((node as XmlTagNode)).tag.lexeme); | |
| 276 } else if (node is XmlAttributeNode) { | |
| 277 builder.append(((node as XmlAttributeNode)).name.lexeme); | |
| 278 } else { | |
| 279 builder.append("htmlUnit"); | |
| 280 } | |
| 281 } | |
| 282 | |
| 283 /** | |
| 284 * This method exists for debugging purposes only. | |
| 285 */ | |
| 286 String buildRecursiveStructureMessage(XmlNode newParent) { | |
| 287 JavaStringBuilder builder = new JavaStringBuilder(); | |
| 288 builder.append("Attempt to create recursive structure: "); | |
| 289 XmlNode current = newParent; | |
| 290 while (current != null) { | |
| 291 if (current != newParent) { | |
| 292 builder.append(" -> "); | |
| 293 } | |
| 294 if (identical(current, this)) { | |
| 295 builder.appendChar(0x2A); | |
| 296 appendIdentifier(builder, current); | |
| 297 builder.appendChar(0x2A); | |
| 298 } else { | |
| 299 appendIdentifier(builder, current); | |
| 300 } | |
| 301 current = current.parent; | |
| 302 } | |
| 303 return builder.toString(); | |
| 304 } | |
| 305 | |
| 306 /** | |
| 307 * Set the parent of this node to the given node. | |
| 308 * | |
| 309 * @param newParent the node that is to be made the parent of this node | |
| 310 */ | |
| 311 void set parent(XmlNode newParent) { | |
| 312 XmlNode current = newParent; | |
| 313 while (current != null) { | |
| 314 if (identical(current, this)) { | |
| 315 AnalysisEngine.instance.logger.logError3(new IllegalArgumentException(bu
ildRecursiveStructureMessage(newParent))); | |
| 316 return; | |
| 317 } | |
| 318 current = current.parent; | |
| 319 } | |
| 320 _parent = newParent; | |
| 321 } | |
| 322 } | |
| 323 /** | |
| 324 * Instances of the class `SimpleXmlVisitor` implement an AST visitor that will
do nothing | |
| 325 * when visiting an AST node. It is intended to be a superclass for classes that
use the visitor | |
| 326 * pattern primarily as a dispatch mechanism (and hence don't need to recursivel
y visit a whole | |
| 327 * structure) and that only need to visit a small number of node types. | |
| 328 */ | |
| 329 class SimpleXmlVisitor<R> implements XmlVisitor<R> { | |
| 330 R visitHtmlUnit(HtmlUnit htmlUnit) => null; | |
| 331 R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode) => null; | |
| 332 R visitXmlTagNode(XmlTagNode xmlTagNode) => null; | |
| 333 } | |
| 334 /** | |
| 335 * The abstract class `AbstractScanner` implements a scanner for HTML code. Subc
lasses are | |
| 336 * required to implement the interface used to access the characters being scann
ed. | |
| 337 * | |
| 338 * @coverage dart.engine.html | |
| 339 */ | |
| 340 abstract class AbstractScanner { | |
| 341 static List<String> _NO_PASS_THROUGH_ELEMENTS = <String> []; | |
| 342 | |
| 343 /** | |
| 344 * The source being scanned. | |
| 345 */ | |
| 346 Source source; | |
| 347 | |
| 348 /** | |
| 349 * The token pointing to the head of the linked list of tokens. | |
| 350 */ | |
| 351 Token _tokens; | |
| 352 | |
| 353 /** | |
| 354 * The last token that was scanned. | |
| 355 */ | |
| 356 Token _tail; | |
| 357 | |
| 358 /** | |
| 359 * A list containing the offsets of the first character of each line in the so
urce code. | |
| 360 */ | |
| 361 List<int> _lineStarts = new List<int>(); | |
| 362 | |
| 363 /** | |
| 364 * An array of element tags for which the content between tags should be consi
der a single token. | |
| 365 */ | |
| 366 List<String> _passThroughElements = _NO_PASS_THROUGH_ELEMENTS; | |
| 367 | |
| 368 /** | |
| 369 * Initialize a newly created scanner. | |
| 370 * | |
| 371 * @param source the source being scanned | |
| 372 */ | |
| 373 AbstractScanner(Source source) { | |
| 374 this.source = source; | |
| 375 _tokens = new Token.con1(TokenType.EOF, -1); | |
| 376 _tokens.setNext(_tokens); | |
| 377 _tail = _tokens; | |
| 378 recordStartOfLine(); | |
| 379 } | |
| 380 | |
| 381 /** | |
| 382 * Return an array containing the offsets of the first character of each line
in the source code. | |
| 383 * | |
| 384 * @return an array containing the offsets of the first character of each line
in the source code | |
| 385 */ | |
| 386 List<int> get lineStarts => _lineStarts; | |
| 387 | |
| 388 /** | |
| 389 * Return the current offset relative to the beginning of the file. Return the
initial offset if | |
| 390 * the scanner has not yet scanned the source code, and one (1) past the end o
f the source code if | |
| 391 * the source code has been scanned. | |
| 392 * | |
| 393 * @return the current offset of the scanner in the source | |
| 394 */ | |
| 395 int get offset; | |
| 396 | |
| 397 /** | |
| 398 * Set array of element tags for which the content between tags should be cons
ider a single token. | |
| 399 */ | |
| 400 void set passThroughElements(List<String> passThroughElements) { | |
| 401 this._passThroughElements = passThroughElements != null ? passThroughElement
s : _NO_PASS_THROUGH_ELEMENTS; | |
| 402 } | |
| 403 | |
| 404 /** | |
| 405 * Scan the source code to produce a list of tokens representing the source. | |
| 406 * | |
| 407 * @return the first token in the list of tokens that were produced | |
| 408 */ | |
| 409 Token tokenize() { | |
| 410 scan(); | |
| 411 appendEofToken(); | |
| 412 return firstToken(); | |
| 413 } | |
| 414 | |
| 415 /** | |
| 416 * Advance the current position and return the character at the new current po
sition. | |
| 417 * | |
| 418 * @return the character at the new current position | |
| 419 */ | |
| 420 int advance(); | |
| 421 | |
| 422 /** | |
| 423 * Return the substring of the source code between the start offset and the mo
dified current | |
| 424 * position. The current position is modified by adding the end delta. | |
| 425 * | |
| 426 * @param start the offset to the beginning of the string, relative to the sta
rt of the file | |
| 427 * @param endDelta the number of character after the current location to be in
cluded in the | |
| 428 * string, or the number of characters before the current location to
be excluded if the | |
| 429 * offset is negative | |
| 430 * @return the specified substring of the source code | |
| 431 */ | |
| 432 String getString(int start, int endDelta); | |
| 433 | |
| 434 /** | |
| 435 * Return the character at the current position without changing the current p
osition. | |
| 436 * | |
| 437 * @return the character at the current position | |
| 438 */ | |
| 439 int peek(); | |
| 440 | |
| 441 /** | |
| 442 * Record the fact that we are at the beginning of a new line in the source. | |
| 443 */ | |
| 444 void recordStartOfLine() { | |
| 445 _lineStarts.add(offset); | |
| 446 } | |
| 447 void appendEofToken() { | |
| 448 Token eofToken = new Token.con1(TokenType.EOF, offset); | |
| 449 eofToken.setNext(eofToken); | |
| 450 _tail = _tail.setNext(eofToken); | |
| 451 } | |
| 452 Token emit(Token token) { | |
| 453 _tail.setNext(token); | |
| 454 _tail = token; | |
| 455 return token; | |
| 456 } | |
| 457 Token emit2(TokenType type, int start) => emit(new Token.con1(type, start)); | |
| 458 Token emit3(TokenType type, int start, int count) => emit(new Token.con2(type,
start, getString(start, count))); | |
| 459 Token firstToken() => _tokens.next; | |
| 460 int recordStartOfLineAndAdvance(int c) { | |
| 461 if (c == 0xD) { | |
| 462 c = advance(); | |
| 463 if (c == 0xA) { | |
| 464 c = advance(); | |
| 465 } | |
| 466 recordStartOfLine(); | |
| 467 } else if (c == 0xA) { | |
| 468 c = advance(); | |
| 469 recordStartOfLine(); | |
| 470 } else { | |
| 471 c = advance(); | |
| 472 } | |
| 473 return c; | |
| 474 } | |
| 475 void scan() { | |
| 476 bool inBrackets = false; | |
| 477 String endPassThrough = null; | |
| 478 int c = advance(); | |
| 479 while (c >= 0) { | |
| 480 int start = offset; | |
| 481 if (c == 0x3C) { | |
| 482 c = advance(); | |
| 483 if (c == 0x21) { | |
| 484 c = advance(); | |
| 485 if (c == 0x2D && peek() == 0x2D) { | |
| 486 c = advance(); | |
| 487 int dashCount = 1; | |
| 488 while (c >= 0) { | |
| 489 if (c == 0x2D) { | |
| 490 dashCount++; | |
| 491 } else if (c == 0x3E && dashCount >= 2) { | |
| 492 c = advance(); | |
| 493 break; | |
| 494 } else { | |
| 495 dashCount = 0; | |
| 496 } | |
| 497 c = recordStartOfLineAndAdvance(c); | |
| 498 } | |
| 499 emit3(TokenType.COMMENT, start, -1); | |
| 500 if (_tail.length < 7) { | |
| 501 } | |
| 502 } else { | |
| 503 while (c >= 0) { | |
| 504 if (c == 0x3E) { | |
| 505 c = advance(); | |
| 506 break; | |
| 507 } | |
| 508 c = recordStartOfLineAndAdvance(c); | |
| 509 } | |
| 510 emit3(TokenType.DECLARATION, start, -1); | |
| 511 if (!_tail.lexeme.endsWith(">")) { | |
| 512 } | |
| 513 } | |
| 514 } else if (c == 0x3F) { | |
| 515 while (c >= 0) { | |
| 516 if (c == 0x3F) { | |
| 517 c = advance(); | |
| 518 if (c == 0x3E) { | |
| 519 c = advance(); | |
| 520 break; | |
| 521 } | |
| 522 } else { | |
| 523 c = recordStartOfLineAndAdvance(c); | |
| 524 } | |
| 525 } | |
| 526 emit3(TokenType.DIRECTIVE, start, -1); | |
| 527 if (_tail.length < 4) { | |
| 528 } | |
| 529 } else if (c == 0x2F) { | |
| 530 emit2(TokenType.LT_SLASH, start); | |
| 531 inBrackets = true; | |
| 532 c = advance(); | |
| 533 } else { | |
| 534 inBrackets = true; | |
| 535 emit2(TokenType.LT, start); | |
| 536 while (Character.isWhitespace(c)) { | |
| 537 c = recordStartOfLineAndAdvance(c); | |
| 538 } | |
| 539 if (Character.isLetterOrDigit(c)) { | |
| 540 int tagStart = offset; | |
| 541 c = advance(); | |
| 542 while (Character.isLetterOrDigit(c) || c == 0x2D || c == 0x5F) { | |
| 543 c = advance(); | |
| 544 } | |
| 545 emit3(TokenType.TAG, tagStart, -1); | |
| 546 String tag = _tail.lexeme; | |
| 547 for (String str in _passThroughElements) { | |
| 548 if (str == tag) { | |
| 549 endPassThrough = "</${str}>"; | |
| 550 break; | |
| 551 } | |
| 552 } | |
| 553 } | |
| 554 } | |
| 555 } else if (c == 0x3E) { | |
| 556 emit2(TokenType.GT, start); | |
| 557 inBrackets = false; | |
| 558 c = advance(); | |
| 559 if (endPassThrough != null) { | |
| 560 bool endFound = false; | |
| 561 int len = endPassThrough.length; | |
| 562 int firstC = endPassThrough.codeUnitAt(0); | |
| 563 int index = 0; | |
| 564 int nextC = firstC; | |
| 565 while (c >= 0) { | |
| 566 if (c == nextC) { | |
| 567 index++; | |
| 568 if (index == len) { | |
| 569 endFound = true; | |
| 570 break; | |
| 571 } | |
| 572 nextC = endPassThrough.codeUnitAt(index); | |
| 573 } else if (c == firstC) { | |
| 574 index = 1; | |
| 575 nextC = endPassThrough.codeUnitAt(1); | |
| 576 } else { | |
| 577 index = 0; | |
| 578 nextC = firstC; | |
| 579 } | |
| 580 c = recordStartOfLineAndAdvance(c); | |
| 581 } | |
| 582 if (start + 1 < offset) { | |
| 583 if (endFound) { | |
| 584 emit3(TokenType.TEXT, start + 1, -len); | |
| 585 emit2(TokenType.LT_SLASH, offset - len + 1); | |
| 586 emit3(TokenType.TAG, offset - len + 3, -1); | |
| 587 } else { | |
| 588 emit3(TokenType.TEXT, start + 1, -1); | |
| 589 } | |
| 590 } | |
| 591 endPassThrough = null; | |
| 592 } | |
| 593 } else if (c == 0x2F && peek() == 0x3E) { | |
| 594 advance(); | |
| 595 emit2(TokenType.SLASH_GT, start); | |
| 596 inBrackets = false; | |
| 597 c = advance(); | |
| 598 } else if (!inBrackets) { | |
| 599 c = recordStartOfLineAndAdvance(c); | |
| 600 while (c != 0x3C && c >= 0) { | |
| 601 c = recordStartOfLineAndAdvance(c); | |
| 602 } | |
| 603 emit3(TokenType.TEXT, start, -1); | |
| 604 } else if (c == 0x22 || c == 0x27) { | |
| 605 int endQuote = c; | |
| 606 c = advance(); | |
| 607 while (c >= 0) { | |
| 608 if (c == endQuote) { | |
| 609 c = advance(); | |
| 610 break; | |
| 611 } | |
| 612 c = recordStartOfLineAndAdvance(c); | |
| 613 } | |
| 614 emit3(TokenType.STRING, start, -1); | |
| 615 } else if (c == 0x3D) { | |
| 616 emit2(TokenType.EQ, start); | |
| 617 c = advance(); | |
| 618 } else if (Character.isWhitespace(c)) { | |
| 619 do { | |
| 620 c = recordStartOfLineAndAdvance(c); | |
| 621 } while (Character.isWhitespace(c)); | |
| 622 } else if (Character.isLetterOrDigit(c)) { | |
| 623 c = advance(); | |
| 624 while (Character.isLetterOrDigit(c) || c == 0x2D || c == 0x5F) { | |
| 625 c = advance(); | |
| 626 } | |
| 627 emit3(TokenType.TAG, start, -1); | |
| 628 } else { | |
| 629 emit3(TokenType.TEXT, start, 0); | |
| 630 c = advance(); | |
| 631 } | |
| 632 } | |
| 633 } | |
| 634 } | |
| 635 /** | |
| 636 * Instances of `HtmlScanResult` hold the result of scanning an HTML file. | |
| 637 * | |
| 638 * @coverage dart.engine.html | |
| 639 */ | |
| 640 class HtmlScanResult { | |
| 641 | |
| 642 /** | |
| 643 * The time at which the contents of the source were last set. | |
| 644 */ | |
| 645 int modificationTime = 0; | |
| 646 | |
| 647 /** | |
| 648 * The first token in the token stream (not `null`). | |
| 649 */ | |
| 650 Token token; | |
| 651 | |
| 652 /** | |
| 653 * The line start information that was produced. | |
| 654 */ | |
| 655 List<int> lineStarts; | |
| 656 HtmlScanResult(int modificationTime, Token token, List<int> lineStarts) { | |
| 657 this.modificationTime = modificationTime; | |
| 658 this.token = token; | |
| 659 this.lineStarts = lineStarts; | |
| 660 } | |
| 661 } | |
| 662 /** | |
| 663 * Instances of the class `StringScanner` implement a scanner that reads from a
string. The | |
| 664 * scanning logic is in the superclass. | |
| 665 * | |
| 666 * @coverage dart.engine.html | |
| 667 */ | |
| 668 class StringScanner extends AbstractScanner { | |
| 669 | |
| 670 /** | |
| 671 * The string from which characters will be read. | |
| 672 */ | |
| 673 String _string; | |
| 674 | |
| 675 /** | |
| 676 * The number of characters in the string. | |
| 677 */ | |
| 678 int _stringLength = 0; | |
| 679 | |
| 680 /** | |
| 681 * The index, relative to the string, of the last character that was read. | |
| 682 */ | |
| 683 int _charOffset = 0; | |
| 684 | |
| 685 /** | |
| 686 * Initialize a newly created scanner to scan the characters in the given stri
ng. | |
| 687 * | |
| 688 * @param source the source being scanned | |
| 689 * @param string the string from which characters will be read | |
| 690 */ | |
| 691 StringScanner(Source source, String string) : super(source) { | |
| 692 this._string = string; | |
| 693 this._stringLength = string.length; | |
| 694 this._charOffset = -1; | |
| 695 } | |
| 696 int get offset => _charOffset; | |
| 697 void set offset(int offset) { | |
| 698 _charOffset = offset; | |
| 699 } | |
| 700 int advance() { | |
| 701 if (++_charOffset < _stringLength) { | |
| 702 return _string.codeUnitAt(_charOffset); | |
| 703 } | |
| 704 _charOffset = _stringLength; | |
| 705 return -1; | |
| 706 } | |
| 707 String getString(int start, int endDelta) => _string.substring(start, _charOff
set + 1 + endDelta); | |
| 708 int peek() { | |
| 709 if (_charOffset + 1 < _stringLength) { | |
| 710 return _string.codeUnitAt(_charOffset + 1); | |
| 711 } | |
| 712 return -1; | |
| 713 } | |
| 714 } | |
| 715 /** | |
| 716 * Instances of the class `CharBufferScanner` implement a scanner that reads fro
m a character | |
| 717 * buffer. The scanning logic is in the superclass. | |
| 718 * | |
| 719 * @coverage dart.engine.html | |
| 720 */ | |
| 721 class CharBufferScanner extends AbstractScanner { | |
| 722 | |
| 723 /** | |
| 724 * The buffer from which characters will be read. | |
| 725 */ | |
| 726 CharSequence _buffer; | |
| 727 | |
| 728 /** | |
| 729 * The number of characters in the buffer. | |
| 730 */ | |
| 731 int _bufferLength = 0; | |
| 732 | |
| 733 /** | |
| 734 * The index of the last character that was read. | |
| 735 */ | |
| 736 int _charOffset = 0; | |
| 737 | |
| 738 /** | |
| 739 * Initialize a newly created scanner to scan the characters in the given char
acter buffer. | |
| 740 * | |
| 741 * @param source the source being scanned | |
| 742 * @param buffer the buffer from which characters will be read | |
| 743 */ | |
| 744 CharBufferScanner(Source source, CharSequence buffer) : super(source) { | |
| 745 this._buffer = buffer; | |
| 746 this._bufferLength = buffer.length(); | |
| 747 this._charOffset = -1; | |
| 748 } | |
| 749 int get offset => _charOffset; | |
| 750 int advance() { | |
| 751 if (++_charOffset < _bufferLength) { | |
| 752 return _buffer.charAt(_charOffset); | |
| 753 } | |
| 754 _charOffset = _bufferLength; | |
| 755 return -1; | |
| 756 } | |
| 757 String getString(int start, int endDelta) => _buffer.subSequence(start, _charO
ffset + 1 + endDelta).toString(); | |
| 758 int peek() { | |
| 759 if (_charOffset + 1 < _bufferLength) { | |
| 760 return _buffer.charAt(_charOffset + 1); | |
| 761 } | |
| 762 return -1; | |
| 763 } | |
| 764 } | |
| 765 /** | |
| 766 * Instances of the class `ToSourceVisitor` write a source representation of a v
isited XML | |
| 767 * node (and all of it's children) to a writer. | |
| 768 * | |
| 769 * @coverage dart.engine.html | |
| 770 */ | |
| 771 class ToSourceVisitor implements XmlVisitor<Object> { | |
| 772 | |
| 773 /** | |
| 774 * The writer to which the source is to be written. | |
| 775 */ | |
| 776 PrintWriter _writer; | |
| 777 | |
| 778 /** | |
| 779 * Initialize a newly created visitor to write source code representing the vi
sited nodes to the | |
| 780 * given writer. | |
| 781 * | |
| 782 * @param writer the writer to which the source is to be written | |
| 783 */ | |
| 784 ToSourceVisitor(PrintWriter writer) { | |
| 785 this._writer = writer; | |
| 786 } | |
| 787 Object visitHtmlUnit(HtmlUnit node) { | |
| 788 for (XmlTagNode child in node.tagNodes) { | |
| 789 visit(child); | |
| 790 } | |
| 791 return null; | |
| 792 } | |
| 793 Object visitXmlAttributeNode(XmlAttributeNode node) { | |
| 794 String name = node.name.lexeme; | |
| 795 Token value = node.value; | |
| 796 if (name.length == 0) { | |
| 797 _writer.print("__"); | |
| 798 } else { | |
| 799 _writer.print(name); | |
| 800 } | |
| 801 _writer.print("="); | |
| 802 if (value == null) { | |
| 803 _writer.print("__"); | |
| 804 } else { | |
| 805 _writer.print(value.lexeme); | |
| 806 } | |
| 807 return null; | |
| 808 } | |
| 809 Object visitXmlTagNode(XmlTagNode node) { | |
| 810 _writer.print("<"); | |
| 811 String tagName = node.tag.lexeme; | |
| 812 _writer.print(tagName); | |
| 813 for (XmlAttributeNode attribute in node.attributes) { | |
| 814 _writer.print(" "); | |
| 815 visit(attribute); | |
| 816 } | |
| 817 _writer.print(node.attributeEnd.lexeme); | |
| 818 if (node.closingTag != null) { | |
| 819 for (XmlTagNode child in node.tagNodes) { | |
| 820 visit(child); | |
| 821 } | |
| 822 _writer.print("</"); | |
| 823 _writer.print(tagName); | |
| 824 _writer.print(">"); | |
| 825 } | |
| 826 return null; | |
| 827 } | |
| 828 | |
| 829 /** | |
| 830 * Safely visit the given node. | |
| 831 * | |
| 832 * @param node the node to be visited | |
| 833 */ | |
| 834 void visit(XmlNode node) { | |
| 835 if (node != null) { | |
| 836 node.accept(this); | |
| 837 } | |
| 838 } | |
| 839 } | |
| 840 /** | |
| 841 * The enumeration `TokenType` defines the types of tokens that can be returned
by the | |
| 842 * scanner. | |
| 843 * | |
| 844 * @coverage dart.engine.html | |
| 845 */ | |
| 846 class TokenType extends Enum<TokenType> { | |
| 847 | |
| 848 /** | |
| 849 * The type of the token that marks the end of the input. | |
| 850 */ | |
| 851 static final TokenType EOF = new TokenType_EOF('EOF', 0, ""); | |
| 852 static final TokenType EQ = new TokenType('EQ', 1, "="); | |
| 853 static final TokenType GT = new TokenType('GT', 2, ">"); | |
| 854 static final TokenType LT_SLASH = new TokenType('LT_SLASH', 3, "</"); | |
| 855 static final TokenType LT = new TokenType('LT', 4, "<"); | |
| 856 static final TokenType SLASH_GT = new TokenType('SLASH_GT', 5, "/>"); | |
| 857 static final TokenType COMMENT = new TokenType('COMMENT', 6, null); | |
| 858 static final TokenType DECLARATION = new TokenType('DECLARATION', 7, null); | |
| 859 static final TokenType DIRECTIVE = new TokenType('DIRECTIVE', 8, null); | |
| 860 static final TokenType STRING = new TokenType('STRING', 9, null); | |
| 861 static final TokenType TAG = new TokenType('TAG', 10, null); | |
| 862 static final TokenType TEXT = new TokenType('TEXT', 11, null); | |
| 863 static final List<TokenType> values = [ | |
| 864 EOF, | |
| 865 EQ, | |
| 866 GT, | |
| 867 LT_SLASH, | |
| 868 LT, | |
| 869 SLASH_GT, | |
| 870 COMMENT, | |
| 871 DECLARATION, | |
| 872 DIRECTIVE, | |
| 873 STRING, | |
| 874 TAG, | |
| 875 TEXT]; | |
| 876 | |
| 877 /** | |
| 878 * The lexeme that defines this type of token, or `null` if there is more than
one possible | |
| 879 * lexeme for this type of token. | |
| 880 */ | |
| 881 String lexeme; | |
| 882 TokenType(String name, int ordinal, String lexeme) : super(name, ordinal) { | |
| 883 this.lexeme = lexeme; | |
| 884 } | |
| 885 } | |
| 886 class TokenType_EOF extends TokenType { | |
| 887 TokenType_EOF(String name, int ordinal, String arg0) : super(name, ordinal, ar
g0); | |
| 888 String toString() => "-eof-"; | |
| 889 } | |
| 890 /** | |
| 891 * Instances of `XmlAttributeNode` represent name/value pairs owned by an [XmlTa
gNode]. | |
| 892 * | |
| 893 * @coverage dart.engine.html | |
| 894 */ | |
| 895 class XmlAttributeNode extends XmlNode { | |
| 896 Token name; | |
| 897 Token equals; | |
| 898 Token value; | |
| 899 | |
| 900 /** | |
| 901 * Construct a new instance representing an XML attribute. | |
| 902 * | |
| 903 * @param name the name token (not `null`). This may be a zero length token if
the attribute | |
| 904 * is badly formed. | |
| 905 * @param equals the equals sign or `null` if none | |
| 906 * @param value the value token (not `null`) | |
| 907 */ | |
| 908 XmlAttributeNode(Token name, Token equals, Token value) { | |
| 909 this.name = name; | |
| 910 this.equals = equals; | |
| 911 this.value = value; | |
| 912 } | |
| 913 accept(XmlVisitor visitor) => visitor.visitXmlAttributeNode(this); | |
| 914 Token get beginToken => name; | |
| 915 Token get endToken => value; | |
| 916 | |
| 917 /** | |
| 918 * Answer the lexeme for the value token without the leading and trailing quot
es. | |
| 919 * | |
| 920 * @return the text or `null` if the value is not specified | |
| 921 */ | |
| 922 String get text { | |
| 923 if (value == null) { | |
| 924 return null; | |
| 925 } | |
| 926 String text = value.lexeme; | |
| 927 int len = text.length; | |
| 928 if (len > 0) { | |
| 929 if (text.codeUnitAt(0) == 0x22) { | |
| 930 if (len > 1 && text.codeUnitAt(len - 1) == 0x22) { | |
| 931 return text.substring(1, len - 1); | |
| 932 } else { | |
| 933 return text.substring(1); | |
| 934 } | |
| 935 } else if (text.codeUnitAt(0) == 0x27) { | |
| 936 if (len > 1 && text.codeUnitAt(len - 1) == 0x27) { | |
| 937 return text.substring(1, len - 1); | |
| 938 } else { | |
| 939 return text.substring(1); | |
| 940 } | |
| 941 } | |
| 942 } | |
| 943 return text; | |
| 944 } | |
| 945 void visitChildren(XmlVisitor visitor) { | |
| 946 } | |
| 947 } | |
| 948 /** | |
| 949 * The interface `XmlVisitor` defines the behavior of objects that can be used t
o visit an | |
| 950 * [XmlNode] structure. | |
| 951 * | |
| 952 * @coverage dart.engine.html | |
| 953 */ | |
| 954 abstract class XmlVisitor<R> { | |
| 955 R visitHtmlUnit(HtmlUnit htmlUnit); | |
| 956 R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode); | |
| 957 R visitXmlTagNode(XmlTagNode xmlTagNode); | |
| 958 } | |
| 959 /** | |
| 960 * Instances of `HtmlScanner` receive and scan HTML content from a [Source].<br/
> | |
| 961 * For example, the following code scans HTML source and returns the result: | |
| 962 * | |
| 963 * <pre> | |
| 964 * HtmlScanner scanner = new HtmlScanner(source); | |
| 965 * source.getContents(scanner); | |
| 966 * return scanner.getResult(); | |
| 967 * </pre> | |
| 968 * | |
| 969 * @coverage dart.engine.html | |
| 970 */ | |
| 971 class HtmlScanner implements Source_ContentReceiver { | |
| 972 List<String> _SCRIPT_TAG = <String> ["script"]; | |
| 973 | |
| 974 /** | |
| 975 * The source being scanned (not `null`) | |
| 976 */ | |
| 977 Source _source; | |
| 978 | |
| 979 /** | |
| 980 * The time at which the contents of the source were last set. | |
| 981 */ | |
| 982 int _modificationTime = 0; | |
| 983 | |
| 984 /** | |
| 985 * The scanner used to scan the source | |
| 986 */ | |
| 987 AbstractScanner _scanner; | |
| 988 | |
| 989 /** | |
| 990 * The first token in the token stream. | |
| 991 */ | |
| 992 Token _token; | |
| 993 | |
| 994 /** | |
| 995 * Construct a new instance to scan the specified source. | |
| 996 * | |
| 997 * @param source the source to be scanned (not `null`) | |
| 998 */ | |
| 999 HtmlScanner(Source source) { | |
| 1000 this._source = source; | |
| 1001 } | |
| 1002 void accept(CharBuffer contents, int modificationTime) { | |
| 1003 this._modificationTime = modificationTime; | |
| 1004 _scanner = new CharBufferScanner(_source, contents); | |
| 1005 _scanner.passThroughElements = _SCRIPT_TAG; | |
| 1006 _token = _scanner.tokenize(); | |
| 1007 } | |
| 1008 void accept2(String contents, int modificationTime) { | |
| 1009 this._modificationTime = modificationTime; | |
| 1010 _scanner = new StringScanner(_source, contents); | |
| 1011 _scanner.passThroughElements = _SCRIPT_TAG; | |
| 1012 _token = _scanner.tokenize(); | |
| 1013 } | |
| 1014 | |
| 1015 /** | |
| 1016 * Answer the result of scanning the source | |
| 1017 * | |
| 1018 * @return the result (not `null`) | |
| 1019 */ | |
| 1020 HtmlScanResult get result => new HtmlScanResult(_modificationTime, _token, _sc
anner.lineStarts); | |
| 1021 } | |
| 1022 /** | |
| 1023 * Instances of the class `XmlParser` are used to parse tokens into a AST struct
ure comprised | |
| 1024 * of [XmlNode]s. | |
| 1025 * | |
| 1026 * @coverage dart.engine.html | |
| 1027 */ | |
| 1028 class XmlParser { | |
| 1029 | |
| 1030 /** | |
| 1031 * The source being parsed. | |
| 1032 */ | |
| 1033 Source source; | |
| 1034 | |
| 1035 /** | |
| 1036 * The next token to be parsed. | |
| 1037 */ | |
| 1038 Token currentToken; | |
| 1039 | |
| 1040 /** | |
| 1041 * Construct a parser for the specified source. | |
| 1042 * | |
| 1043 * @param source the source being parsed | |
| 1044 */ | |
| 1045 XmlParser(Source source) { | |
| 1046 this.source = source; | |
| 1047 } | |
| 1048 | |
| 1049 /** | |
| 1050 * Answer `true` if the specified tag is self closing and thus should never ha
ve content or | |
| 1051 * child tag nodes. | |
| 1052 * | |
| 1053 * @param tag the tag (not `null`) | |
| 1054 * @return `true` if self closing | |
| 1055 */ | |
| 1056 bool isSelfClosing(Token tag) => false; | |
| 1057 | |
| 1058 /** | |
| 1059 * Parse the entire token stream and in the process, advance the current token
to the end of the | |
| 1060 * token stream. | |
| 1061 * | |
| 1062 * @return the list of tag nodes found (not `null`, contains no `null`) | |
| 1063 */ | |
| 1064 List<XmlTagNode> parseTopTagNodes(Token firstToken) { | |
| 1065 currentToken = firstToken; | |
| 1066 List<XmlTagNode> tagNodes = new List<XmlTagNode>(); | |
| 1067 while (true) { | |
| 1068 while (true) { | |
| 1069 if (currentToken.type == TokenType.LT) { | |
| 1070 tagNodes.add(parseTagNode()); | |
| 1071 } else if (currentToken.type == TokenType.DECLARATION || currentToken.ty
pe == TokenType.DIRECTIVE || currentToken.type == TokenType.COMMENT) { | |
| 1072 currentToken = currentToken.next; | |
| 1073 } else if (currentToken.type == TokenType.EOF) { | |
| 1074 return tagNodes; | |
| 1075 } else { | |
| 1076 reportUnexpectedToken(); | |
| 1077 currentToken = currentToken.next; | |
| 1078 } | |
| 1079 break; | |
| 1080 } | |
| 1081 } | |
| 1082 } | |
| 1083 | |
| 1084 /** | |
| 1085 * Insert a synthetic token of the specified type before the current token | |
| 1086 * | |
| 1087 * @param type the type of token to be inserted (not `null`) | |
| 1088 * @return the synthetic token that was inserted (not `null`) | |
| 1089 */ | |
| 1090 Token insertSyntheticToken(TokenType type) { | |
| 1091 Token token = new Token.con2(type, currentToken.offset, ""); | |
| 1092 currentToken.previous.setNext(token); | |
| 1093 token.setNext(currentToken); | |
| 1094 return token; | |
| 1095 } | |
| 1096 | |
| 1097 /** | |
| 1098 * Parse the token stream for an attribute. This method advances the current t
oken over the | |
| 1099 * attribute, but should not be called if the [currentToken] is not [TokenType
#TAG]. | |
| 1100 * | |
| 1101 * @return the attribute (not `null`) | |
| 1102 */ | |
| 1103 XmlAttributeNode parseAttribute() { | |
| 1104 Token name = currentToken; | |
| 1105 currentToken = currentToken.next; | |
| 1106 Token equals; | |
| 1107 if (identical(currentToken.type, TokenType.EQ)) { | |
| 1108 equals = currentToken; | |
| 1109 currentToken = currentToken.next; | |
| 1110 } else { | |
| 1111 reportUnexpectedToken(); | |
| 1112 equals = insertSyntheticToken(TokenType.EQ); | |
| 1113 } | |
| 1114 Token value; | |
| 1115 if (identical(currentToken.type, TokenType.STRING)) { | |
| 1116 value = currentToken; | |
| 1117 currentToken = currentToken.next; | |
| 1118 } else { | |
| 1119 reportUnexpectedToken(); | |
| 1120 value = insertSyntheticToken(TokenType.STRING); | |
| 1121 } | |
| 1122 return new XmlAttributeNode(name, equals, value); | |
| 1123 } | |
| 1124 | |
| 1125 /** | |
| 1126 * Parse the stream for a sequence of attributes. This method advances the cur
rent token to the | |
| 1127 * next [TokenType#GT], [TokenType#SLASH_GT], or [TokenType#EOF]. | |
| 1128 * | |
| 1129 * @return a collection of zero or more attributes (not `null`, contains no `n
ull`s) | |
| 1130 */ | |
| 1131 List<XmlAttributeNode> parseAttributes() { | |
| 1132 TokenType type = currentToken.type; | |
| 1133 if (identical(type, TokenType.GT) || identical(type, TokenType.SLASH_GT) ||
identical(type, TokenType.EOF)) { | |
| 1134 return XmlTagNode.NO_ATTRIBUTES; | |
| 1135 } | |
| 1136 List<XmlAttributeNode> attributes = new List<XmlAttributeNode>(); | |
| 1137 while (true) { | |
| 1138 while (true) { | |
| 1139 if (currentToken.type == TokenType.GT || currentToken.type == TokenType.
SLASH_GT || currentToken.type == TokenType.EOF) { | |
| 1140 return attributes; | |
| 1141 } else if (currentToken.type == TokenType.TAG) { | |
| 1142 attributes.add(parseAttribute()); | |
| 1143 } else { | |
| 1144 reportUnexpectedToken(); | |
| 1145 currentToken = currentToken.next; | |
| 1146 } | |
| 1147 break; | |
| 1148 } | |
| 1149 } | |
| 1150 } | |
| 1151 | |
| 1152 /** | |
| 1153 * Parse the stream for a sequence of tag nodes existing within a parent tag n
ode. This method | |
| 1154 * advances the current token to the next [TokenType#LT_SLASH] or [TokenType#E
OF]. | |
| 1155 * | |
| 1156 * @return a list of nodes (not `null`, contains no `null`s) | |
| 1157 */ | |
| 1158 List<XmlTagNode> parseChildTagNodes() { | |
| 1159 TokenType type = currentToken.type; | |
| 1160 if (identical(type, TokenType.LT_SLASH) || identical(type, TokenType.EOF)) { | |
| 1161 return XmlTagNode.NO_TAG_NODES; | |
| 1162 } | |
| 1163 List<XmlTagNode> nodes = new List<XmlTagNode>(); | |
| 1164 while (true) { | |
| 1165 while (true) { | |
| 1166 if (currentToken.type == TokenType.LT) { | |
| 1167 nodes.add(parseTagNode()); | |
| 1168 } else if (currentToken.type == TokenType.LT_SLASH || currentToken.type
== TokenType.EOF) { | |
| 1169 return nodes; | |
| 1170 } else if (currentToken.type == TokenType.COMMENT) { | |
| 1171 currentToken = currentToken.next; | |
| 1172 } else { | |
| 1173 reportUnexpectedToken(); | |
| 1174 currentToken = currentToken.next; | |
| 1175 } | |
| 1176 break; | |
| 1177 } | |
| 1178 } | |
| 1179 } | |
| 1180 | |
| 1181 /** | |
| 1182 * Parse the token stream for the next tag node. This method advances current
token over the | |
| 1183 * parsed tag node, but should only be called if the current token is [TokenTy
pe#LT] | |
| 1184 * | |
| 1185 * @return the tag node or `null` if none found | |
| 1186 */ | |
| 1187 XmlTagNode parseTagNode() { | |
| 1188 Token nodeStart = currentToken; | |
| 1189 currentToken = currentToken.next; | |
| 1190 Token tag; | |
| 1191 if (identical(currentToken.type, TokenType.TAG)) { | |
| 1192 tag = currentToken; | |
| 1193 currentToken = currentToken.next; | |
| 1194 } else { | |
| 1195 reportUnexpectedToken(); | |
| 1196 tag = insertSyntheticToken(TokenType.TAG); | |
| 1197 } | |
| 1198 List<XmlAttributeNode> attributes = parseAttributes(); | |
| 1199 Token attributeEnd; | |
| 1200 if (identical(currentToken.type, TokenType.GT) || identical(currentToken.typ
e, TokenType.SLASH_GT)) { | |
| 1201 attributeEnd = currentToken; | |
| 1202 currentToken = currentToken.next; | |
| 1203 } else { | |
| 1204 reportUnexpectedToken(); | |
| 1205 attributeEnd = insertSyntheticToken(TokenType.SLASH_GT); | |
| 1206 } | |
| 1207 if (identical(attributeEnd.type, TokenType.SLASH_GT) || isSelfClosing(tag))
{ | |
| 1208 return new XmlTagNode(nodeStart, tag, attributes, attributeEnd, XmlTagNode
.NO_TAG_NODES, currentToken, null, attributeEnd); | |
| 1209 } | |
| 1210 List<XmlTagNode> tagNodes = parseChildTagNodes(); | |
| 1211 Token contentEnd; | |
| 1212 if (identical(currentToken.type, TokenType.LT_SLASH)) { | |
| 1213 contentEnd = currentToken; | |
| 1214 currentToken = currentToken.next; | |
| 1215 } else { | |
| 1216 reportUnexpectedToken(); | |
| 1217 contentEnd = insertSyntheticToken(TokenType.LT_SLASH); | |
| 1218 } | |
| 1219 Token closingTag; | |
| 1220 if (identical(currentToken.type, TokenType.TAG)) { | |
| 1221 closingTag = currentToken; | |
| 1222 currentToken = currentToken.next; | |
| 1223 } else { | |
| 1224 reportUnexpectedToken(); | |
| 1225 closingTag = insertSyntheticToken(TokenType.TAG); | |
| 1226 } | |
| 1227 Token nodeEnd; | |
| 1228 if (identical(currentToken.type, TokenType.GT)) { | |
| 1229 nodeEnd = currentToken; | |
| 1230 currentToken = currentToken.next; | |
| 1231 } else { | |
| 1232 reportUnexpectedToken(); | |
| 1233 nodeEnd = insertSyntheticToken(TokenType.GT); | |
| 1234 } | |
| 1235 return new XmlTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes, co
ntentEnd, closingTag, nodeEnd); | |
| 1236 } | |
| 1237 | |
| 1238 /** | |
| 1239 * Report the current token as unexpected | |
| 1240 */ | |
| 1241 void reportUnexpectedToken() { | |
| 1242 } | |
| 1243 } | |
| 1244 /** | |
| 1245 * Instances of `XmlTagNode` represent XML or HTML elements such as `` and | |
| 1246 * `<body foo="bar"> ... </body>`. | |
| 1247 * | |
| 1248 * @coverage dart.engine.html | |
| 1249 */ | |
| 1250 class XmlTagNode extends XmlNode { | |
| 1251 | |
| 1252 /** | |
| 1253 * Constant representing empty list of attributes. | |
| 1254 */ | |
| 1255 static List<XmlAttributeNode> NO_ATTRIBUTES = new UnmodifiableListView(new Lis
t<XmlAttributeNode>()); | |
| 1256 | |
| 1257 /** | |
| 1258 * Constant representing empty list of tag nodes. | |
| 1259 */ | |
| 1260 static List<XmlTagNode> NO_TAG_NODES = new UnmodifiableListView(new List<XmlTa
gNode>()); | |
| 1261 | |
| 1262 /** | |
| 1263 * The starting [TokenType#LT] token (not `null`). | |
| 1264 */ | |
| 1265 Token nodeStart; | |
| 1266 | |
| 1267 /** | |
| 1268 * The [TokenType#TAG] token after the starting '<' (not `null`). | |
| 1269 */ | |
| 1270 Token tag; | |
| 1271 | |
| 1272 /** | |
| 1273 * The attributes contained by the receiver (not `null`, contains no `null`s). | |
| 1274 */ | |
| 1275 List<XmlAttributeNode> attributes; | |
| 1276 | |
| 1277 /** | |
| 1278 * The [TokenType#GT] or [TokenType#SLASH_GT] token after the attributes (not | |
| 1279 * `null`). The token may be the same token as [nodeEnd] if there are no child | |
| 1280 * [tagNodes]. | |
| 1281 */ | |
| 1282 Token attributeEnd; | |
| 1283 | |
| 1284 /** | |
| 1285 * The tag nodes contained in the receiver (not `null`, contains no `null`s). | |
| 1286 */ | |
| 1287 List<XmlTagNode> tagNodes; | |
| 1288 | |
| 1289 /** | |
| 1290 * The token (not `null`) after the content, which may be | |
| 1291 * | |
| 1292 * * (1) [TokenType#LT_SLASH] for nodes with open and close tags, or | |
| 1293 * * (2) the [TokenType#LT] nodeStart of the next sibling node if this node is
self | |
| 1294 * closing or the attributeEnd is [TokenType#SLASH_GT], or | |
| 1295 * * (3) [TokenType#EOF] if the node does not have a closing tag and is the la
st node in | |
| 1296 * the stream [TokenType#LT_SLASH] token after the content, or `null` if there
is no | |
| 1297 * content and the attributes ended with [TokenType#SLASH_GT]. | |
| 1298 * | |
| 1299 */ | |
| 1300 Token contentEnd; | |
| 1301 | |
| 1302 /** | |
| 1303 * The closing [TokenType#TAG] after the child elements or `null` if there is
no | |
| 1304 * content and the attributes ended with [TokenType#SLASH_GT] | |
| 1305 */ | |
| 1306 Token closingTag; | |
| 1307 | |
| 1308 /** | |
| 1309 * The ending [TokenType#GT] or [TokenType#SLASH_GT] token (not `null`). | |
| 1310 */ | |
| 1311 Token nodeEnd; | |
| 1312 | |
| 1313 /** | |
| 1314 * Construct a new instance representing an XML or HTML element | |
| 1315 * | |
| 1316 * @param nodeStart the starting [TokenType#LT] token (not `null`) | |
| 1317 * @param tag the [TokenType#TAG] token after the starting '<' (not `null`)
. | |
| 1318 * @param attributes the attributes associated with this element or [NO_ATTRIB
UTES] (not | |
| 1319 * `null`, contains no `null`s) | |
| 1320 * @param attributeEnd The [TokenType#GT] or [TokenType#SLASH_GT] token after
the | |
| 1321 * attributes (not `null`). The token may be the same token as [nodeE
nd] if | |
| 1322 * there are no child [tagNodes]. | |
| 1323 * @param tagNodes child tag nodes of the receiver or [NO_TAG_NODES] (not `nul
l`, | |
| 1324 * contains no `null`s) | |
| 1325 * @param contentEnd the token (not `null`) after the content, which may be | |
| 1326 * | |
| 1327 * * (1) [TokenType#LT_SLASH] for nodes with open and close tags, or | |
| 1328 * * (2) the [TokenType#LT] nodeStart of the next sibling node if thi
s node is | |
| 1329 * self closing or the attributeEnd is [TokenType#SLASH_GT], or | |
| 1330 * * (3) [TokenType#EOF] if the node does not have a closing tag and
is the last | |
| 1331 * node in the stream [TokenType#LT_SLASH] token after the content, o
r `null` | |
| 1332 * if there is no content and the attributes ended with [TokenType#SL
ASH_GT]. | |
| 1333 * | |
| 1334 * @param closingTag the closing [TokenType#TAG] after the child elements or `
null` if | |
| 1335 * there is no content and the attributes ended with [TokenType#SLASH
_GT] | |
| 1336 * @param nodeEnd the ending [TokenType#GT] or [TokenType#SLASH_GT] token (not | |
| 1337 * `null`) | |
| 1338 */ | |
| 1339 XmlTagNode(Token nodeStart, Token tag, List<XmlAttributeNode> attributes, Toke
n attributeEnd, List<XmlTagNode> tagNodes, Token contentEnd, Token closingTag, T
oken nodeEnd) { | |
| 1340 this.nodeStart = nodeStart; | |
| 1341 this.tag = tag; | |
| 1342 this.attributes = becomeParentOfEmpty(attributes, NO_ATTRIBUTES); | |
| 1343 this.attributeEnd = attributeEnd; | |
| 1344 this.tagNodes = becomeParentOfEmpty(tagNodes, NO_TAG_NODES); | |
| 1345 this.contentEnd = contentEnd; | |
| 1346 this.closingTag = closingTag; | |
| 1347 this.nodeEnd = nodeEnd; | |
| 1348 } | |
| 1349 accept(XmlVisitor visitor) => visitor.visitXmlTagNode(this); | |
| 1350 | |
| 1351 /** | |
| 1352 * Answer the attribute with the specified name. | |
| 1353 * | |
| 1354 * @param name the attribute name | |
| 1355 * @return the attribute or `null` if no matching attribute is found | |
| 1356 */ | |
| 1357 XmlAttributeNode getAttribute(String name) { | |
| 1358 for (XmlAttributeNode attribute in attributes) { | |
| 1359 if (attribute.name.lexeme == name) { | |
| 1360 return attribute; | |
| 1361 } | |
| 1362 } | |
| 1363 return null; | |
| 1364 } | |
| 1365 | |
| 1366 /** | |
| 1367 * Find the attribute with the given name (see [getAttribute] and answer the l
exeme | |
| 1368 * for the attribute's value token without the leading and trailing quotes (se
e | |
| 1369 * [XmlAttributeNode#getText]). | |
| 1370 * | |
| 1371 * @param name the attribute name | |
| 1372 * @return the attribute text or `null` if no matching attribute is found | |
| 1373 */ | |
| 1374 String getAttributeText(String name) { | |
| 1375 XmlAttributeNode attribute = getAttribute(name); | |
| 1376 return attribute != null ? attribute.text : null; | |
| 1377 } | |
| 1378 Token get beginToken => nodeStart; | |
| 1379 | |
| 1380 /** | |
| 1381 * Answer a string representing the content contained in the receiver. This in
cludes the textual | |
| 1382 * representation of any child tag nodes ([getTagNodes]). Whitespace between '
<', | |
| 1383 * '</', and '>', '/>' is discarded, but all other whitespace is preserved. | |
| 1384 * | |
| 1385 * @return the content (not `null`) | |
| 1386 */ | |
| 1387 String get content { | |
| 1388 Token token = attributeEnd.next; | |
| 1389 if (identical(token, contentEnd)) { | |
| 1390 return ""; | |
| 1391 } | |
| 1392 String content = token.lexeme; | |
| 1393 token = token.next; | |
| 1394 if (identical(token, contentEnd)) { | |
| 1395 return content; | |
| 1396 } | |
| 1397 JavaStringBuilder buffer = new JavaStringBuilder(); | |
| 1398 while (token != contentEnd) { | |
| 1399 buffer.append(token.lexeme); | |
| 1400 token = token.next; | |
| 1401 } | |
| 1402 return buffer.toString(); | |
| 1403 } | |
| 1404 Token get endToken { | |
| 1405 if (nodeEnd != null) { | |
| 1406 return nodeEnd; | |
| 1407 } | |
| 1408 if (closingTag != null) { | |
| 1409 return closingTag; | |
| 1410 } | |
| 1411 if (contentEnd != null) { | |
| 1412 return contentEnd; | |
| 1413 } | |
| 1414 if (!tagNodes.isEmpty) { | |
| 1415 return tagNodes[tagNodes.length - 1].endToken; | |
| 1416 } | |
| 1417 if (attributeEnd != null) { | |
| 1418 return attributeEnd; | |
| 1419 } | |
| 1420 if (!attributes.isEmpty) { | |
| 1421 return attributes[attributes.length - 1].endToken; | |
| 1422 } | |
| 1423 return tag; | |
| 1424 } | |
| 1425 void visitChildren(XmlVisitor visitor) { | |
| 1426 for (XmlAttributeNode node in attributes) { | |
| 1427 node.accept(visitor); | |
| 1428 } | |
| 1429 for (XmlTagNode node in tagNodes) { | |
| 1430 node.accept(visitor); | |
| 1431 } | |
| 1432 } | |
| 1433 | |
| 1434 /** | |
| 1435 * Same as [becomeParentOf], but returns given "ifEmpty" if "children" is empt
y | |
| 1436 */ | |
| 1437 List becomeParentOfEmpty(List children, List ifEmpty) { | |
| 1438 if (children != null && children.isEmpty) { | |
| 1439 return ifEmpty; | |
| 1440 } | |
| 1441 return becomeParentOf(children); | |
| 1442 } | |
| 1443 } | |
| 1444 /** | |
| 1445 * Instances of the class `HtmlParser` are used to parse tokens into a AST struc
ture comprised | |
| 1446 * of [XmlNode]s. | |
| 1447 * | |
| 1448 * @coverage dart.engine.html | |
| 1449 */ | |
| 1450 class HtmlParser extends XmlParser { | |
| 1451 static Set<String> SELF_CLOSING = new Set<String>(); | |
| 1452 | |
| 1453 /** | |
| 1454 * Construct a parser for the specified source. | |
| 1455 * | |
| 1456 * @param source the source being parsed | |
| 1457 */ | |
| 1458 HtmlParser(Source source) : super(source); | |
| 1459 | |
| 1460 /** | |
| 1461 * Parse the tokens specified by the given scan result. | |
| 1462 * | |
| 1463 * @param scanResult the result of scanning an HTML source (not `null`) | |
| 1464 * @return the parse result (not `null`) | |
| 1465 */ | |
| 1466 HtmlParseResult parse(HtmlScanResult scanResult) { | |
| 1467 Token firstToken = scanResult.token; | |
| 1468 List<XmlTagNode> tagNodes = parseTopTagNodes(firstToken); | |
| 1469 HtmlUnit unit = new HtmlUnit(firstToken, tagNodes, currentToken); | |
| 1470 return new HtmlParseResult(scanResult.modificationTime, firstToken, scanResu
lt.lineStarts, unit); | |
| 1471 } | |
| 1472 | |
| 1473 /** | |
| 1474 * Scan then parse the specified source. | |
| 1475 * | |
| 1476 * @param source the source to be scanned and parsed (not `null`) | |
| 1477 * @return the parse result (not `null`) | |
| 1478 */ | |
| 1479 HtmlParseResult parse2(Source source) { | |
| 1480 HtmlScanner scanner = new HtmlScanner(source); | |
| 1481 source.getContents(scanner); | |
| 1482 return parse(scanner.result); | |
| 1483 } | |
| 1484 bool isSelfClosing(Token tag) => SELF_CLOSING.contains(tag.lexeme); | |
| 1485 } | |
| 1486 /** | |
| 1487 * Instances of the class `HtmlUnit` represent the contents of an HTML file. | |
| 1488 * | |
| 1489 * @coverage dart.engine.html | |
| 1490 */ | |
| 1491 class HtmlUnit extends XmlNode { | |
| 1492 | |
| 1493 /** | |
| 1494 * The first token in the token stream that was parsed to form this HTML unit. | |
| 1495 */ | |
| 1496 Token _beginToken; | |
| 1497 | |
| 1498 /** | |
| 1499 * The last token in the token stream that was parsed to form this compilation
unit. This token | |
| 1500 * should always have a type of [TokenType.EOF]. | |
| 1501 */ | |
| 1502 Token _endToken; | |
| 1503 | |
| 1504 /** | |
| 1505 * The tag nodes contained in the receiver (not `null`, contains no `null`s). | |
| 1506 */ | |
| 1507 List<XmlTagNode> tagNodes; | |
| 1508 | |
| 1509 /** | |
| 1510 * The element associated with this HTML unit or `null` if the receiver is not
resolved. | |
| 1511 */ | |
| 1512 HtmlElementImpl element; | |
| 1513 | |
| 1514 /** | |
| 1515 * Construct a new instance representing the content of an HTML file. | |
| 1516 * | |
| 1517 * @param beginToken the first token in the file (not `null`) | |
| 1518 * @param tagNodes child tag nodes of the receiver (not `null`, contains no `n
ull`s) | |
| 1519 * @param endToken the last token in the token stream which should be of type | |
| 1520 * [TokenType.EOF] | |
| 1521 */ | |
| 1522 HtmlUnit(Token beginToken, List<XmlTagNode> tagNodes, Token endToken) { | |
| 1523 this._beginToken = beginToken; | |
| 1524 this.tagNodes = becomeParentOf(tagNodes); | |
| 1525 this._endToken = endToken; | |
| 1526 } | |
| 1527 accept(XmlVisitor visitor) => visitor.visitHtmlUnit(this); | |
| 1528 Token get beginToken => _beginToken; | |
| 1529 Token get endToken => _endToken; | |
| 1530 void visitChildren(XmlVisitor visitor) { | |
| 1531 for (XmlTagNode node in tagNodes) { | |
| 1532 node.accept(visitor); | |
| 1533 } | |
| 1534 } | |
| 1535 } | |
| OLD | NEW |