| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 // This code was auto-generated, is not intended to be edited, and is subject to | 5 // This code was auto-generated, is not intended to be edited, and is subject to |
| 6 // significant change. Please see the README file for more information. | 6 // significant change. Please see the README file for more information. |
| 7 | 7 |
| 8 library engine.html; | 8 library engine.html; |
| 9 | 9 |
| 10 import 'dart:collection'; | 10 import 'dart:collection'; |
| 11 import 'java_core.dart'; | 11 import 'java_core.dart'; |
| 12 import 'java_engine.dart'; | 12 import 'java_engine.dart'; |
| 13 import 'source.dart'; | 13 import 'source.dart'; |
| 14 import 'error.dart' show AnalysisErrorListener; | 14 import 'error.dart' show AnalysisErrorListener; |
| 15 import 'scanner.dart' as sc show Scanner, SubSequenceReader, Token; | 15 import 'scanner.dart' as sc show Scanner, SubSequenceReader, Token; |
| 16 import 'parser.dart' show Parser; | 16 import 'parser.dart' show Parser; |
| 17 import 'ast.dart'; | 17 import 'ast.dart'; |
| 18 import 'element.dart'; | 18 import 'element.dart'; |
| 19 import 'engine.dart' show AnalysisEngine, AngularHtmlUnitResolver, ExpressionVis
itor; | 19 import 'engine.dart' show AnalysisEngine, AngularHtmlUnitResolver, ExpressionVis
itor; |
| 20 | 20 |
| 21 /** | 21 /** |
| 22 * Instances of the class `XmlExpression` represent an abstract expression embed
ded into | |
| 23 * [XmlNode]. | |
| 24 */ | |
| 25 abstract class XmlExpression { | |
| 26 /** | |
| 27 * An empty array of expressions. | |
| 28 */ | |
| 29 static List<XmlExpression> EMPTY_ARRAY = new List<XmlExpression>(0); | |
| 30 | |
| 31 /** | |
| 32 * Check if the given offset belongs to the expression's source range. | |
| 33 */ | |
| 34 bool contains(int offset) => this.offset <= offset && offset < end; | |
| 35 | |
| 36 /** | |
| 37 * Return the offset of the character immediately following the last character
of this | |
| 38 * expression's source range. This is equivalent to `getOffset() + getLength()
`. | |
| 39 * | |
| 40 * @return the offset of the character just past the expression's source range | |
| 41 */ | |
| 42 int get end; | |
| 43 | |
| 44 /** | |
| 45 * Return the number of characters in the expression's source range. | |
| 46 */ | |
| 47 int get length; | |
| 48 | |
| 49 /** | |
| 50 * Return the offset of the first character in the expression's source range. | |
| 51 */ | |
| 52 int get offset; | |
| 53 | |
| 54 /** | |
| 55 * Return the [Reference] at the given offset. | |
| 56 * | |
| 57 * @param offset the offset from the beginning of the file | |
| 58 * @return the [Reference] at the given offset, maybe `null` | |
| 59 */ | |
| 60 XmlExpression_Reference getReference(int offset); | |
| 61 } | |
| 62 | |
| 63 /** | |
| 64 * The reference to the [Element]. | |
| 65 */ | |
| 66 class XmlExpression_Reference { | |
| 67 Element element; | |
| 68 | |
| 69 int offset = 0; | |
| 70 | |
| 71 int length = 0; | |
| 72 | |
| 73 XmlExpression_Reference(Element element, int offset, int length) { | |
| 74 this.element = element; | |
| 75 this.offset = offset; | |
| 76 this.length = length; | |
| 77 } | |
| 78 } | |
| 79 | |
| 80 /** | |
| 81 * Instances of the class `SimpleXmlVisitor` implement an AST visitor that will
do nothing | |
| 82 * when visiting an AST node. It is intended to be a superclass for classes that
use the visitor | |
| 83 * pattern primarily as a dispatch mechanism (and hence don't need to recursivel
y visit a whole | |
| 84 * structure) and that only need to visit a small number of node types. | |
| 85 */ | |
| 86 class SimpleXmlVisitor<R> implements XmlVisitor<R> { | |
| 87 @override | |
| 88 R visitHtmlScriptTagNode(HtmlScriptTagNode node) => null; | |
| 89 | |
| 90 @override | |
| 91 R visitHtmlUnit(HtmlUnit htmlUnit) => null; | |
| 92 | |
| 93 @override | |
| 94 R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode) => null; | |
| 95 | |
| 96 @override | |
| 97 R visitXmlTagNode(XmlTagNode xmlTagNode) => null; | |
| 98 } | |
| 99 | |
| 100 /** | |
| 101 * The abstract class `AbstractScanner` implements a scanner for HTML code. Subc
lasses are | 22 * The abstract class `AbstractScanner` implements a scanner for HTML code. Subc
lasses are |
| 102 * required to implement the interface used to access the characters being scann
ed. | 23 * required to implement the interface used to access the characters being scann
ed. |
| 103 */ | 24 */ |
| 104 abstract class AbstractScanner { | 25 abstract class AbstractScanner { |
| 105 static List<String> _NO_PASS_THROUGH_ELEMENTS = <String> []; | 26 static List<String> _NO_PASS_THROUGH_ELEMENTS = <String> []; |
| 106 | 27 |
| 107 /** | 28 /** |
| 108 * The source being scanned. | 29 * The source being scanned. |
| 109 */ | 30 */ |
| 110 final Source source; | 31 final Source source; |
| (...skipping 298 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 409 _emitWithOffsetAndLength(TokenType.TAG, start, -1); | 330 _emitWithOffsetAndLength(TokenType.TAG, start, -1); |
| 410 } else { | 331 } else { |
| 411 // a non-char token | 332 // a non-char token |
| 412 _emitWithOffsetAndLength(TokenType.TEXT, start, 0); | 333 _emitWithOffsetAndLength(TokenType.TEXT, start, 0); |
| 413 c = advance(); | 334 c = advance(); |
| 414 } | 335 } |
| 415 } | 336 } |
| 416 } | 337 } |
| 417 } | 338 } |
| 418 | 339 |
| 340 class ExpressionVisitor_HtmlUnitUtils_getExpression extends ExpressionVisitor { |
| 341 int offset = 0; |
| 342 |
| 343 List<Expression> result; |
| 344 |
| 345 ExpressionVisitor_HtmlUnitUtils_getExpression(this.offset, this.result) : supe
r(); |
| 346 |
| 347 @override |
| 348 void visitExpression(Expression expression) { |
| 349 Expression at = HtmlUnitUtils._getExpressionAt(expression, offset); |
| 350 if (at != null) { |
| 351 result[0] = at; |
| 352 throw new HtmlUnitUtils_FoundExpressionError(); |
| 353 } |
| 354 } |
| 355 } |
| 356 |
| 419 /** | 357 /** |
| 420 * Instances of the class `Token` represent a token that was scanned from the in
put. Each | 358 * Instances of the class `HtmlParser` are used to parse tokens into a AST struc
ture comprised |
| 421 * token knows which token follows it, acting as the head of a linked list of to
kens. | 359 * of [XmlNode]s. |
| 422 */ | 360 */ |
| 423 class Token { | 361 class HtmlParser extends XmlParser { |
| 424 /** | 362 /** |
| 425 * The offset from the beginning of the file to the first character in the tok
en. | 363 * The line information associated with the source being parsed. |
| 426 */ | 364 */ |
| 427 final int offset; | 365 LineInfo _lineInfo; |
| 428 | 366 |
| 429 /** | 367 /** |
| 430 * The previous token in the token stream. | 368 * The error listener to which errors will be reported. |
| 431 */ | 369 */ |
| 432 Token previous; | 370 final AnalysisErrorListener _errorListener; |
| 433 | 371 |
| 434 /** | 372 static String _APPLICATION_DART_IN_DOUBLE_QUOTES = "\"application/dart\""; |
| 435 * The next token in the token stream. | 373 |
| 436 */ | 374 static String _APPLICATION_DART_IN_SINGLE_QUOTES = "'application/dart'"; |
| 437 Token _next; | 375 |
| 438 | 376 static String _SCRIPT = "script"; |
| 439 /** | 377 |
| 440 * The type of the token. | 378 static String _TYPE = "type"; |
| 441 */ | 379 |
| 442 final TokenType type; | 380 /** |
| 443 | 381 * A set containing the names of tags that do not have a closing tag. |
| 444 /** | 382 */ |
| 445 * The lexeme represented by this token. | 383 static Set<String> SELF_CLOSING = new Set<String>.from(JavaArrays.asList(<Stri
ng> [ |
| 446 */ | 384 "area", |
| 447 String _value; | 385 "base", |
| 448 | 386 "basefont", |
| 449 /** | 387 "br", |
| 450 * Initialize a newly created token. | 388 "col", |
| 451 * | 389 "frame", |
| 452 * @param type the token type (not `null`) | 390 "hr", |
| 453 * @param offset the offset from the beginning of the file to the first charac
ter in the token | 391 "img", |
| 454 */ | 392 "input", |
| 455 Token.con1(TokenType type, int offset) : this.con2(type, offset, type.lexeme); | 393 "link", |
| 456 | 394 "meta", |
| 457 /** | 395 "param", |
| 458 * Initialize a newly created token. | 396 "!"])); |
| 459 * | 397 |
| 460 * @param type the token type (not `null`) | 398 /** |
| 461 * @param offset the offset from the beginning of the file to the first charac
ter in the token | 399 * Given the contents of an embedded expression that occurs at the given offse
t, parse it as a |
| 462 * @param value the lexeme represented by this token (not `null`) | 400 * Dart expression. The contents should not include the expression's delimiter
s. |
| 463 */ | 401 * |
| 464 Token.con2(this.type, this.offset, String value) { | 402 * @param source the source that contains that given token |
| 465 this._value = StringUtilities.intern(value); | 403 * @param token the token to start parsing from |
| 466 } | 404 * @return the Dart expression that was parsed |
| 467 | 405 */ |
| 468 /** | 406 static Expression parseEmbeddedExpression(Source source, sc.Token token, Analy
sisErrorListener errorListener) { |
| 469 * Return the offset from the beginning of the file to the character after las
t character of the | 407 Parser parser = new Parser(source, errorListener); |
| 470 * token. | 408 return parser.parseExpression(token); |
| 471 * | 409 } |
| 472 * @return the offset from the beginning of the file to the first character af
ter last character | 410 |
| 473 * of the token | 411 /** |
| 474 */ | 412 * Given the contents of an embedded expression that occurs at the given offse
t, scans it as a |
| 475 int get end => offset + length; | 413 * Dart code. |
| 476 | 414 * |
| 477 /** | 415 * @param source the source of that contains the given contents |
| 478 * Return the number of characters in the node's source range. | 416 * @param contents the contents to scan |
| 479 * | 417 * @param contentOffset the offset of the contents in the larger file |
| 480 * @return the number of characters in the node's source range | 418 * @return the first Dart token |
| 481 */ | 419 */ |
| 482 int get length => lexeme.length; | 420 static sc.Token scanDartSource(Source source, LineInfo lineInfo, String conten
ts, int contentOffset, AnalysisErrorListener errorListener) { |
| 483 | 421 LineInfo_Location location = lineInfo.getLocation(contentOffset); |
| 484 /** | 422 sc.Scanner scanner = new sc.Scanner(source, new sc.SubSequenceReader(content
s, contentOffset), errorListener); |
| 485 * Return the lexeme that represents this token. | 423 scanner.setSourceStart(location.lineNumber, location.columnNumber); |
| 486 * | 424 return scanner.tokenize(); |
| 487 * @return the lexeme (not `null`) | 425 } |
| 488 */ | 426 |
| 489 String get lexeme => _value; | 427 /** |
| 490 | 428 * Construct a parser for the specified source. |
| 491 /** | 429 * |
| 492 * Return the next token in the token stream. | 430 * @param source the source being parsed |
| 493 * | 431 * @param errorListener the error listener to which errors will be reported |
| 494 * @return the next token in the token stream | 432 */ |
| 495 */ | 433 HtmlParser(Source source, this._errorListener) : super(source); |
| 496 Token get next => _next; | 434 |
| 497 | 435 /** |
| 498 /** | 436 * Parse the given tokens. |
| 499 * Return `true` if this token is a synthetic token. A synthetic token is a to
ken that was | 437 * |
| 500 * introduced by the parser in order to recover from an error in the code. Syn
thetic tokens always | 438 * @param token the first token in the stream of tokens to be parsed |
| 501 * have a length of zero (`0`). | 439 * @param lineInfo the line information created by the scanner |
| 502 * | 440 * @return the parse result (not `null`) |
| 503 * @return `true` if this token is a synthetic token | 441 */ |
| 504 */ | 442 HtmlUnit parse(Token token, LineInfo lineInfo) { |
| 505 bool get isSynthetic => length == 0; | 443 this._lineInfo = lineInfo; |
| 506 | 444 List<XmlTagNode> tagNodes = parseTopTagNodes(token); |
| 507 /** | 445 return new HtmlUnit(token, tagNodes, currentToken); |
| 508 * Set the next token in the token stream to the given token. This has the sid
e-effect of setting | 446 } |
| 509 * this token to be the previous token for the given token. | 447 |
| 510 * | 448 @override |
| 511 * @param token the next token in the token stream | 449 XmlAttributeNode createAttributeNode(Token name, Token equals, Token value) =>
new XmlAttributeNode(name, equals, value); |
| 512 * @return the token that was passed in | 450 |
| 513 */ | 451 @override |
| 514 Token setNext(Token token) { | 452 XmlTagNode createTagNode(Token nodeStart, Token tag, List<XmlAttributeNode> at
tributes, Token attributeEnd, List<XmlTagNode> tagNodes, Token contentEnd, Token
closingTag, Token nodeEnd) { |
| 515 _next = token; | 453 if (_isScriptNode(tag, attributes, tagNodes)) { |
| 516 token.previous = this; | 454 HtmlScriptTagNode tagNode = new HtmlScriptTagNode(nodeStart, tag, attribut
es, attributeEnd, tagNodes, contentEnd, closingTag, nodeEnd); |
| 517 return token; | 455 String contents = tagNode.content; |
| 518 } | 456 int contentOffset = attributeEnd.end; |
| 519 | 457 LineInfo_Location location = _lineInfo.getLocation(contentOffset); |
| 520 @override | 458 sc.Scanner scanner = new sc.Scanner(source, new sc.SubSequenceReader(conte
nts, contentOffset), _errorListener); |
| 521 String toString() => lexeme; | 459 scanner.setSourceStart(location.lineNumber, location.columnNumber); |
| 460 sc.Token firstToken = scanner.tokenize(); |
| 461 Parser parser = new Parser(source, _errorListener); |
| 462 CompilationUnit unit = parser.parseCompilationUnit(firstToken); |
| 463 unit.lineInfo = _lineInfo; |
| 464 tagNode.script = unit; |
| 465 return tagNode; |
| 466 } |
| 467 return new XmlTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes, co
ntentEnd, closingTag, nodeEnd); |
| 468 } |
| 469 |
| 470 @override |
| 471 bool isSelfClosing(Token tag) => SELF_CLOSING.contains(tag.lexeme); |
| 472 |
| 473 /** |
| 474 * Determine if the specified node is a Dart script. |
| 475 * |
| 476 * @param node the node to be tested (not `null`) |
| 477 * @return `true` if the node is a Dart script |
| 478 */ |
| 479 bool _isScriptNode(Token tag, List<XmlAttributeNode> attributes, List<XmlTagNo
de> tagNodes) { |
| 480 if (tagNodes.length != 0 || tag.lexeme != _SCRIPT) { |
| 481 return false; |
| 482 } |
| 483 for (XmlAttributeNode attribute in attributes) { |
| 484 if (attribute.name == _TYPE) { |
| 485 Token valueToken = attribute.valueToken; |
| 486 if (valueToken != null) { |
| 487 String value = valueToken.lexeme; |
| 488 if (value == _APPLICATION_DART_IN_DOUBLE_QUOTES || value == _APPLICATI
ON_DART_IN_SINGLE_QUOTES) { |
| 489 return true; |
| 490 } |
| 491 } |
| 492 } |
| 493 } |
| 494 return false; |
| 495 } |
| 522 } | 496 } |
| 523 | 497 |
| 524 /** | 498 /** |
| 525 * Instances of `XmlTagNode` represent XML or HTML elements such as `` and | 499 * Instances of the class `HtmlScriptTagNode` represent a script tag within an H
TML file that |
| 526 * `<body foo="bar"> ... </body>`. | 500 * references a Dart script. |
| 527 */ | 501 */ |
| 528 class XmlTagNode extends XmlNode { | 502 class HtmlScriptTagNode extends XmlTagNode { |
| 529 /** | 503 /** |
| 530 * Constant representing empty list of attributes. | 504 * The AST structure representing the Dart code within this tag. |
| 531 */ | 505 */ |
| 532 static List<XmlAttributeNode> NO_ATTRIBUTES = new UnmodifiableListView(new Lis
t<XmlAttributeNode>()); | 506 CompilationUnit _script; |
| 533 | 507 |
| 534 /** | 508 /** |
| 535 * Constant representing empty list of tag nodes. | 509 * The element representing this script. |
| 536 */ | 510 */ |
| 537 static List<XmlTagNode> NO_TAG_NODES = new UnmodifiableListView(new List<XmlTa
gNode>()); | 511 HtmlScriptElement scriptElement; |
| 538 | 512 |
| 539 /** | 513 /** |
| 540 * The starting [TokenType#LT] token (not `null`). | 514 * Initialize a newly created node to represent a script tag within an HTML fi
le that references a |
| 541 */ | 515 * Dart script. |
| 542 final Token nodeStart; | 516 * |
| 543 | 517 * @param nodeStart the token marking the beginning of the tag |
| 544 /** | 518 * @param tag the name of the tag |
| 545 * The [TokenType#TAG] token after the starting '<' (not `null`). | 519 * @param attributes the attributes in the tag |
| 546 */ | 520 * @param attributeEnd the token terminating the region where attributes can b
e |
| 547 final Token _tag; | 521 * @param tagNodes the children of the tag |
| 548 | 522 * @param contentEnd the token that starts the closing tag |
| 549 /** | 523 * @param closingTag the name of the tag that occurs in the closing tag |
| 550 * The attributes contained by the receiver (not `null`, contains no `null`s). | 524 * @param nodeEnd the last token in the tag |
| 551 */ | 525 */ |
| 552 List<XmlAttributeNode> _attributes; | 526 HtmlScriptTagNode(Token nodeStart, Token tag, List<XmlAttributeNode> attribute
s, Token attributeEnd, List<XmlTagNode> tagNodes, Token contentEnd, Token closin
gTag, Token nodeEnd) : super(nodeStart, tag, attributes, attributeEnd, tagNodes,
contentEnd, closingTag, nodeEnd); |
| 553 | 527 |
| 554 /** | 528 @override |
| 555 * The [TokenType#GT] or [TokenType#SLASH_GT] token after the attributes (not | 529 accept(XmlVisitor visitor) => visitor.visitHtmlScriptTagNode(this); |
| 556 * `null`). The token may be the same token as [nodeEnd] if there are no child | 530 |
| 557 * [tagNodes]. | 531 /** |
| 558 */ | 532 * Return the AST structure representing the Dart code within this tag, or `nu
ll` if this |
| 559 final Token attributeEnd; | 533 * tag references an external script. |
| 534 * |
| 535 * @return the AST structure representing the Dart code within this tag |
| 536 */ |
| 537 CompilationUnit get script => _script; |
| 538 |
| 539 /** |
| 540 * Set the AST structure representing the Dart code within this tag to the giv
en compilation unit. |
| 541 * |
| 542 * @param unit the AST structure representing the Dart code within this tag |
| 543 */ |
| 544 void set script(CompilationUnit unit) { |
| 545 _script = unit; |
| 546 } |
| 547 } |
| 548 |
| 549 /** |
| 550 * Instances of the class `HtmlUnit` represent the contents of an HTML file. |
| 551 */ |
| 552 class HtmlUnit extends XmlNode { |
| 553 /** |
| 554 * The first token in the token stream that was parsed to form this HTML unit. |
| 555 */ |
| 556 final Token beginToken; |
| 557 |
| 558 /** |
| 559 * The last token in the token stream that was parsed to form this compilation
unit. This token |
| 560 * should always have a type of [TokenType.EOF]. |
| 561 */ |
| 562 final Token endToken; |
| 560 | 563 |
| 561 /** | 564 /** |
| 562 * The tag nodes contained in the receiver (not `null`, contains no `null`s). | 565 * The tag nodes contained in the receiver (not `null`, contains no `null`s). |
| 563 */ | 566 */ |
| 564 List<XmlTagNode> _tagNodes; | 567 List<XmlTagNode> _tagNodes; |
| 565 | 568 |
| 566 /** | 569 /** |
| 567 * The token (not `null`) after the content, which may be | 570 * Construct a new instance representing the content of an HTML file. |
| 568 * * (1) [TokenType#LT_SLASH] for nodes with open and close tags, or | 571 * |
| 569 * * (2) the [TokenType#LT] nodeStart of the next sibling node if this node is
self | 572 * @param beginToken the first token in the file (not `null`) |
| 570 * closing or the attributeEnd is [TokenType#SLASH_GT], or | 573 * @param tagNodes child tag nodes of the receiver (not `null`, contains no `n
ull`s) |
| 571 * * (3) [TokenType#EOF] if the node does not have a closing tag and is the la
st node in | 574 * @param endToken the last token in the token stream which should be of type |
| 572 * the stream [TokenType#LT_SLASH] token after the content, or `null` if there
is no | 575 * [TokenType.EOF] |
| 573 * content and the attributes ended with [TokenType#SLASH_GT]. | 576 */ |
| 574 */ | 577 HtmlUnit(this.beginToken, List<XmlTagNode> tagNodes, this.endToken) { |
| 575 final Token contentEnd; | 578 this._tagNodes = becomeParentOfAll(tagNodes); |
| 576 | 579 } |
| 577 /** | 580 |
| 578 * The closing [TokenType#TAG] after the child elements or `null` if there is
no | 581 @override |
| 579 * content and the attributes ended with [TokenType#SLASH_GT] | 582 accept(XmlVisitor visitor) => visitor.visitHtmlUnit(this); |
| 580 */ | 583 |
| 581 final Token closingTag; | 584 /** |
| 582 | 585 * Return the element associated with this HTML unit. |
| 583 /** | 586 * |
| 584 * The ending [TokenType#GT] or [TokenType#SLASH_GT] token (not `null`). | 587 * @return the element or `null` if the receiver is not resolved |
| 585 */ | 588 */ |
| 586 final Token nodeEnd; | 589 @override |
| 587 | 590 HtmlElement get element => super.element as HtmlElement; |
| 588 /** | |
| 589 * The expressions that are embedded in the tag's content. | |
| 590 */ | |
| 591 List<XmlExpression> expressions = XmlExpression.EMPTY_ARRAY; | |
| 592 | |
| 593 /** | |
| 594 * Construct a new instance representing an XML or HTML element | |
| 595 * | |
| 596 * @param nodeStart the starting [TokenType#LT] token (not `null`) | |
| 597 * @param tag the [TokenType#TAG] token after the starting '<' (not `null`)
. | |
| 598 * @param attributes the attributes associated with this element or [NO_ATTRIB
UTES] (not | |
| 599 * `null`, contains no `null`s) | |
| 600 * @param attributeEnd The [TokenType#GT] or [TokenType#SLASH_GT] token after
the | |
| 601 * attributes (not `null`). The token may be the same token as [nodeE
nd] if | |
| 602 * there are no child [tagNodes]. | |
| 603 * @param tagNodes child tag nodes of the receiver or [NO_TAG_NODES] (not `nul
l`, | |
| 604 * contains no `null`s) | |
| 605 * @param contentEnd the token (not `null`) after the content, which may be | |
| 606 * * (1) [TokenType#LT_SLASH] for nodes with open and close tags, or | |
| 607 * * (2) the [TokenType#LT] nodeStart of the next sibling node if thi
s node is | |
| 608 * self closing or the attributeEnd is [TokenType#SLASH_GT], or | |
| 609 * * (3) [TokenType#EOF] if the node does not have a closing tag and
is the last | |
| 610 * node in the stream [TokenType#LT_SLASH] token after the content, o
r `null` | |
| 611 * if there is no content and the attributes ended with [TokenType#SL
ASH_GT]. | |
| 612 * @param closingTag the closing [TokenType#TAG] after the child elements or `
null` if | |
| 613 * there is no content and the attributes ended with [TokenType#SLASH
_GT] | |
| 614 * @param nodeEnd the ending [TokenType#GT] or [TokenType#SLASH_GT] token (not | |
| 615 * `null`) | |
| 616 */ | |
| 617 XmlTagNode(this.nodeStart, this._tag, List<XmlAttributeNode> attributes, this.
attributeEnd, List<XmlTagNode> tagNodes, this.contentEnd, this.closingTag, this.
nodeEnd) { | |
| 618 this._attributes = becomeParentOfAll(attributes, ifEmpty: NO_ATTRIBUTES); | |
| 619 this._tagNodes = becomeParentOfAll(tagNodes, ifEmpty: NO_TAG_NODES); | |
| 620 } | |
| 621 | |
| 622 @override | |
| 623 accept(XmlVisitor visitor) => visitor.visitXmlTagNode(this); | |
| 624 | |
| 625 /** | |
| 626 * Answer the attribute with the specified name. | |
| 627 * | |
| 628 * @param name the attribute name | |
| 629 * @return the attribute or `null` if no matching attribute is found | |
| 630 */ | |
| 631 XmlAttributeNode getAttribute(String name) { | |
| 632 for (XmlAttributeNode attribute in _attributes) { | |
| 633 if (attribute.name == name) { | |
| 634 return attribute; | |
| 635 } | |
| 636 } | |
| 637 return null; | |
| 638 } | |
| 639 | |
| 640 /** | |
| 641 * Answer the receiver's attributes. Callers should not manipulate the returne
d list to edit the | |
| 642 * AST structure. | |
| 643 * | |
| 644 * @return the attributes (not `null`, contains no `null`s) | |
| 645 */ | |
| 646 List<XmlAttributeNode> get attributes => _attributes; | |
| 647 | |
| 648 /** | |
| 649 * Find the attribute with the given name (see [getAttribute] and answer the l
exeme | |
| 650 * for the attribute's value token without the leading and trailing quotes (se
e | |
| 651 * [XmlAttributeNode#getText]). | |
| 652 * | |
| 653 * @param name the attribute name | |
| 654 * @return the attribute text or `null` if no matching attribute is found | |
| 655 */ | |
| 656 String getAttributeText(String name) { | |
| 657 XmlAttributeNode attribute = getAttribute(name); | |
| 658 return attribute != null ? attribute.text : null; | |
| 659 } | |
| 660 | |
| 661 @override | |
| 662 Token get beginToken => nodeStart; | |
| 663 | |
| 664 /** | |
| 665 * Answer a string representing the content contained in the receiver. This in
cludes the textual | |
| 666 * representation of any child tag nodes ([getTagNodes]). Whitespace between '
<', | |
| 667 * '</', and '>', '/>' is discarded, but all other whitespace is preserved. | |
| 668 * | |
| 669 * @return the content (not `null`) | |
| 670 */ | |
| 671 String get content { | |
| 672 Token token = attributeEnd.next; | |
| 673 if (identical(token, contentEnd)) { | |
| 674 return ""; | |
| 675 } | |
| 676 //TODO (danrubel): handle CDATA and replace HTML character encodings with th
e actual characters | |
| 677 String content = token.lexeme; | |
| 678 token = token.next; | |
| 679 if (identical(token, contentEnd)) { | |
| 680 return content; | |
| 681 } | |
| 682 JavaStringBuilder buffer = new JavaStringBuilder(); | |
| 683 while (!identical(token, contentEnd)) { | |
| 684 buffer.append(token.lexeme); | |
| 685 token = token.next; | |
| 686 } | |
| 687 return buffer.toString(); | |
| 688 } | |
| 689 | |
| 690 @override | |
| 691 Token get endToken { | |
| 692 if (nodeEnd != null) { | |
| 693 return nodeEnd; | |
| 694 } | |
| 695 if (closingTag != null) { | |
| 696 return closingTag; | |
| 697 } | |
| 698 if (contentEnd != null) { | |
| 699 return contentEnd; | |
| 700 } | |
| 701 if (!_tagNodes.isEmpty) { | |
| 702 return _tagNodes[_tagNodes.length - 1].endToken; | |
| 703 } | |
| 704 if (attributeEnd != null) { | |
| 705 return attributeEnd; | |
| 706 } | |
| 707 if (!_attributes.isEmpty) { | |
| 708 return _attributes[_attributes.length - 1].endToken; | |
| 709 } | |
| 710 return _tag; | |
| 711 } | |
| 712 | |
| 713 /** | |
| 714 * Answer the tag name after the starting '<'. | |
| 715 * | |
| 716 * @return the tag name (not `null`) | |
| 717 */ | |
| 718 String get tag => _tag.lexeme; | |
| 719 | 591 |
| 720 /** | 592 /** |
| 721 * Answer the tag nodes contained in the receiver. Callers should not manipula
te the returned list | 593 * Answer the tag nodes contained in the receiver. Callers should not manipula
te the returned list |
| 722 * to edit the AST structure. | 594 * to edit the AST structure. |
| 723 * | 595 * |
| 724 * @return the children (not `null`, contains no `null`s) | 596 * @return the children (not `null`, contains no `null`s) |
| 725 */ | 597 */ |
| 726 List<XmlTagNode> get tagNodes => _tagNodes; | 598 List<XmlTagNode> get tagNodes => _tagNodes; |
| 727 | 599 |
| 728 /** | 600 @override |
| 729 * Answer the [TokenType#TAG] token after the starting '<'. | 601 void set element(Element element) { |
| 730 * | 602 if (element != null && element is! HtmlElement) { |
| 731 * @return the token (not `null`) | 603 throw new IllegalArgumentException("HtmlElement expected, but ${element.ru
ntimeType} given"); |
| 732 */ | 604 } |
| 733 Token get tagToken => _tag; | 605 super.element = element; |
| 606 } |
| 734 | 607 |
| 735 @override | 608 @override |
| 736 void visitChildren(XmlVisitor visitor) { | 609 void visitChildren(XmlVisitor visitor) { |
| 737 for (XmlAttributeNode node in _attributes) { | |
| 738 node.accept(visitor); | |
| 739 } | |
| 740 for (XmlTagNode node in _tagNodes) { | 610 for (XmlTagNode node in _tagNodes) { |
| 741 node.accept(visitor); | 611 node.accept(visitor); |
| 742 } | 612 } |
| 743 } | 613 } |
| 744 } | 614 } |
| 745 | 615 |
| 746 /** | 616 /** |
| 747 * Instances of the class `XmlParser` are used to parse tokens into a AST struct
ure comprised | 617 * Utilities locating [Expression]s and [Element]s in [HtmlUnit]. |
| 748 * of [XmlNode]s. | |
| 749 */ | 618 */ |
| 750 class XmlParser { | 619 class HtmlUnitUtils { |
| 751 /** | 620 /** |
| 752 * The source being parsed. | 621 * Returns the [XmlAttributeNode] that is part of the given [HtmlUnit] and enc
loses |
| 622 * the given offset. |
| 753 */ | 623 */ |
| 754 final Source source; | 624 static XmlAttributeNode getAttributeNode(HtmlUnit htmlUnit, int offset) { |
| 755 | 625 if (htmlUnit == null) { |
| 756 /** | 626 return null; |
| 757 * The next token to be parsed. | |
| 758 */ | |
| 759 Token _currentToken; | |
| 760 | |
| 761 /** | |
| 762 * Construct a parser for the specified source. | |
| 763 * | |
| 764 * @param source the source being parsed | |
| 765 */ | |
| 766 XmlParser(this.source); | |
| 767 | |
| 768 /** | |
| 769 * Create a node representing an attribute. | |
| 770 * | |
| 771 * @param name the name of the attribute | |
| 772 * @param equals the equals sign, or `null` if there is no value | |
| 773 * @param value the value of the attribute | |
| 774 * @return the node that was created | |
| 775 */ | |
| 776 XmlAttributeNode createAttributeNode(Token name, Token equals, Token value) =>
new XmlAttributeNode(name, equals, value); | |
| 777 | |
| 778 /** | |
| 779 * Create a node representing a tag. | |
| 780 * | |
| 781 * @param nodeStart the token marking the beginning of the tag | |
| 782 * @param tag the name of the tag | |
| 783 * @param attributes the attributes in the tag | |
| 784 * @param attributeEnd the token terminating the region where attributes can b
e | |
| 785 * @param tagNodes the children of the tag | |
| 786 * @param contentEnd the token that starts the closing tag | |
| 787 * @param closingTag the name of the tag that occurs in the closing tag | |
| 788 * @param nodeEnd the last token in the tag | |
| 789 * @return the node that was created | |
| 790 */ | |
| 791 XmlTagNode createTagNode(Token nodeStart, Token tag, List<XmlAttributeNode> at
tributes, Token attributeEnd, List<XmlTagNode> tagNodes, Token contentEnd, Token
closingTag, Token nodeEnd) => new XmlTagNode(nodeStart, tag, attributes, attrib
uteEnd, tagNodes, contentEnd, closingTag, nodeEnd); | |
| 792 | |
| 793 /** | |
| 794 * Answer `true` if the specified tag is self closing and thus should never ha
ve content or | |
| 795 * child tag nodes. | |
| 796 * | |
| 797 * @param tag the tag (not `null`) | |
| 798 * @return `true` if self closing | |
| 799 */ | |
| 800 bool isSelfClosing(Token tag) => false; | |
| 801 | |
| 802 /** | |
| 803 * Parse the entire token stream and in the process, advance the current token
to the end of the | |
| 804 * token stream. | |
| 805 * | |
| 806 * @return the list of tag nodes found (not `null`, contains no `null`) | |
| 807 */ | |
| 808 List<XmlTagNode> parseTopTagNodes(Token firstToken) { | |
| 809 _currentToken = firstToken; | |
| 810 List<XmlTagNode> tagNodes = new List<XmlTagNode>(); | |
| 811 TokenType type = _currentToken.type; | |
| 812 while (type != TokenType.EOF) { | |
| 813 if (type == TokenType.LT) { | |
| 814 tagNodes.add(_parseTagNode()); | |
| 815 } else if (type == TokenType.DECLARATION || type == TokenType.DIRECTIVE ||
type == TokenType.COMMENT) { | |
| 816 // ignored tokens | |
| 817 _currentToken = _currentToken.next; | |
| 818 } else { | |
| 819 _reportUnexpectedToken(); | |
| 820 _currentToken = _currentToken.next; | |
| 821 } | |
| 822 type = _currentToken.type; | |
| 823 } | 627 } |
| 824 return tagNodes; | 628 List<XmlAttributeNode> result = [null]; |
| 629 try { |
| 630 htmlUnit.accept(new RecursiveXmlVisitor_HtmlUnitUtils_getAttributeNode(off
set, result)); |
| 631 } on HtmlUnitUtils_FoundAttributeNodeError catch (e) { |
| 632 return result[0]; |
| 633 } |
| 634 return null; |
| 825 } | 635 } |
| 826 | 636 |
| 827 /** | 637 /** |
| 828 * Answer the current token. | 638 * Returns the best [Element] of the given [Expression]. |
| 829 * | |
| 830 * @return the current token | |
| 831 */ | 639 */ |
| 832 Token get currentToken => _currentToken; | 640 static Element getElement(Expression expression) { |
| 833 | 641 if (expression == null) { |
| 834 /** | 642 return null; |
| 835 * Insert a synthetic token of the specified type before the current token | 643 } |
| 836 * | 644 return ElementLocator.locate(expression); |
| 837 * @param type the type of token to be inserted (not `null`) | |
| 838 * @return the synthetic token that was inserted (not `null`) | |
| 839 */ | |
| 840 Token _insertSyntheticToken(TokenType type) { | |
| 841 Token token = new Token.con2(type, _currentToken.offset, ""); | |
| 842 _currentToken.previous.setNext(token); | |
| 843 token.setNext(_currentToken); | |
| 844 return token; | |
| 845 } | 645 } |
| 846 | 646 |
| 847 /** | 647 /** |
| 848 * Parse the token stream for an attribute. This method advances the current t
oken over the | 648 * Returns the [Element] of the [Expression] in the given [HtmlUnit], enclosin
g |
| 849 * attribute, but should not be called if the [currentToken] is not [TokenType
#TAG]. | 649 * the given offset. |
| 850 * | |
| 851 * @return the attribute (not `null`) | |
| 852 */ | 650 */ |
| 853 XmlAttributeNode _parseAttribute() { | 651 static Element getElementAtOffset(HtmlUnit htmlUnit, int offset) { |
| 854 // Assume the current token is a tag | 652 Expression expression = getExpression(htmlUnit, offset); |
| 855 Token name = _currentToken; | 653 return getElement(expression); |
| 856 _currentToken = _currentToken.next; | |
| 857 // Equals sign | |
| 858 Token equals; | |
| 859 if (_currentToken.type == TokenType.EQ) { | |
| 860 equals = _currentToken; | |
| 861 _currentToken = _currentToken.next; | |
| 862 } else { | |
| 863 _reportUnexpectedToken(); | |
| 864 equals = _insertSyntheticToken(TokenType.EQ); | |
| 865 } | |
| 866 // String value | |
| 867 Token value; | |
| 868 if (_currentToken.type == TokenType.STRING) { | |
| 869 value = _currentToken; | |
| 870 _currentToken = _currentToken.next; | |
| 871 } else { | |
| 872 _reportUnexpectedToken(); | |
| 873 value = _insertSyntheticToken(TokenType.STRING); | |
| 874 } | |
| 875 return createAttributeNode(name, equals, value); | |
| 876 } | 654 } |
| 877 | 655 |
| 878 /** | 656 /** |
| 879 * Parse the stream for a sequence of attributes. This method advances the cur
rent token to the | 657 * Returns the [Element] to open when requested at the given [Expression]. |
| 880 * next [TokenType#GT], [TokenType#SLASH_GT], or [TokenType#EOF]. | |
| 881 * | |
| 882 * @return a collection of zero or more attributes (not `null`, contains no `n
ull`s) | |
| 883 */ | 658 */ |
| 884 List<XmlAttributeNode> _parseAttributes() { | 659 static Element getElementToOpen(HtmlUnit htmlUnit, Expression expression) { |
| 885 TokenType type = _currentToken.type; | 660 Element element = getElement(expression); |
| 886 if (type == TokenType.GT || type == TokenType.SLASH_GT || type == TokenType.
EOF) { | 661 { |
| 887 return XmlTagNode.NO_ATTRIBUTES; | 662 AngularElement angularElement = AngularHtmlUnitResolver.getAngularElement(
element); |
| 663 if (angularElement != null) { |
| 664 return angularElement; |
| 665 } |
| 888 } | 666 } |
| 889 List<XmlAttributeNode> attributes = new List<XmlAttributeNode>(); | 667 return element; |
| 890 while (type != TokenType.GT && type != TokenType.SLASH_GT && type != TokenTy
pe.EOF) { | |
| 891 if (type == TokenType.TAG) { | |
| 892 attributes.add(_parseAttribute()); | |
| 893 } else { | |
| 894 _reportUnexpectedToken(); | |
| 895 _currentToken = _currentToken.next; | |
| 896 } | |
| 897 type = _currentToken.type; | |
| 898 } | |
| 899 return attributes; | |
| 900 } | 668 } |
| 901 | 669 |
| 902 /** | 670 /** |
| 903 * Parse the stream for a sequence of tag nodes existing within a parent tag n
ode. This method | 671 * Returns the [XmlTagNode] that is part of the given [HtmlUnit] and encloses
the |
| 904 * advances the current token to the next [TokenType#LT_SLASH] or [TokenType#E
OF]. | 672 * given offset. |
| 905 * | |
| 906 * @return a list of nodes (not `null`, contains no `null`s) | |
| 907 */ | 673 */ |
| 908 List<XmlTagNode> _parseChildTagNodes() { | 674 static XmlTagNode getEnclosingTagNode(HtmlUnit htmlUnit, int offset) { |
| 909 TokenType type = _currentToken.type; | 675 if (htmlUnit == null) { |
| 910 if (type == TokenType.LT_SLASH || type == TokenType.EOF) { | 676 return null; |
| 911 return XmlTagNode.NO_TAG_NODES; | |
| 912 } | 677 } |
| 913 List<XmlTagNode> nodes = new List<XmlTagNode>(); | 678 List<XmlTagNode> result = [null]; |
| 914 while (type != TokenType.LT_SLASH && type != TokenType.EOF) { | 679 try { |
| 915 if (type == TokenType.LT) { | 680 htmlUnit.accept(new RecursiveXmlVisitor_HtmlUnitUtils_getEnclosingTagNode(
offset, result)); |
| 916 nodes.add(_parseTagNode()); | 681 } on HtmlUnitUtils_FoundTagNodeError catch (e) { |
| 917 } else if (type == TokenType.COMMENT) { | 682 return result[0]; |
| 918 // ignored token | |
| 919 _currentToken = _currentToken.next; | |
| 920 } else { | |
| 921 _reportUnexpectedToken(); | |
| 922 _currentToken = _currentToken.next; | |
| 923 } | |
| 924 type = _currentToken.type; | |
| 925 } | 683 } |
| 926 return nodes; | 684 return null; |
| 927 } | 685 } |
| 928 | 686 |
| 929 /** | 687 /** |
| 930 * Parse the token stream for the next tag node. This method advances current
token over the | 688 * Returns the [Expression] that is part of the given [HtmlUnit] and encloses
the |
| 931 * parsed tag node, but should only be called if the current token is [TokenTy
pe#LT] | 689 * given offset. |
| 932 * | |
| 933 * @return the tag node or `null` if none found | |
| 934 */ | 690 */ |
| 935 XmlTagNode _parseTagNode() { | 691 static Expression getExpression(HtmlUnit htmlUnit, int offset) { |
| 936 // Assume that the current node is a tag node start TokenType#LT | 692 if (htmlUnit == null) { |
| 937 Token nodeStart = _currentToken; | 693 return null; |
| 938 _currentToken = _currentToken.next; | |
| 939 // Get the tag or create a synthetic tag and report an error | |
| 940 Token tag; | |
| 941 if (_currentToken.type == TokenType.TAG) { | |
| 942 tag = _currentToken; | |
| 943 _currentToken = _currentToken.next; | |
| 944 } else { | |
| 945 _reportUnexpectedToken(); | |
| 946 tag = _insertSyntheticToken(TokenType.TAG); | |
| 947 } | 694 } |
| 948 // Parse the attributes | 695 List<Expression> result = [null]; |
| 949 List<XmlAttributeNode> attributes = _parseAttributes(); | 696 try { |
| 950 // Token ending attribute list | 697 // TODO(scheglov) this code is very Angular specific |
| 951 Token attributeEnd; | 698 htmlUnit.accept(new ExpressionVisitor_HtmlUnitUtils_getExpression(offset,
result)); |
| 952 if (_currentToken.type == TokenType.GT || _currentToken.type == TokenType.SL
ASH_GT) { | 699 } on HtmlUnitUtils_FoundExpressionError catch (e) { |
| 953 attributeEnd = _currentToken; | 700 return result[0]; |
| 954 _currentToken = _currentToken.next; | |
| 955 } else { | |
| 956 _reportUnexpectedToken(); | |
| 957 attributeEnd = _insertSyntheticToken(TokenType.SLASH_GT); | |
| 958 } | 701 } |
| 959 // If the node has no children, then return the node | 702 return null; |
| 960 if (attributeEnd.type == TokenType.SLASH_GT || isSelfClosing(tag)) { | |
| 961 return createTagNode(nodeStart, tag, attributes, attributeEnd, XmlTagNode.
NO_TAG_NODES, _currentToken, null, attributeEnd); | |
| 962 } | |
| 963 // Parse the child tag nodes | |
| 964 List<XmlTagNode> tagNodes = _parseChildTagNodes(); | |
| 965 // Token ending child tag nodes | |
| 966 Token contentEnd; | |
| 967 if (_currentToken.type == TokenType.LT_SLASH) { | |
| 968 contentEnd = _currentToken; | |
| 969 _currentToken = _currentToken.next; | |
| 970 } else { | |
| 971 // TODO (danrubel): handle self closing HTML elements by inserting synthet
ic tokens | |
| 972 // but not reporting an error | |
| 973 _reportUnexpectedToken(); | |
| 974 contentEnd = _insertSyntheticToken(TokenType.LT_SLASH); | |
| 975 } | |
| 976 // Closing tag | |
| 977 Token closingTag; | |
| 978 if (_currentToken.type == TokenType.TAG) { | |
| 979 closingTag = _currentToken; | |
| 980 _currentToken = _currentToken.next; | |
| 981 } else { | |
| 982 _reportUnexpectedToken(); | |
| 983 closingTag = _insertSyntheticToken(TokenType.TAG); | |
| 984 } | |
| 985 // Token ending node | |
| 986 Token nodeEnd; | |
| 987 if (_currentToken.type == TokenType.GT) { | |
| 988 nodeEnd = _currentToken; | |
| 989 _currentToken = _currentToken.next; | |
| 990 } else { | |
| 991 _reportUnexpectedToken(); | |
| 992 nodeEnd = _insertSyntheticToken(TokenType.GT); | |
| 993 } | |
| 994 return createTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes, con
tentEnd, closingTag, nodeEnd); | |
| 995 } | 703 } |
| 996 | 704 |
| 997 /** | 705 /** |
| 998 * Report the current token as unexpected | 706 * Returns the [XmlTagNode] that is part of the given [HtmlUnit] and its open
or |
| 707 * closing tag name encloses the given offset. |
| 999 */ | 708 */ |
| 1000 void _reportUnexpectedToken() { | 709 static XmlTagNode getTagNode(HtmlUnit htmlUnit, int offset) { |
| 710 XmlTagNode node = getEnclosingTagNode(htmlUnit, offset); |
| 711 // do we have an enclosing tag at all? |
| 712 if (node == null) { |
| 713 return null; |
| 714 } |
| 715 // is "offset" in the open tag? |
| 716 Token openTag = node.tagToken; |
| 717 if (openTag.offset <= offset && offset <= openTag.end) { |
| 718 return node; |
| 719 } |
| 720 // is "offset" in the open tag? |
| 721 Token closeTag = node.closingTag; |
| 722 if (closeTag != null && closeTag.offset <= offset && offset <= closeTag.end)
{ |
| 723 return node; |
| 724 } |
| 725 // not on a tag name |
| 726 return null; |
| 727 } |
| 728 |
| 729 /** |
| 730 * Returns the [Expression] that is part of the given root [AstNode] and enclo
ses the |
| 731 * given offset. |
| 732 */ |
| 733 static Expression _getExpressionAt(AstNode root, int offset) { |
| 734 if (root.offset <= offset && offset <= root.end) { |
| 735 AstNode dartNode = new NodeLocator.con1(offset).searchWithin(root); |
| 736 if (dartNode is Expression) { |
| 737 return dartNode; |
| 738 } |
| 739 } |
| 740 return null; |
| 1001 } | 741 } |
| 1002 } | 742 } |
| 1003 | 743 |
| 1004 /** | 744 class HtmlUnitUtils_FoundAttributeNodeError extends Error { |
| 1005 * The abstract class `XmlNode` defines behavior common to all XML/HTML nodes. | 745 } |
| 1006 */ | |
| 1007 abstract class XmlNode { | |
| 1008 /** | |
| 1009 * The parent of the node, or `null` if the node is the root of an AST structu
re. | |
| 1010 */ | |
| 1011 XmlNode _parent; | |
| 1012 | 746 |
| 1013 /** | 747 class HtmlUnitUtils_FoundExpressionError extends Error { |
| 1014 * The element associated with this node or `null` if the receiver is not reso
lved. | 748 } |
| 1015 */ | |
| 1016 Element _element; | |
| 1017 | 749 |
| 1018 /** | 750 class HtmlUnitUtils_FoundTagNodeError extends Error { |
| 1019 * Use the given visitor to visit this node. | |
| 1020 * | |
| 1021 * @param visitor the visitor that will visit this node | |
| 1022 * @return the value returned by the visitor as a result of visiting this node | |
| 1023 */ | |
| 1024 accept(XmlVisitor visitor); | |
| 1025 | |
| 1026 /** | |
| 1027 * Return the first token included in this node's source range. | |
| 1028 * | |
| 1029 * @return the first token or `null` if none | |
| 1030 */ | |
| 1031 Token get beginToken; | |
| 1032 | |
| 1033 /** | |
| 1034 * Return the element associated with this node. | |
| 1035 * | |
| 1036 * @return the element or `null` if the receiver is not resolved | |
| 1037 */ | |
| 1038 Element get element => _element; | |
| 1039 | |
| 1040 /** | |
| 1041 * Return the offset of the character immediately following the last character
of this node's | |
| 1042 * source range. This is equivalent to `node.getOffset() + node.getLength()`.
For an html | |
| 1043 * unit this will be equal to the length of the unit's source. | |
| 1044 * | |
| 1045 * @return the offset of the character just past the node's source range | |
| 1046 */ | |
| 1047 int get end => offset + length; | |
| 1048 | |
| 1049 /** | |
| 1050 * Return the last token included in this node's source range. | |
| 1051 * | |
| 1052 * @return the last token or `null` if none | |
| 1053 */ | |
| 1054 Token get endToken; | |
| 1055 | |
| 1056 /** | |
| 1057 * Return the number of characters in the node's source range. | |
| 1058 * | |
| 1059 * @return the number of characters in the node's source range | |
| 1060 */ | |
| 1061 int get length { | |
| 1062 Token beginToken = this.beginToken; | |
| 1063 Token endToken = this.endToken; | |
| 1064 if (beginToken == null || endToken == null) { | |
| 1065 return -1; | |
| 1066 } | |
| 1067 return endToken.offset + endToken.length - beginToken.offset; | |
| 1068 } | |
| 1069 | |
| 1070 /** | |
| 1071 * Return the offset from the beginning of the file to the first character in
the node's source | |
| 1072 * range. | |
| 1073 * | |
| 1074 * @return the offset from the beginning of the file to the first character in
the node's source | |
| 1075 * range | |
| 1076 */ | |
| 1077 int get offset { | |
| 1078 Token beginToken = this.beginToken; | |
| 1079 if (beginToken == null) { | |
| 1080 return -1; | |
| 1081 } | |
| 1082 return this.beginToken.offset; | |
| 1083 } | |
| 1084 | |
| 1085 /** | |
| 1086 * Return this node's parent node, or `null` if this node is the root of an AS
T structure. | |
| 1087 * | |
| 1088 * Note that the relationship between an AST node and its parent node may chan
ge over the lifetime | |
| 1089 * of a node. | |
| 1090 * | |
| 1091 * @return the parent of this node, or `null` if none | |
| 1092 */ | |
| 1093 XmlNode get parent => _parent; | |
| 1094 | |
| 1095 /** | |
| 1096 * Set the element associated with this node. | |
| 1097 * | |
| 1098 * @param element the element | |
| 1099 */ | |
| 1100 void set element(Element element) { | |
| 1101 this._element = element; | |
| 1102 } | |
| 1103 | |
| 1104 @override | |
| 1105 String toString() { | |
| 1106 PrintStringWriter writer = new PrintStringWriter(); | |
| 1107 accept(new ToSourceVisitor(writer)); | |
| 1108 return writer.toString(); | |
| 1109 } | |
| 1110 | |
| 1111 /** | |
| 1112 * Use the given visitor to visit all of the children of this node. The childr
en will be visited | |
| 1113 * in source order. | |
| 1114 * | |
| 1115 * @param visitor the visitor that will be used to visit the children of this
node | |
| 1116 */ | |
| 1117 void visitChildren(XmlVisitor visitor); | |
| 1118 | |
| 1119 /** | |
| 1120 * Make this node the parent of the given child node. | |
| 1121 * | |
| 1122 * @param child the node that will become a child of this node | |
| 1123 * @return the node that was made a child of this node | |
| 1124 */ | |
| 1125 XmlNode becomeParentOf(XmlNode child) { | |
| 1126 if (child != null) { | |
| 1127 XmlNode node = child; | |
| 1128 node.parent = this; | |
| 1129 } | |
| 1130 return child; | |
| 1131 } | |
| 1132 | |
| 1133 /** | |
| 1134 * Make this node the parent of the given child nodes. | |
| 1135 * | |
| 1136 * @param children the nodes that will become the children of this node | |
| 1137 * @param ifEmpty the (empty) nodes to return if "children" is empty | |
| 1138 * @return the nodes that were made children of this node | |
| 1139 */ | |
| 1140 List becomeParentOfAll(List children, {List ifEmpty}) { | |
| 1141 if (children == null || children.isEmpty) { | |
| 1142 if (ifEmpty != null) { | |
| 1143 return ifEmpty; | |
| 1144 } | |
| 1145 } | |
| 1146 if (children != null) { | |
| 1147 for (JavaIterator iter = new JavaIterator(children); iter.hasNext;) { | |
| 1148 XmlNode node = iter.next(); | |
| 1149 node.parent = this; | |
| 1150 } | |
| 1151 // This will create ArrayList for exactly given number of elements. | |
| 1152 return new List.from(children); | |
| 1153 } | |
| 1154 return children; | |
| 1155 } | |
| 1156 | |
| 1157 /** | |
| 1158 * This method exists for debugging purposes only. | |
| 1159 */ | |
| 1160 void _appendIdentifier(JavaStringBuilder builder, XmlNode node) { | |
| 1161 if (node is XmlTagNode) { | |
| 1162 builder.append(node.tag); | |
| 1163 } else if (node is XmlAttributeNode) { | |
| 1164 builder.append(node.name); | |
| 1165 } else { | |
| 1166 builder.append("htmlUnit"); | |
| 1167 } | |
| 1168 } | |
| 1169 | |
| 1170 /** | |
| 1171 * This method exists for debugging purposes only. | |
| 1172 */ | |
| 1173 String _buildRecursiveStructureMessage(XmlNode newParent) { | |
| 1174 JavaStringBuilder builder = new JavaStringBuilder(); | |
| 1175 builder.append("Attempt to create recursive structure: "); | |
| 1176 XmlNode current = newParent; | |
| 1177 while (current != null) { | |
| 1178 if (!identical(current, newParent)) { | |
| 1179 builder.append(" -> "); | |
| 1180 } | |
| 1181 if (identical(current, this)) { | |
| 1182 builder.appendChar(0x2A); | |
| 1183 _appendIdentifier(builder, current); | |
| 1184 builder.appendChar(0x2A); | |
| 1185 } else { | |
| 1186 _appendIdentifier(builder, current); | |
| 1187 } | |
| 1188 current = current.parent; | |
| 1189 } | |
| 1190 return builder.toString(); | |
| 1191 } | |
| 1192 | |
| 1193 /** | |
| 1194 * Set the parent of this node to the given node. | |
| 1195 * | |
| 1196 * @param newParent the node that is to be made the parent of this node | |
| 1197 */ | |
| 1198 void set parent(XmlNode newParent) { | |
| 1199 XmlNode current = newParent; | |
| 1200 while (current != null) { | |
| 1201 if (identical(current, this)) { | |
| 1202 AnalysisEngine.instance.logger.logError2("Circular structure while setti
ng an XML node's parent", new IllegalArgumentException(_buildRecursiveStructureM
essage(newParent))); | |
| 1203 return; | |
| 1204 } | |
| 1205 current = current.parent; | |
| 1206 } | |
| 1207 _parent = newParent; | |
| 1208 } | |
| 1209 } | 751 } |
| 1210 | 752 |
| 1211 /** | 753 /** |
| 1212 * Implementation of [XmlExpression] for an [Expression] embedded without any wr
apping | 754 * Implementation of [XmlExpression] for an [Expression] embedded without any wr
apping |
| 1213 * characters. | 755 * characters. |
| 1214 */ | 756 */ |
| 1215 class RawXmlExpression extends XmlExpression { | 757 class RawXmlExpression extends XmlExpression { |
| 1216 final Expression expression; | 758 final Expression expression; |
| 1217 | 759 |
| 1218 RawXmlExpression(this.expression); | 760 RawXmlExpression(this.expression); |
| (...skipping 12 matching lines...) Expand all Loading... |
| 1231 AstNode node = new NodeLocator.con1(offset).searchWithin(expression); | 773 AstNode node = new NodeLocator.con1(offset).searchWithin(expression); |
| 1232 if (node != null) { | 774 if (node != null) { |
| 1233 Element element = ElementLocator.locate(node); | 775 Element element = ElementLocator.locate(node); |
| 1234 return new XmlExpression_Reference(element, node.offset, node.length); | 776 return new XmlExpression_Reference(element, node.offset, node.length); |
| 1235 } | 777 } |
| 1236 return null; | 778 return null; |
| 1237 } | 779 } |
| 1238 } | 780 } |
| 1239 | 781 |
| 1240 /** | 782 /** |
| 1241 * Utilities locating [Expression]s and [Element]s in [HtmlUnit]. | 783 * Instances of the class `RecursiveXmlVisitor` implement an XML visitor that wi
ll recursively |
| 784 * visit all of the nodes in an XML structure. For example, using an instance of
this class to visit |
| 785 * a [XmlTagNode] will also cause all of the contained [XmlAttributeNode]s and |
| 786 * [XmlTagNode]s to be visited. |
| 787 * |
| 788 * Subclasses that override a visit method must either invoke the overridden vis
it method or must |
| 789 * explicitly ask the visited node to visit its children. Failure to do so will
cause the children |
| 790 * of the visited node to not be visited. |
| 1242 */ | 791 */ |
| 1243 class HtmlUnitUtils { | 792 class RecursiveXmlVisitor<R> implements XmlVisitor<R> { |
| 1244 /** | 793 @override |
| 1245 * Returns the [XmlAttributeNode] that is part of the given [HtmlUnit] and enc
loses | 794 R visitHtmlScriptTagNode(HtmlScriptTagNode node) { |
| 1246 * the given offset. | 795 node.visitChildren(this); |
| 1247 */ | |
| 1248 static XmlAttributeNode getAttributeNode(HtmlUnit htmlUnit, int offset) { | |
| 1249 if (htmlUnit == null) { | |
| 1250 return null; | |
| 1251 } | |
| 1252 List<XmlAttributeNode> result = [null]; | |
| 1253 try { | |
| 1254 htmlUnit.accept(new RecursiveXmlVisitor_HtmlUnitUtils_getAttributeNode(off
set, result)); | |
| 1255 } on HtmlUnitUtils_FoundAttributeNodeError catch (e) { | |
| 1256 return result[0]; | |
| 1257 } | |
| 1258 return null; | 796 return null; |
| 1259 } | 797 } |
| 1260 | 798 |
| 1261 /** | 799 @override |
| 1262 * Returns the best [Element] of the given [Expression]. | 800 R visitHtmlUnit(HtmlUnit node) { |
| 1263 */ | 801 node.visitChildren(this); |
| 1264 static Element getElement(Expression expression) { | |
| 1265 if (expression == null) { | |
| 1266 return null; | |
| 1267 } | |
| 1268 return ElementLocator.locate(expression); | |
| 1269 } | |
| 1270 | |
| 1271 /** | |
| 1272 * Returns the [Element] of the [Expression] in the given [HtmlUnit], enclosin
g | |
| 1273 * the given offset. | |
| 1274 */ | |
| 1275 static Element getElementAtOffset(HtmlUnit htmlUnit, int offset) { | |
| 1276 Expression expression = getExpression(htmlUnit, offset); | |
| 1277 return getElement(expression); | |
| 1278 } | |
| 1279 | |
| 1280 /** | |
| 1281 * Returns the [Element] to open when requested at the given [Expression]. | |
| 1282 */ | |
| 1283 static Element getElementToOpen(HtmlUnit htmlUnit, Expression expression) { | |
| 1284 Element element = getElement(expression); | |
| 1285 { | |
| 1286 AngularElement angularElement = AngularHtmlUnitResolver.getAngularElement(
element); | |
| 1287 if (angularElement != null) { | |
| 1288 return angularElement; | |
| 1289 } | |
| 1290 } | |
| 1291 return element; | |
| 1292 } | |
| 1293 | |
| 1294 /** | |
| 1295 * Returns the [XmlTagNode] that is part of the given [HtmlUnit] and encloses
the | |
| 1296 * given offset. | |
| 1297 */ | |
| 1298 static XmlTagNode getEnclosingTagNode(HtmlUnit htmlUnit, int offset) { | |
| 1299 if (htmlUnit == null) { | |
| 1300 return null; | |
| 1301 } | |
| 1302 List<XmlTagNode> result = [null]; | |
| 1303 try { | |
| 1304 htmlUnit.accept(new RecursiveXmlVisitor_HtmlUnitUtils_getEnclosingTagNode(
offset, result)); | |
| 1305 } on HtmlUnitUtils_FoundTagNodeError catch (e) { | |
| 1306 return result[0]; | |
| 1307 } | |
| 1308 return null; | 802 return null; |
| 1309 } | 803 } |
| 1310 | 804 |
| 1311 /** | 805 @override |
| 1312 * Returns the [Expression] that is part of the given [HtmlUnit] and encloses
the | 806 R visitXmlAttributeNode(XmlAttributeNode node) { |
| 1313 * given offset. | 807 node.visitChildren(this); |
| 1314 */ | |
| 1315 static Expression getExpression(HtmlUnit htmlUnit, int offset) { | |
| 1316 if (htmlUnit == null) { | |
| 1317 return null; | |
| 1318 } | |
| 1319 List<Expression> result = [null]; | |
| 1320 try { | |
| 1321 // TODO(scheglov) this code is very Angular specific | |
| 1322 htmlUnit.accept(new ExpressionVisitor_HtmlUnitUtils_getExpression(offset,
result)); | |
| 1323 } on HtmlUnitUtils_FoundExpressionError catch (e) { | |
| 1324 return result[0]; | |
| 1325 } | |
| 1326 return null; | 808 return null; |
| 1327 } | 809 } |
| 1328 | 810 |
| 1329 /** | 811 @override |
| 1330 * Returns the [XmlTagNode] that is part of the given [HtmlUnit] and its open
or | 812 R visitXmlTagNode(XmlTagNode node) { |
| 1331 * closing tag name encloses the given offset. | 813 node.visitChildren(this); |
| 1332 */ | |
| 1333 static XmlTagNode getTagNode(HtmlUnit htmlUnit, int offset) { | |
| 1334 XmlTagNode node = getEnclosingTagNode(htmlUnit, offset); | |
| 1335 // do we have an enclosing tag at all? | |
| 1336 if (node == null) { | |
| 1337 return null; | |
| 1338 } | |
| 1339 // is "offset" in the open tag? | |
| 1340 Token openTag = node.tagToken; | |
| 1341 if (openTag.offset <= offset && offset <= openTag.end) { | |
| 1342 return node; | |
| 1343 } | |
| 1344 // is "offset" in the open tag? | |
| 1345 Token closeTag = node.closingTag; | |
| 1346 if (closeTag != null && closeTag.offset <= offset && offset <= closeTag.end)
{ | |
| 1347 return node; | |
| 1348 } | |
| 1349 // not on a tag name | |
| 1350 return null; | 814 return null; |
| 1351 } | 815 } |
| 1352 | |
| 1353 /** | |
| 1354 * Returns the [Expression] that is part of the given root [AstNode] and enclo
ses the | |
| 1355 * given offset. | |
| 1356 */ | |
| 1357 static Expression _getExpressionAt(AstNode root, int offset) { | |
| 1358 if (root.offset <= offset && offset <= root.end) { | |
| 1359 AstNode dartNode = new NodeLocator.con1(offset).searchWithin(root); | |
| 1360 if (dartNode is Expression) { | |
| 1361 return dartNode; | |
| 1362 } | |
| 1363 } | |
| 1364 return null; | |
| 1365 } | |
| 1366 } | |
| 1367 | |
| 1368 class HtmlUnitUtils_FoundAttributeNodeError extends Error { | |
| 1369 } | |
| 1370 | |
| 1371 class HtmlUnitUtils_FoundExpressionError extends Error { | |
| 1372 } | |
| 1373 | |
| 1374 class HtmlUnitUtils_FoundTagNodeError extends Error { | |
| 1375 } | 816 } |
| 1376 | 817 |
| 1377 class RecursiveXmlVisitor_HtmlUnitUtils_getAttributeNode extends RecursiveXmlVis
itor<Object> { | 818 class RecursiveXmlVisitor_HtmlUnitUtils_getAttributeNode extends RecursiveXmlVis
itor<Object> { |
| 1378 int offset = 0; | 819 int offset = 0; |
| 1379 | 820 |
| 1380 List<XmlAttributeNode> result; | 821 List<XmlAttributeNode> result; |
| 1381 | 822 |
| 1382 RecursiveXmlVisitor_HtmlUnitUtils_getAttributeNode(this.offset, this.result) :
super(); | 823 RecursiveXmlVisitor_HtmlUnitUtils_getAttributeNode(this.offset, this.result) :
super(); |
| 1383 | 824 |
| 1384 @override | 825 @override |
| (...skipping 18 matching lines...) Expand all Loading... |
| 1403 Object visitXmlTagNode(XmlTagNode node) { | 844 Object visitXmlTagNode(XmlTagNode node) { |
| 1404 if (node.offset <= offset && offset < node.end) { | 845 if (node.offset <= offset && offset < node.end) { |
| 1405 result[0] = node; | 846 result[0] = node; |
| 1406 super.visitXmlTagNode(node); | 847 super.visitXmlTagNode(node); |
| 1407 throw new HtmlUnitUtils_FoundTagNodeError(); | 848 throw new HtmlUnitUtils_FoundTagNodeError(); |
| 1408 } | 849 } |
| 1409 return null; | 850 return null; |
| 1410 } | 851 } |
| 1411 } | 852 } |
| 1412 | 853 |
| 1413 class ExpressionVisitor_HtmlUnitUtils_getExpression extends ExpressionVisitor { | |
| 1414 int offset = 0; | |
| 1415 | |
| 1416 List<Expression> result; | |
| 1417 | |
| 1418 ExpressionVisitor_HtmlUnitUtils_getExpression(this.offset, this.result) : supe
r(); | |
| 1419 | |
| 1420 @override | |
| 1421 void visitExpression(Expression expression) { | |
| 1422 Expression at = HtmlUnitUtils._getExpressionAt(expression, offset); | |
| 1423 if (at != null) { | |
| 1424 result[0] = at; | |
| 1425 throw new HtmlUnitUtils_FoundExpressionError(); | |
| 1426 } | |
| 1427 } | |
| 1428 } | |
| 1429 | |
| 1430 /** | 854 /** |
| 1431 * The interface `XmlVisitor` defines the behavior of objects that can be used t
o visit an | 855 * Instances of the class `SimpleXmlVisitor` implement an AST visitor that will
do nothing |
| 1432 * [XmlNode] structure. | 856 * when visiting an AST node. It is intended to be a superclass for classes that
use the visitor |
| 857 * pattern primarily as a dispatch mechanism (and hence don't need to recursivel
y visit a whole |
| 858 * structure) and that only need to visit a small number of node types. |
| 1433 */ | 859 */ |
| 1434 abstract class XmlVisitor<R> { | 860 class SimpleXmlVisitor<R> implements XmlVisitor<R> { |
| 1435 R visitHtmlScriptTagNode(HtmlScriptTagNode node); | 861 @override |
| 862 R visitHtmlScriptTagNode(HtmlScriptTagNode node) => null; |
| 1436 | 863 |
| 1437 R visitHtmlUnit(HtmlUnit htmlUnit); | 864 @override |
| 865 R visitHtmlUnit(HtmlUnit htmlUnit) => null; |
| 1438 | 866 |
| 1439 R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode); | 867 @override |
| 868 R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode) => null; |
| 1440 | 869 |
| 1441 R visitXmlTagNode(XmlTagNode xmlTagNode); | 870 @override |
| 871 R visitXmlTagNode(XmlTagNode xmlTagNode) => null; |
| 1442 } | 872 } |
| 1443 | 873 |
| 1444 /** | 874 /** |
| 875 * Instances of the class `StringScanner` implement a scanner that reads from a
string. The |
| 876 * scanning logic is in the superclass. |
| 877 */ |
| 878 class StringScanner extends AbstractScanner { |
| 879 /** |
| 880 * The string from which characters will be read. |
| 881 */ |
| 882 final String _string; |
| 883 |
| 884 /** |
| 885 * The number of characters in the string. |
| 886 */ |
| 887 int _stringLength = 0; |
| 888 |
| 889 /** |
| 890 * The index, relative to the string, of the last character that was read. |
| 891 */ |
| 892 int _charOffset = 0; |
| 893 |
| 894 /** |
| 895 * Initialize a newly created scanner to scan the characters in the given stri
ng. |
| 896 * |
| 897 * @param source the source being scanned |
| 898 * @param string the string from which characters will be read |
| 899 */ |
| 900 StringScanner(Source source, this._string) : super(source) { |
| 901 this._stringLength = _string.length; |
| 902 this._charOffset = -1; |
| 903 } |
| 904 |
| 905 @override |
| 906 int get offset => _charOffset; |
| 907 |
| 908 void set offset(int offset) { |
| 909 _charOffset = offset; |
| 910 } |
| 911 |
| 912 @override |
| 913 int advance() { |
| 914 if (++_charOffset < _stringLength) { |
| 915 return _string.codeUnitAt(_charOffset); |
| 916 } |
| 917 _charOffset = _stringLength; |
| 918 return -1; |
| 919 } |
| 920 |
| 921 @override |
| 922 String getString(int start, int endDelta) => _string.substring(start, _charOff
set + 1 + endDelta).toString(); |
| 923 |
| 924 @override |
| 925 int peek() { |
| 926 if (_charOffset + 1 < _stringLength) { |
| 927 return _string.codeUnitAt(_charOffset + 1); |
| 928 } |
| 929 return -1; |
| 930 } |
| 931 } |
| 932 |
| 933 /** |
| 1445 * Instances of the class `ToSourceVisitor` write a source representation of a v
isited XML | 934 * Instances of the class `ToSourceVisitor` write a source representation of a v
isited XML |
| 1446 * node (and all of it's children) to a writer. | 935 * node (and all of it's children) to a writer. |
| 1447 */ | 936 */ |
| 1448 class ToSourceVisitor implements XmlVisitor<Object> { | 937 class ToSourceVisitor implements XmlVisitor<Object> { |
| 1449 /** | 938 /** |
| 1450 * The writer to which the source is to be written. | 939 * The writer to which the source is to be written. |
| 1451 */ | 940 */ |
| 1452 final PrintWriter _writer; | 941 final PrintWriter _writer; |
| 1453 | 942 |
| 1454 /** | 943 /** |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1515 * @param node the node to be visited | 1004 * @param node the node to be visited |
| 1516 */ | 1005 */ |
| 1517 void _visit(XmlNode node) { | 1006 void _visit(XmlNode node) { |
| 1518 if (node != null) { | 1007 if (node != null) { |
| 1519 node.accept(this); | 1008 node.accept(this); |
| 1520 } | 1009 } |
| 1521 } | 1010 } |
| 1522 } | 1011 } |
| 1523 | 1012 |
| 1524 /** | 1013 /** |
| 1525 * Instances of the class `HtmlUnit` represent the contents of an HTML file. | 1014 * Instances of the class `Token` represent a token that was scanned from the in
put. Each |
| 1015 * token knows which token follows it, acting as the head of a linked list of to
kens. |
| 1526 */ | 1016 */ |
| 1527 class HtmlUnit extends XmlNode { | 1017 class Token { |
| 1528 /** | 1018 /** |
| 1529 * The first token in the token stream that was parsed to form this HTML unit. | 1019 * The offset from the beginning of the file to the first character in the tok
en. |
| 1530 */ | 1020 */ |
| 1531 final Token beginToken; | 1021 final int offset; |
| 1532 | 1022 |
| 1533 /** | 1023 /** |
| 1534 * The last token in the token stream that was parsed to form this compilation
unit. This token | 1024 * The previous token in the token stream. |
| 1535 * should always have a type of [TokenType.EOF]. | 1025 */ |
| 1536 */ | 1026 Token previous; |
| 1537 final Token endToken; | 1027 |
| 1538 | 1028 /** |
| 1539 /** | 1029 * The next token in the token stream. |
| 1540 * The tag nodes contained in the receiver (not `null`, contains no `null`s). | 1030 */ |
| 1541 */ | 1031 Token _next; |
| 1542 List<XmlTagNode> _tagNodes; | 1032 |
| 1543 | 1033 /** |
| 1544 /** | 1034 * The type of the token. |
| 1545 * Construct a new instance representing the content of an HTML file. | 1035 */ |
| 1546 * | 1036 final TokenType type; |
| 1547 * @param beginToken the first token in the file (not `null`) | 1037 |
| 1548 * @param tagNodes child tag nodes of the receiver (not `null`, contains no `n
ull`s) | 1038 /** |
| 1549 * @param endToken the last token in the token stream which should be of type | 1039 * The lexeme represented by this token. |
| 1550 * [TokenType.EOF] | 1040 */ |
| 1551 */ | 1041 String _value; |
| 1552 HtmlUnit(this.beginToken, List<XmlTagNode> tagNodes, this.endToken) { | 1042 |
| 1553 this._tagNodes = becomeParentOfAll(tagNodes); | 1043 /** |
| 1044 * Initialize a newly created token. |
| 1045 * |
| 1046 * @param type the token type (not `null`) |
| 1047 * @param offset the offset from the beginning of the file to the first charac
ter in the token |
| 1048 */ |
| 1049 Token.con1(TokenType type, int offset) : this.con2(type, offset, type.lexeme); |
| 1050 |
| 1051 /** |
| 1052 * Initialize a newly created token. |
| 1053 * |
| 1054 * @param type the token type (not `null`) |
| 1055 * @param offset the offset from the beginning of the file to the first charac
ter in the token |
| 1056 * @param value the lexeme represented by this token (not `null`) |
| 1057 */ |
| 1058 Token.con2(this.type, this.offset, String value) { |
| 1059 this._value = StringUtilities.intern(value); |
| 1060 } |
| 1061 |
| 1062 /** |
| 1063 * Return the offset from the beginning of the file to the character after las
t character of the |
| 1064 * token. |
| 1065 * |
| 1066 * @return the offset from the beginning of the file to the first character af
ter last character |
| 1067 * of the token |
| 1068 */ |
| 1069 int get end => offset + length; |
| 1070 |
| 1071 /** |
| 1072 * Return the number of characters in the node's source range. |
| 1073 * |
| 1074 * @return the number of characters in the node's source range |
| 1075 */ |
| 1076 int get length => lexeme.length; |
| 1077 |
| 1078 /** |
| 1079 * Return the lexeme that represents this token. |
| 1080 * |
| 1081 * @return the lexeme (not `null`) |
| 1082 */ |
| 1083 String get lexeme => _value; |
| 1084 |
| 1085 /** |
| 1086 * Return the next token in the token stream. |
| 1087 * |
| 1088 * @return the next token in the token stream |
| 1089 */ |
| 1090 Token get next => _next; |
| 1091 |
| 1092 /** |
| 1093 * Return `true` if this token is a synthetic token. A synthetic token is a to
ken that was |
| 1094 * introduced by the parser in order to recover from an error in the code. Syn
thetic tokens always |
| 1095 * have a length of zero (`0`). |
| 1096 * |
| 1097 * @return `true` if this token is a synthetic token |
| 1098 */ |
| 1099 bool get isSynthetic => length == 0; |
| 1100 |
| 1101 /** |
| 1102 * Set the next token in the token stream to the given token. This has the sid
e-effect of setting |
| 1103 * this token to be the previous token for the given token. |
| 1104 * |
| 1105 * @param token the next token in the token stream |
| 1106 * @return the token that was passed in |
| 1107 */ |
| 1108 Token setNext(Token token) { |
| 1109 _next = token; |
| 1110 token.previous = this; |
| 1111 return token; |
| 1554 } | 1112 } |
| 1555 | 1113 |
| 1556 @override | 1114 @override |
| 1557 accept(XmlVisitor visitor) => visitor.visitHtmlUnit(this); | 1115 String toString() => lexeme; |
| 1558 | |
| 1559 /** | |
| 1560 * Return the element associated with this HTML unit. | |
| 1561 * | |
| 1562 * @return the element or `null` if the receiver is not resolved | |
| 1563 */ | |
| 1564 @override | |
| 1565 HtmlElement get element => super.element as HtmlElement; | |
| 1566 | |
| 1567 /** | |
| 1568 * Answer the tag nodes contained in the receiver. Callers should not manipula
te the returned list | |
| 1569 * to edit the AST structure. | |
| 1570 * | |
| 1571 * @return the children (not `null`, contains no `null`s) | |
| 1572 */ | |
| 1573 List<XmlTagNode> get tagNodes => _tagNodes; | |
| 1574 | |
| 1575 @override | |
| 1576 void set element(Element element) { | |
| 1577 if (element != null && element is! HtmlElement) { | |
| 1578 throw new IllegalArgumentException("HtmlElement expected, but ${element.ru
ntimeType} given"); | |
| 1579 } | |
| 1580 super.element = element; | |
| 1581 } | |
| 1582 | |
| 1583 @override | |
| 1584 void visitChildren(XmlVisitor visitor) { | |
| 1585 for (XmlTagNode node in _tagNodes) { | |
| 1586 node.accept(visitor); | |
| 1587 } | |
| 1588 } | |
| 1589 } | |
| 1590 | |
| 1591 /** | |
| 1592 * Instances of the class `StringScanner` implement a scanner that reads from a
string. The | |
| 1593 * scanning logic is in the superclass. | |
| 1594 */ | |
| 1595 class StringScanner extends AbstractScanner { | |
| 1596 /** | |
| 1597 * The string from which characters will be read. | |
| 1598 */ | |
| 1599 final String _string; | |
| 1600 | |
| 1601 /** | |
| 1602 * The number of characters in the string. | |
| 1603 */ | |
| 1604 int _stringLength = 0; | |
| 1605 | |
| 1606 /** | |
| 1607 * The index, relative to the string, of the last character that was read. | |
| 1608 */ | |
| 1609 int _charOffset = 0; | |
| 1610 | |
| 1611 /** | |
| 1612 * Initialize a newly created scanner to scan the characters in the given stri
ng. | |
| 1613 * | |
| 1614 * @param source the source being scanned | |
| 1615 * @param string the string from which characters will be read | |
| 1616 */ | |
| 1617 StringScanner(Source source, this._string) : super(source) { | |
| 1618 this._stringLength = _string.length; | |
| 1619 this._charOffset = -1; | |
| 1620 } | |
| 1621 | |
| 1622 @override | |
| 1623 int get offset => _charOffset; | |
| 1624 | |
| 1625 void set offset(int offset) { | |
| 1626 _charOffset = offset; | |
| 1627 } | |
| 1628 | |
| 1629 @override | |
| 1630 int advance() { | |
| 1631 if (++_charOffset < _stringLength) { | |
| 1632 return _string.codeUnitAt(_charOffset); | |
| 1633 } | |
| 1634 _charOffset = _stringLength; | |
| 1635 return -1; | |
| 1636 } | |
| 1637 | |
| 1638 @override | |
| 1639 String getString(int start, int endDelta) => _string.substring(start, _charOff
set + 1 + endDelta).toString(); | |
| 1640 | |
| 1641 @override | |
| 1642 int peek() { | |
| 1643 if (_charOffset + 1 < _stringLength) { | |
| 1644 return _string.codeUnitAt(_charOffset + 1); | |
| 1645 } | |
| 1646 return -1; | |
| 1647 } | |
| 1648 } | |
| 1649 | |
| 1650 /** | |
| 1651 * Instances of the class `HtmlParser` are used to parse tokens into a AST struc
ture comprised | |
| 1652 * of [XmlNode]s. | |
| 1653 */ | |
| 1654 class HtmlParser extends XmlParser { | |
| 1655 /** | |
| 1656 * The line information associated with the source being parsed. | |
| 1657 */ | |
| 1658 LineInfo _lineInfo; | |
| 1659 | |
| 1660 /** | |
| 1661 * The error listener to which errors will be reported. | |
| 1662 */ | |
| 1663 final AnalysisErrorListener _errorListener; | |
| 1664 | |
| 1665 static String _APPLICATION_DART_IN_DOUBLE_QUOTES = "\"application/dart\""; | |
| 1666 | |
| 1667 static String _APPLICATION_DART_IN_SINGLE_QUOTES = "'application/dart'"; | |
| 1668 | |
| 1669 static String _SCRIPT = "script"; | |
| 1670 | |
| 1671 static String _TYPE = "type"; | |
| 1672 | |
| 1673 /** | |
| 1674 * A set containing the names of tags that do not have a closing tag. | |
| 1675 */ | |
| 1676 static Set<String> SELF_CLOSING = new Set<String>.from(JavaArrays.asList(<Stri
ng> [ | |
| 1677 "area", | |
| 1678 "base", | |
| 1679 "basefont", | |
| 1680 "br", | |
| 1681 "col", | |
| 1682 "frame", | |
| 1683 "hr", | |
| 1684 "img", | |
| 1685 "input", | |
| 1686 "link", | |
| 1687 "meta", | |
| 1688 "param", | |
| 1689 "!"])); | |
| 1690 | |
| 1691 /** | |
| 1692 * Given the contents of an embedded expression that occurs at the given offse
t, parse it as a | |
| 1693 * Dart expression. The contents should not include the expression's delimiter
s. | |
| 1694 * | |
| 1695 * @param source the source that contains that given token | |
| 1696 * @param token the token to start parsing from | |
| 1697 * @return the Dart expression that was parsed | |
| 1698 */ | |
| 1699 static Expression parseEmbeddedExpression(Source source, sc.Token token, Analy
sisErrorListener errorListener) { | |
| 1700 Parser parser = new Parser(source, errorListener); | |
| 1701 return parser.parseExpression(token); | |
| 1702 } | |
| 1703 | |
| 1704 /** | |
| 1705 * Given the contents of an embedded expression that occurs at the given offse
t, scans it as a | |
| 1706 * Dart code. | |
| 1707 * | |
| 1708 * @param source the source of that contains the given contents | |
| 1709 * @param contents the contents to scan | |
| 1710 * @param contentOffset the offset of the contents in the larger file | |
| 1711 * @return the first Dart token | |
| 1712 */ | |
| 1713 static sc.Token scanDartSource(Source source, LineInfo lineInfo, String conten
ts, int contentOffset, AnalysisErrorListener errorListener) { | |
| 1714 LineInfo_Location location = lineInfo.getLocation(contentOffset); | |
| 1715 sc.Scanner scanner = new sc.Scanner(source, new sc.SubSequenceReader(content
s, contentOffset), errorListener); | |
| 1716 scanner.setSourceStart(location.lineNumber, location.columnNumber); | |
| 1717 return scanner.tokenize(); | |
| 1718 } | |
| 1719 | |
| 1720 /** | |
| 1721 * Construct a parser for the specified source. | |
| 1722 * | |
| 1723 * @param source the source being parsed | |
| 1724 * @param errorListener the error listener to which errors will be reported | |
| 1725 */ | |
| 1726 HtmlParser(Source source, this._errorListener) : super(source); | |
| 1727 | |
| 1728 /** | |
| 1729 * Parse the given tokens. | |
| 1730 * | |
| 1731 * @param token the first token in the stream of tokens to be parsed | |
| 1732 * @param lineInfo the line information created by the scanner | |
| 1733 * @return the parse result (not `null`) | |
| 1734 */ | |
| 1735 HtmlUnit parse(Token token, LineInfo lineInfo) { | |
| 1736 this._lineInfo = lineInfo; | |
| 1737 List<XmlTagNode> tagNodes = parseTopTagNodes(token); | |
| 1738 return new HtmlUnit(token, tagNodes, currentToken); | |
| 1739 } | |
| 1740 | |
| 1741 @override | |
| 1742 XmlAttributeNode createAttributeNode(Token name, Token equals, Token value) =>
new XmlAttributeNode(name, equals, value); | |
| 1743 | |
| 1744 @override | |
| 1745 XmlTagNode createTagNode(Token nodeStart, Token tag, List<XmlAttributeNode> at
tributes, Token attributeEnd, List<XmlTagNode> tagNodes, Token contentEnd, Token
closingTag, Token nodeEnd) { | |
| 1746 if (_isScriptNode(tag, attributes, tagNodes)) { | |
| 1747 HtmlScriptTagNode tagNode = new HtmlScriptTagNode(nodeStart, tag, attribut
es, attributeEnd, tagNodes, contentEnd, closingTag, nodeEnd); | |
| 1748 String contents = tagNode.content; | |
| 1749 int contentOffset = attributeEnd.end; | |
| 1750 LineInfo_Location location = _lineInfo.getLocation(contentOffset); | |
| 1751 sc.Scanner scanner = new sc.Scanner(source, new sc.SubSequenceReader(conte
nts, contentOffset), _errorListener); | |
| 1752 scanner.setSourceStart(location.lineNumber, location.columnNumber); | |
| 1753 sc.Token firstToken = scanner.tokenize(); | |
| 1754 Parser parser = new Parser(source, _errorListener); | |
| 1755 CompilationUnit unit = parser.parseCompilationUnit(firstToken); | |
| 1756 unit.lineInfo = _lineInfo; | |
| 1757 tagNode.script = unit; | |
| 1758 return tagNode; | |
| 1759 } | |
| 1760 return new XmlTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes, co
ntentEnd, closingTag, nodeEnd); | |
| 1761 } | |
| 1762 | |
| 1763 @override | |
| 1764 bool isSelfClosing(Token tag) => SELF_CLOSING.contains(tag.lexeme); | |
| 1765 | |
| 1766 /** | |
| 1767 * Determine if the specified node is a Dart script. | |
| 1768 * | |
| 1769 * @param node the node to be tested (not `null`) | |
| 1770 * @return `true` if the node is a Dart script | |
| 1771 */ | |
| 1772 bool _isScriptNode(Token tag, List<XmlAttributeNode> attributes, List<XmlTagNo
de> tagNodes) { | |
| 1773 if (tagNodes.length != 0 || tag.lexeme != _SCRIPT) { | |
| 1774 return false; | |
| 1775 } | |
| 1776 for (XmlAttributeNode attribute in attributes) { | |
| 1777 if (attribute.name == _TYPE) { | |
| 1778 Token valueToken = attribute.valueToken; | |
| 1779 if (valueToken != null) { | |
| 1780 String value = valueToken.lexeme; | |
| 1781 if (value == _APPLICATION_DART_IN_DOUBLE_QUOTES || value == _APPLICATI
ON_DART_IN_SINGLE_QUOTES) { | |
| 1782 return true; | |
| 1783 } | |
| 1784 } | |
| 1785 } | |
| 1786 } | |
| 1787 return false; | |
| 1788 } | |
| 1789 } | |
| 1790 | |
| 1791 /** | |
| 1792 * Instances of the class `RecursiveXmlVisitor` implement an XML visitor that wi
ll recursively | |
| 1793 * visit all of the nodes in an XML structure. For example, using an instance of
this class to visit | |
| 1794 * a [XmlTagNode] will also cause all of the contained [XmlAttributeNode]s and | |
| 1795 * [XmlTagNode]s to be visited. | |
| 1796 * | |
| 1797 * Subclasses that override a visit method must either invoke the overridden vis
it method or must | |
| 1798 * explicitly ask the visited node to visit its children. Failure to do so will
cause the children | |
| 1799 * of the visited node to not be visited. | |
| 1800 */ | |
| 1801 class RecursiveXmlVisitor<R> implements XmlVisitor<R> { | |
| 1802 @override | |
| 1803 R visitHtmlScriptTagNode(HtmlScriptTagNode node) { | |
| 1804 node.visitChildren(this); | |
| 1805 return null; | |
| 1806 } | |
| 1807 | |
| 1808 @override | |
| 1809 R visitHtmlUnit(HtmlUnit node) { | |
| 1810 node.visitChildren(this); | |
| 1811 return null; | |
| 1812 } | |
| 1813 | |
| 1814 @override | |
| 1815 R visitXmlAttributeNode(XmlAttributeNode node) { | |
| 1816 node.visitChildren(this); | |
| 1817 return null; | |
| 1818 } | |
| 1819 | |
| 1820 @override | |
| 1821 R visitXmlTagNode(XmlTagNode node) { | |
| 1822 node.visitChildren(this); | |
| 1823 return null; | |
| 1824 } | |
| 1825 } | |
| 1826 | |
| 1827 /** | |
| 1828 * Instances of the class `HtmlScriptTagNode` represent a script tag within an H
TML file that | |
| 1829 * references a Dart script. | |
| 1830 */ | |
| 1831 class HtmlScriptTagNode extends XmlTagNode { | |
| 1832 /** | |
| 1833 * The AST structure representing the Dart code within this tag. | |
| 1834 */ | |
| 1835 CompilationUnit _script; | |
| 1836 | |
| 1837 /** | |
| 1838 * The element representing this script. | |
| 1839 */ | |
| 1840 HtmlScriptElement scriptElement; | |
| 1841 | |
| 1842 /** | |
| 1843 * Initialize a newly created node to represent a script tag within an HTML fi
le that references a | |
| 1844 * Dart script. | |
| 1845 * | |
| 1846 * @param nodeStart the token marking the beginning of the tag | |
| 1847 * @param tag the name of the tag | |
| 1848 * @param attributes the attributes in the tag | |
| 1849 * @param attributeEnd the token terminating the region where attributes can b
e | |
| 1850 * @param tagNodes the children of the tag | |
| 1851 * @param contentEnd the token that starts the closing tag | |
| 1852 * @param closingTag the name of the tag that occurs in the closing tag | |
| 1853 * @param nodeEnd the last token in the tag | |
| 1854 */ | |
| 1855 HtmlScriptTagNode(Token nodeStart, Token tag, List<XmlAttributeNode> attribute
s, Token attributeEnd, List<XmlTagNode> tagNodes, Token contentEnd, Token closin
gTag, Token nodeEnd) : super(nodeStart, tag, attributes, attributeEnd, tagNodes,
contentEnd, closingTag, nodeEnd); | |
| 1856 | |
| 1857 @override | |
| 1858 accept(XmlVisitor visitor) => visitor.visitHtmlScriptTagNode(this); | |
| 1859 | |
| 1860 /** | |
| 1861 * Return the AST structure representing the Dart code within this tag, or `nu
ll` if this | |
| 1862 * tag references an external script. | |
| 1863 * | |
| 1864 * @return the AST structure representing the Dart code within this tag | |
| 1865 */ | |
| 1866 CompilationUnit get script => _script; | |
| 1867 | |
| 1868 /** | |
| 1869 * Set the AST structure representing the Dart code within this tag to the giv
en compilation unit. | |
| 1870 * | |
| 1871 * @param unit the AST structure representing the Dart code within this tag | |
| 1872 */ | |
| 1873 void set script(CompilationUnit unit) { | |
| 1874 _script = unit; | |
| 1875 } | |
| 1876 } | 1116 } |
| 1877 | 1117 |
| 1878 /** | 1118 /** |
| 1879 * The enumeration `TokenType` defines the types of tokens that can be returned
by the | 1119 * The enumeration `TokenType` defines the types of tokens that can be returned
by the |
| 1880 * scanner. | 1120 * scanner. |
| 1881 */ | 1121 */ |
| 1882 class TokenType extends Enum<TokenType> { | 1122 class TokenType extends Enum<TokenType> { |
| 1883 /** | 1123 /** |
| 1884 * The type of the token that marks the end of the input. | 1124 * The type of the token that marks the end of the input. |
| 1885 */ | 1125 */ |
| (...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2033 * Answer the attribute value token. A properly formed value will start and en
d with matching | 1273 * Answer the attribute value token. A properly formed value will start and en
d with matching |
| 2034 * quote characters, but the value returned may not be properly formed. | 1274 * quote characters, but the value returned may not be properly formed. |
| 2035 * | 1275 * |
| 2036 * @return the value token or `null` if this represents a badly formed attribu
te | 1276 * @return the value token or `null` if this represents a badly formed attribu
te |
| 2037 */ | 1277 */ |
| 2038 Token get valueToken => _value; | 1278 Token get valueToken => _value; |
| 2039 | 1279 |
| 2040 @override | 1280 @override |
| 2041 void visitChildren(XmlVisitor visitor) { | 1281 void visitChildren(XmlVisitor visitor) { |
| 2042 } | 1282 } |
| 1283 } |
| 1284 |
| 1285 /** |
| 1286 * Instances of the class `XmlExpression` represent an abstract expression embed
ded into |
| 1287 * [XmlNode]. |
| 1288 */ |
| 1289 abstract class XmlExpression { |
| 1290 /** |
| 1291 * An empty array of expressions. |
| 1292 */ |
| 1293 static List<XmlExpression> EMPTY_ARRAY = new List<XmlExpression>(0); |
| 1294 |
| 1295 /** |
| 1296 * Check if the given offset belongs to the expression's source range. |
| 1297 */ |
| 1298 bool contains(int offset) => this.offset <= offset && offset < end; |
| 1299 |
| 1300 /** |
| 1301 * Return the offset of the character immediately following the last character
of this |
| 1302 * expression's source range. This is equivalent to `getOffset() + getLength()
`. |
| 1303 * |
| 1304 * @return the offset of the character just past the expression's source range |
| 1305 */ |
| 1306 int get end; |
| 1307 |
| 1308 /** |
| 1309 * Return the number of characters in the expression's source range. |
| 1310 */ |
| 1311 int get length; |
| 1312 |
| 1313 /** |
| 1314 * Return the offset of the first character in the expression's source range. |
| 1315 */ |
| 1316 int get offset; |
| 1317 |
| 1318 /** |
| 1319 * Return the [Reference] at the given offset. |
| 1320 * |
| 1321 * @param offset the offset from the beginning of the file |
| 1322 * @return the [Reference] at the given offset, maybe `null` |
| 1323 */ |
| 1324 XmlExpression_Reference getReference(int offset); |
| 1325 } |
| 1326 |
| 1327 /** |
| 1328 * The reference to the [Element]. |
| 1329 */ |
| 1330 class XmlExpression_Reference { |
| 1331 Element element; |
| 1332 |
| 1333 int offset = 0; |
| 1334 |
| 1335 int length = 0; |
| 1336 |
| 1337 XmlExpression_Reference(Element element, int offset, int length) { |
| 1338 this.element = element; |
| 1339 this.offset = offset; |
| 1340 this.length = length; |
| 1341 } |
| 1342 } |
| 1343 |
| 1344 /** |
| 1345 * The abstract class `XmlNode` defines behavior common to all XML/HTML nodes. |
| 1346 */ |
| 1347 abstract class XmlNode { |
| 1348 /** |
| 1349 * The parent of the node, or `null` if the node is the root of an AST structu
re. |
| 1350 */ |
| 1351 XmlNode _parent; |
| 1352 |
| 1353 /** |
| 1354 * The element associated with this node or `null` if the receiver is not reso
lved. |
| 1355 */ |
| 1356 Element _element; |
| 1357 |
| 1358 /** |
| 1359 * Use the given visitor to visit this node. |
| 1360 * |
| 1361 * @param visitor the visitor that will visit this node |
| 1362 * @return the value returned by the visitor as a result of visiting this node |
| 1363 */ |
| 1364 accept(XmlVisitor visitor); |
| 1365 |
| 1366 /** |
| 1367 * Return the first token included in this node's source range. |
| 1368 * |
| 1369 * @return the first token or `null` if none |
| 1370 */ |
| 1371 Token get beginToken; |
| 1372 |
| 1373 /** |
| 1374 * Return the element associated with this node. |
| 1375 * |
| 1376 * @return the element or `null` if the receiver is not resolved |
| 1377 */ |
| 1378 Element get element => _element; |
| 1379 |
| 1380 /** |
| 1381 * Return the offset of the character immediately following the last character
of this node's |
| 1382 * source range. This is equivalent to `node.getOffset() + node.getLength()`.
For an html |
| 1383 * unit this will be equal to the length of the unit's source. |
| 1384 * |
| 1385 * @return the offset of the character just past the node's source range |
| 1386 */ |
| 1387 int get end => offset + length; |
| 1388 |
| 1389 /** |
| 1390 * Return the last token included in this node's source range. |
| 1391 * |
| 1392 * @return the last token or `null` if none |
| 1393 */ |
| 1394 Token get endToken; |
| 1395 |
| 1396 /** |
| 1397 * Return the number of characters in the node's source range. |
| 1398 * |
| 1399 * @return the number of characters in the node's source range |
| 1400 */ |
| 1401 int get length { |
| 1402 Token beginToken = this.beginToken; |
| 1403 Token endToken = this.endToken; |
| 1404 if (beginToken == null || endToken == null) { |
| 1405 return -1; |
| 1406 } |
| 1407 return endToken.offset + endToken.length - beginToken.offset; |
| 1408 } |
| 1409 |
| 1410 /** |
| 1411 * Return the offset from the beginning of the file to the first character in
the node's source |
| 1412 * range. |
| 1413 * |
| 1414 * @return the offset from the beginning of the file to the first character in
the node's source |
| 1415 * range |
| 1416 */ |
| 1417 int get offset { |
| 1418 Token beginToken = this.beginToken; |
| 1419 if (beginToken == null) { |
| 1420 return -1; |
| 1421 } |
| 1422 return this.beginToken.offset; |
| 1423 } |
| 1424 |
| 1425 /** |
| 1426 * Return this node's parent node, or `null` if this node is the root of an AS
T structure. |
| 1427 * |
| 1428 * Note that the relationship between an AST node and its parent node may chan
ge over the lifetime |
| 1429 * of a node. |
| 1430 * |
| 1431 * @return the parent of this node, or `null` if none |
| 1432 */ |
| 1433 XmlNode get parent => _parent; |
| 1434 |
| 1435 /** |
| 1436 * Set the element associated with this node. |
| 1437 * |
| 1438 * @param element the element |
| 1439 */ |
| 1440 void set element(Element element) { |
| 1441 this._element = element; |
| 1442 } |
| 1443 |
| 1444 @override |
| 1445 String toString() { |
| 1446 PrintStringWriter writer = new PrintStringWriter(); |
| 1447 accept(new ToSourceVisitor(writer)); |
| 1448 return writer.toString(); |
| 1449 } |
| 1450 |
| 1451 /** |
| 1452 * Use the given visitor to visit all of the children of this node. The childr
en will be visited |
| 1453 * in source order. |
| 1454 * |
| 1455 * @param visitor the visitor that will be used to visit the children of this
node |
| 1456 */ |
| 1457 void visitChildren(XmlVisitor visitor); |
| 1458 |
| 1459 /** |
| 1460 * Make this node the parent of the given child node. |
| 1461 * |
| 1462 * @param child the node that will become a child of this node |
| 1463 * @return the node that was made a child of this node |
| 1464 */ |
| 1465 XmlNode becomeParentOf(XmlNode child) { |
| 1466 if (child != null) { |
| 1467 XmlNode node = child; |
| 1468 node.parent = this; |
| 1469 } |
| 1470 return child; |
| 1471 } |
| 1472 |
| 1473 /** |
| 1474 * Make this node the parent of the given child nodes. |
| 1475 * |
| 1476 * @param children the nodes that will become the children of this node |
| 1477 * @param ifEmpty the (empty) nodes to return if "children" is empty |
| 1478 * @return the nodes that were made children of this node |
| 1479 */ |
| 1480 List becomeParentOfAll(List children, {List ifEmpty}) { |
| 1481 if (children == null || children.isEmpty) { |
| 1482 if (ifEmpty != null) { |
| 1483 return ifEmpty; |
| 1484 } |
| 1485 } |
| 1486 if (children != null) { |
| 1487 for (JavaIterator iter = new JavaIterator(children); iter.hasNext;) { |
| 1488 XmlNode node = iter.next(); |
| 1489 node.parent = this; |
| 1490 } |
| 1491 // This will create ArrayList for exactly given number of elements. |
| 1492 return new List.from(children); |
| 1493 } |
| 1494 return children; |
| 1495 } |
| 1496 |
| 1497 /** |
| 1498 * This method exists for debugging purposes only. |
| 1499 */ |
| 1500 void _appendIdentifier(JavaStringBuilder builder, XmlNode node) { |
| 1501 if (node is XmlTagNode) { |
| 1502 builder.append(node.tag); |
| 1503 } else if (node is XmlAttributeNode) { |
| 1504 builder.append(node.name); |
| 1505 } else { |
| 1506 builder.append("htmlUnit"); |
| 1507 } |
| 1508 } |
| 1509 |
| 1510 /** |
| 1511 * This method exists for debugging purposes only. |
| 1512 */ |
| 1513 String _buildRecursiveStructureMessage(XmlNode newParent) { |
| 1514 JavaStringBuilder builder = new JavaStringBuilder(); |
| 1515 builder.append("Attempt to create recursive structure: "); |
| 1516 XmlNode current = newParent; |
| 1517 while (current != null) { |
| 1518 if (!identical(current, newParent)) { |
| 1519 builder.append(" -> "); |
| 1520 } |
| 1521 if (identical(current, this)) { |
| 1522 builder.appendChar(0x2A); |
| 1523 _appendIdentifier(builder, current); |
| 1524 builder.appendChar(0x2A); |
| 1525 } else { |
| 1526 _appendIdentifier(builder, current); |
| 1527 } |
| 1528 current = current.parent; |
| 1529 } |
| 1530 return builder.toString(); |
| 1531 } |
| 1532 |
| 1533 /** |
| 1534 * Set the parent of this node to the given node. |
| 1535 * |
| 1536 * @param newParent the node that is to be made the parent of this node |
| 1537 */ |
| 1538 void set parent(XmlNode newParent) { |
| 1539 XmlNode current = newParent; |
| 1540 while (current != null) { |
| 1541 if (identical(current, this)) { |
| 1542 AnalysisEngine.instance.logger.logError2("Circular structure while setti
ng an XML node's parent", new IllegalArgumentException(_buildRecursiveStructureM
essage(newParent))); |
| 1543 return; |
| 1544 } |
| 1545 current = current.parent; |
| 1546 } |
| 1547 _parent = newParent; |
| 1548 } |
| 1549 } |
| 1550 |
| 1551 /** |
| 1552 * Instances of the class `XmlParser` are used to parse tokens into a AST struct
ure comprised |
| 1553 * of [XmlNode]s. |
| 1554 */ |
| 1555 class XmlParser { |
| 1556 /** |
| 1557 * The source being parsed. |
| 1558 */ |
| 1559 final Source source; |
| 1560 |
| 1561 /** |
| 1562 * The next token to be parsed. |
| 1563 */ |
| 1564 Token _currentToken; |
| 1565 |
| 1566 /** |
| 1567 * Construct a parser for the specified source. |
| 1568 * |
| 1569 * @param source the source being parsed |
| 1570 */ |
| 1571 XmlParser(this.source); |
| 1572 |
| 1573 /** |
| 1574 * Create a node representing an attribute. |
| 1575 * |
| 1576 * @param name the name of the attribute |
| 1577 * @param equals the equals sign, or `null` if there is no value |
| 1578 * @param value the value of the attribute |
| 1579 * @return the node that was created |
| 1580 */ |
| 1581 XmlAttributeNode createAttributeNode(Token name, Token equals, Token value) =>
new XmlAttributeNode(name, equals, value); |
| 1582 |
| 1583 /** |
| 1584 * Create a node representing a tag. |
| 1585 * |
| 1586 * @param nodeStart the token marking the beginning of the tag |
| 1587 * @param tag the name of the tag |
| 1588 * @param attributes the attributes in the tag |
| 1589 * @param attributeEnd the token terminating the region where attributes can b
e |
| 1590 * @param tagNodes the children of the tag |
| 1591 * @param contentEnd the token that starts the closing tag |
| 1592 * @param closingTag the name of the tag that occurs in the closing tag |
| 1593 * @param nodeEnd the last token in the tag |
| 1594 * @return the node that was created |
| 1595 */ |
| 1596 XmlTagNode createTagNode(Token nodeStart, Token tag, List<XmlAttributeNode> at
tributes, Token attributeEnd, List<XmlTagNode> tagNodes, Token contentEnd, Token
closingTag, Token nodeEnd) => new XmlTagNode(nodeStart, tag, attributes, attrib
uteEnd, tagNodes, contentEnd, closingTag, nodeEnd); |
| 1597 |
| 1598 /** |
| 1599 * Answer `true` if the specified tag is self closing and thus should never ha
ve content or |
| 1600 * child tag nodes. |
| 1601 * |
| 1602 * @param tag the tag (not `null`) |
| 1603 * @return `true` if self closing |
| 1604 */ |
| 1605 bool isSelfClosing(Token tag) => false; |
| 1606 |
| 1607 /** |
| 1608 * Parse the entire token stream and in the process, advance the current token
to the end of the |
| 1609 * token stream. |
| 1610 * |
| 1611 * @return the list of tag nodes found (not `null`, contains no `null`) |
| 1612 */ |
| 1613 List<XmlTagNode> parseTopTagNodes(Token firstToken) { |
| 1614 _currentToken = firstToken; |
| 1615 List<XmlTagNode> tagNodes = new List<XmlTagNode>(); |
| 1616 TokenType type = _currentToken.type; |
| 1617 while (type != TokenType.EOF) { |
| 1618 if (type == TokenType.LT) { |
| 1619 tagNodes.add(_parseTagNode()); |
| 1620 } else if (type == TokenType.DECLARATION || type == TokenType.DIRECTIVE ||
type == TokenType.COMMENT) { |
| 1621 // ignored tokens |
| 1622 _currentToken = _currentToken.next; |
| 1623 } else { |
| 1624 _reportUnexpectedToken(); |
| 1625 _currentToken = _currentToken.next; |
| 1626 } |
| 1627 type = _currentToken.type; |
| 1628 } |
| 1629 return tagNodes; |
| 1630 } |
| 1631 |
| 1632 /** |
| 1633 * Answer the current token. |
| 1634 * |
| 1635 * @return the current token |
| 1636 */ |
| 1637 Token get currentToken => _currentToken; |
| 1638 |
| 1639 /** |
| 1640 * Insert a synthetic token of the specified type before the current token |
| 1641 * |
| 1642 * @param type the type of token to be inserted (not `null`) |
| 1643 * @return the synthetic token that was inserted (not `null`) |
| 1644 */ |
| 1645 Token _insertSyntheticToken(TokenType type) { |
| 1646 Token token = new Token.con2(type, _currentToken.offset, ""); |
| 1647 _currentToken.previous.setNext(token); |
| 1648 token.setNext(_currentToken); |
| 1649 return token; |
| 1650 } |
| 1651 |
| 1652 /** |
| 1653 * Parse the token stream for an attribute. This method advances the current t
oken over the |
| 1654 * attribute, but should not be called if the [currentToken] is not [TokenType
#TAG]. |
| 1655 * |
| 1656 * @return the attribute (not `null`) |
| 1657 */ |
| 1658 XmlAttributeNode _parseAttribute() { |
| 1659 // Assume the current token is a tag |
| 1660 Token name = _currentToken; |
| 1661 _currentToken = _currentToken.next; |
| 1662 // Equals sign |
| 1663 Token equals; |
| 1664 if (_currentToken.type == TokenType.EQ) { |
| 1665 equals = _currentToken; |
| 1666 _currentToken = _currentToken.next; |
| 1667 } else { |
| 1668 _reportUnexpectedToken(); |
| 1669 equals = _insertSyntheticToken(TokenType.EQ); |
| 1670 } |
| 1671 // String value |
| 1672 Token value; |
| 1673 if (_currentToken.type == TokenType.STRING) { |
| 1674 value = _currentToken; |
| 1675 _currentToken = _currentToken.next; |
| 1676 } else { |
| 1677 _reportUnexpectedToken(); |
| 1678 value = _insertSyntheticToken(TokenType.STRING); |
| 1679 } |
| 1680 return createAttributeNode(name, equals, value); |
| 1681 } |
| 1682 |
| 1683 /** |
| 1684 * Parse the stream for a sequence of attributes. This method advances the cur
rent token to the |
| 1685 * next [TokenType#GT], [TokenType#SLASH_GT], or [TokenType#EOF]. |
| 1686 * |
| 1687 * @return a collection of zero or more attributes (not `null`, contains no `n
ull`s) |
| 1688 */ |
| 1689 List<XmlAttributeNode> _parseAttributes() { |
| 1690 TokenType type = _currentToken.type; |
| 1691 if (type == TokenType.GT || type == TokenType.SLASH_GT || type == TokenType.
EOF) { |
| 1692 return XmlTagNode.NO_ATTRIBUTES; |
| 1693 } |
| 1694 List<XmlAttributeNode> attributes = new List<XmlAttributeNode>(); |
| 1695 while (type != TokenType.GT && type != TokenType.SLASH_GT && type != TokenTy
pe.EOF) { |
| 1696 if (type == TokenType.TAG) { |
| 1697 attributes.add(_parseAttribute()); |
| 1698 } else { |
| 1699 _reportUnexpectedToken(); |
| 1700 _currentToken = _currentToken.next; |
| 1701 } |
| 1702 type = _currentToken.type; |
| 1703 } |
| 1704 return attributes; |
| 1705 } |
| 1706 |
| 1707 /** |
| 1708 * Parse the stream for a sequence of tag nodes existing within a parent tag n
ode. This method |
| 1709 * advances the current token to the next [TokenType#LT_SLASH] or [TokenType#E
OF]. |
| 1710 * |
| 1711 * @return a list of nodes (not `null`, contains no `null`s) |
| 1712 */ |
| 1713 List<XmlTagNode> _parseChildTagNodes() { |
| 1714 TokenType type = _currentToken.type; |
| 1715 if (type == TokenType.LT_SLASH || type == TokenType.EOF) { |
| 1716 return XmlTagNode.NO_TAG_NODES; |
| 1717 } |
| 1718 List<XmlTagNode> nodes = new List<XmlTagNode>(); |
| 1719 while (type != TokenType.LT_SLASH && type != TokenType.EOF) { |
| 1720 if (type == TokenType.LT) { |
| 1721 nodes.add(_parseTagNode()); |
| 1722 } else if (type == TokenType.COMMENT) { |
| 1723 // ignored token |
| 1724 _currentToken = _currentToken.next; |
| 1725 } else { |
| 1726 _reportUnexpectedToken(); |
| 1727 _currentToken = _currentToken.next; |
| 1728 } |
| 1729 type = _currentToken.type; |
| 1730 } |
| 1731 return nodes; |
| 1732 } |
| 1733 |
| 1734 /** |
| 1735 * Parse the token stream for the next tag node. This method advances current
token over the |
| 1736 * parsed tag node, but should only be called if the current token is [TokenTy
pe#LT] |
| 1737 * |
| 1738 * @return the tag node or `null` if none found |
| 1739 */ |
| 1740 XmlTagNode _parseTagNode() { |
| 1741 // Assume that the current node is a tag node start TokenType#LT |
| 1742 Token nodeStart = _currentToken; |
| 1743 _currentToken = _currentToken.next; |
| 1744 // Get the tag or create a synthetic tag and report an error |
| 1745 Token tag; |
| 1746 if (_currentToken.type == TokenType.TAG) { |
| 1747 tag = _currentToken; |
| 1748 _currentToken = _currentToken.next; |
| 1749 } else { |
| 1750 _reportUnexpectedToken(); |
| 1751 tag = _insertSyntheticToken(TokenType.TAG); |
| 1752 } |
| 1753 // Parse the attributes |
| 1754 List<XmlAttributeNode> attributes = _parseAttributes(); |
| 1755 // Token ending attribute list |
| 1756 Token attributeEnd; |
| 1757 if (_currentToken.type == TokenType.GT || _currentToken.type == TokenType.SL
ASH_GT) { |
| 1758 attributeEnd = _currentToken; |
| 1759 _currentToken = _currentToken.next; |
| 1760 } else { |
| 1761 _reportUnexpectedToken(); |
| 1762 attributeEnd = _insertSyntheticToken(TokenType.SLASH_GT); |
| 1763 } |
| 1764 // If the node has no children, then return the node |
| 1765 if (attributeEnd.type == TokenType.SLASH_GT || isSelfClosing(tag)) { |
| 1766 return createTagNode(nodeStart, tag, attributes, attributeEnd, XmlTagNode.
NO_TAG_NODES, _currentToken, null, attributeEnd); |
| 1767 } |
| 1768 // Parse the child tag nodes |
| 1769 List<XmlTagNode> tagNodes = _parseChildTagNodes(); |
| 1770 // Token ending child tag nodes |
| 1771 Token contentEnd; |
| 1772 if (_currentToken.type == TokenType.LT_SLASH) { |
| 1773 contentEnd = _currentToken; |
| 1774 _currentToken = _currentToken.next; |
| 1775 } else { |
| 1776 // TODO (danrubel): handle self closing HTML elements by inserting synthet
ic tokens |
| 1777 // but not reporting an error |
| 1778 _reportUnexpectedToken(); |
| 1779 contentEnd = _insertSyntheticToken(TokenType.LT_SLASH); |
| 1780 } |
| 1781 // Closing tag |
| 1782 Token closingTag; |
| 1783 if (_currentToken.type == TokenType.TAG) { |
| 1784 closingTag = _currentToken; |
| 1785 _currentToken = _currentToken.next; |
| 1786 } else { |
| 1787 _reportUnexpectedToken(); |
| 1788 closingTag = _insertSyntheticToken(TokenType.TAG); |
| 1789 } |
| 1790 // Token ending node |
| 1791 Token nodeEnd; |
| 1792 if (_currentToken.type == TokenType.GT) { |
| 1793 nodeEnd = _currentToken; |
| 1794 _currentToken = _currentToken.next; |
| 1795 } else { |
| 1796 _reportUnexpectedToken(); |
| 1797 nodeEnd = _insertSyntheticToken(TokenType.GT); |
| 1798 } |
| 1799 return createTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes, con
tentEnd, closingTag, nodeEnd); |
| 1800 } |
| 1801 |
| 1802 /** |
| 1803 * Report the current token as unexpected |
| 1804 */ |
| 1805 void _reportUnexpectedToken() { |
| 1806 } |
| 1807 } |
| 1808 |
| 1809 /** |
| 1810 * Instances of `XmlTagNode` represent XML or HTML elements such as `` and |
| 1811 * `<body foo="bar"> ... </body>`. |
| 1812 */ |
| 1813 class XmlTagNode extends XmlNode { |
| 1814 /** |
| 1815 * Constant representing empty list of attributes. |
| 1816 */ |
| 1817 static List<XmlAttributeNode> NO_ATTRIBUTES = new UnmodifiableListView(new Lis
t<XmlAttributeNode>()); |
| 1818 |
| 1819 /** |
| 1820 * Constant representing empty list of tag nodes. |
| 1821 */ |
| 1822 static List<XmlTagNode> NO_TAG_NODES = new UnmodifiableListView(new List<XmlTa
gNode>()); |
| 1823 |
| 1824 /** |
| 1825 * The starting [TokenType#LT] token (not `null`). |
| 1826 */ |
| 1827 final Token nodeStart; |
| 1828 |
| 1829 /** |
| 1830 * The [TokenType#TAG] token after the starting '<' (not `null`). |
| 1831 */ |
| 1832 final Token _tag; |
| 1833 |
| 1834 /** |
| 1835 * The attributes contained by the receiver (not `null`, contains no `null`s). |
| 1836 */ |
| 1837 List<XmlAttributeNode> _attributes; |
| 1838 |
| 1839 /** |
| 1840 * The [TokenType#GT] or [TokenType#SLASH_GT] token after the attributes (not |
| 1841 * `null`). The token may be the same token as [nodeEnd] if there are no child |
| 1842 * [tagNodes]. |
| 1843 */ |
| 1844 final Token attributeEnd; |
| 1845 |
| 1846 /** |
| 1847 * The tag nodes contained in the receiver (not `null`, contains no `null`s). |
| 1848 */ |
| 1849 List<XmlTagNode> _tagNodes; |
| 1850 |
| 1851 /** |
| 1852 * The token (not `null`) after the content, which may be |
| 1853 * * (1) [TokenType#LT_SLASH] for nodes with open and close tags, or |
| 1854 * * (2) the [TokenType#LT] nodeStart of the next sibling node if this node is
self |
| 1855 * closing or the attributeEnd is [TokenType#SLASH_GT], or |
| 1856 * * (3) [TokenType#EOF] if the node does not have a closing tag and is the la
st node in |
| 1857 * the stream [TokenType#LT_SLASH] token after the content, or `null` if there
is no |
| 1858 * content and the attributes ended with [TokenType#SLASH_GT]. |
| 1859 */ |
| 1860 final Token contentEnd; |
| 1861 |
| 1862 /** |
| 1863 * The closing [TokenType#TAG] after the child elements or `null` if there is
no |
| 1864 * content and the attributes ended with [TokenType#SLASH_GT] |
| 1865 */ |
| 1866 final Token closingTag; |
| 1867 |
| 1868 /** |
| 1869 * The ending [TokenType#GT] or [TokenType#SLASH_GT] token (not `null`). |
| 1870 */ |
| 1871 final Token nodeEnd; |
| 1872 |
| 1873 /** |
| 1874 * The expressions that are embedded in the tag's content. |
| 1875 */ |
| 1876 List<XmlExpression> expressions = XmlExpression.EMPTY_ARRAY; |
| 1877 |
| 1878 /** |
| 1879 * Construct a new instance representing an XML or HTML element |
| 1880 * |
| 1881 * @param nodeStart the starting [TokenType#LT] token (not `null`) |
| 1882 * @param tag the [TokenType#TAG] token after the starting '<' (not `null`)
. |
| 1883 * @param attributes the attributes associated with this element or [NO_ATTRIB
UTES] (not |
| 1884 * `null`, contains no `null`s) |
| 1885 * @param attributeEnd The [TokenType#GT] or [TokenType#SLASH_GT] token after
the |
| 1886 * attributes (not `null`). The token may be the same token as [nodeE
nd] if |
| 1887 * there are no child [tagNodes]. |
| 1888 * @param tagNodes child tag nodes of the receiver or [NO_TAG_NODES] (not `nul
l`, |
| 1889 * contains no `null`s) |
| 1890 * @param contentEnd the token (not `null`) after the content, which may be |
| 1891 * * (1) [TokenType#LT_SLASH] for nodes with open and close tags, or |
| 1892 * * (2) the [TokenType#LT] nodeStart of the next sibling node if thi
s node is |
| 1893 * self closing or the attributeEnd is [TokenType#SLASH_GT], or |
| 1894 * * (3) [TokenType#EOF] if the node does not have a closing tag and
is the last |
| 1895 * node in the stream [TokenType#LT_SLASH] token after the content, o
r `null` |
| 1896 * if there is no content and the attributes ended with [TokenType#SL
ASH_GT]. |
| 1897 * @param closingTag the closing [TokenType#TAG] after the child elements or `
null` if |
| 1898 * there is no content and the attributes ended with [TokenType#SLASH
_GT] |
| 1899 * @param nodeEnd the ending [TokenType#GT] or [TokenType#SLASH_GT] token (not |
| 1900 * `null`) |
| 1901 */ |
| 1902 XmlTagNode(this.nodeStart, this._tag, List<XmlAttributeNode> attributes, this.
attributeEnd, List<XmlTagNode> tagNodes, this.contentEnd, this.closingTag, this.
nodeEnd) { |
| 1903 this._attributes = becomeParentOfAll(attributes, ifEmpty: NO_ATTRIBUTES); |
| 1904 this._tagNodes = becomeParentOfAll(tagNodes, ifEmpty: NO_TAG_NODES); |
| 1905 } |
| 1906 |
| 1907 @override |
| 1908 accept(XmlVisitor visitor) => visitor.visitXmlTagNode(this); |
| 1909 |
| 1910 /** |
| 1911 * Answer the attribute with the specified name. |
| 1912 * |
| 1913 * @param name the attribute name |
| 1914 * @return the attribute or `null` if no matching attribute is found |
| 1915 */ |
| 1916 XmlAttributeNode getAttribute(String name) { |
| 1917 for (XmlAttributeNode attribute in _attributes) { |
| 1918 if (attribute.name == name) { |
| 1919 return attribute; |
| 1920 } |
| 1921 } |
| 1922 return null; |
| 1923 } |
| 1924 |
| 1925 /** |
| 1926 * Answer the receiver's attributes. Callers should not manipulate the returne
d list to edit the |
| 1927 * AST structure. |
| 1928 * |
| 1929 * @return the attributes (not `null`, contains no `null`s) |
| 1930 */ |
| 1931 List<XmlAttributeNode> get attributes => _attributes; |
| 1932 |
| 1933 /** |
| 1934 * Find the attribute with the given name (see [getAttribute] and answer the l
exeme |
| 1935 * for the attribute's value token without the leading and trailing quotes (se
e |
| 1936 * [XmlAttributeNode#getText]). |
| 1937 * |
| 1938 * @param name the attribute name |
| 1939 * @return the attribute text or `null` if no matching attribute is found |
| 1940 */ |
| 1941 String getAttributeText(String name) { |
| 1942 XmlAttributeNode attribute = getAttribute(name); |
| 1943 return attribute != null ? attribute.text : null; |
| 1944 } |
| 1945 |
| 1946 @override |
| 1947 Token get beginToken => nodeStart; |
| 1948 |
| 1949 /** |
| 1950 * Answer a string representing the content contained in the receiver. This in
cludes the textual |
| 1951 * representation of any child tag nodes ([getTagNodes]). Whitespace between '
<', |
| 1952 * '</', and '>', '/>' is discarded, but all other whitespace is preserved. |
| 1953 * |
| 1954 * @return the content (not `null`) |
| 1955 */ |
| 1956 String get content { |
| 1957 Token token = attributeEnd.next; |
| 1958 if (identical(token, contentEnd)) { |
| 1959 return ""; |
| 1960 } |
| 1961 //TODO (danrubel): handle CDATA and replace HTML character encodings with th
e actual characters |
| 1962 String content = token.lexeme; |
| 1963 token = token.next; |
| 1964 if (identical(token, contentEnd)) { |
| 1965 return content; |
| 1966 } |
| 1967 JavaStringBuilder buffer = new JavaStringBuilder(); |
| 1968 while (!identical(token, contentEnd)) { |
| 1969 buffer.append(token.lexeme); |
| 1970 token = token.next; |
| 1971 } |
| 1972 return buffer.toString(); |
| 1973 } |
| 1974 |
| 1975 @override |
| 1976 Token get endToken { |
| 1977 if (nodeEnd != null) { |
| 1978 return nodeEnd; |
| 1979 } |
| 1980 if (closingTag != null) { |
| 1981 return closingTag; |
| 1982 } |
| 1983 if (contentEnd != null) { |
| 1984 return contentEnd; |
| 1985 } |
| 1986 if (!_tagNodes.isEmpty) { |
| 1987 return _tagNodes[_tagNodes.length - 1].endToken; |
| 1988 } |
| 1989 if (attributeEnd != null) { |
| 1990 return attributeEnd; |
| 1991 } |
| 1992 if (!_attributes.isEmpty) { |
| 1993 return _attributes[_attributes.length - 1].endToken; |
| 1994 } |
| 1995 return _tag; |
| 1996 } |
| 1997 |
| 1998 /** |
| 1999 * Answer the tag name after the starting '<'. |
| 2000 * |
| 2001 * @return the tag name (not `null`) |
| 2002 */ |
| 2003 String get tag => _tag.lexeme; |
| 2004 |
| 2005 /** |
| 2006 * Answer the tag nodes contained in the receiver. Callers should not manipula
te the returned list |
| 2007 * to edit the AST structure. |
| 2008 * |
| 2009 * @return the children (not `null`, contains no `null`s) |
| 2010 */ |
| 2011 List<XmlTagNode> get tagNodes => _tagNodes; |
| 2012 |
| 2013 /** |
| 2014 * Answer the [TokenType#TAG] token after the starting '<'. |
| 2015 * |
| 2016 * @return the token (not `null`) |
| 2017 */ |
| 2018 Token get tagToken => _tag; |
| 2019 |
| 2020 @override |
| 2021 void visitChildren(XmlVisitor visitor) { |
| 2022 for (XmlAttributeNode node in _attributes) { |
| 2023 node.accept(visitor); |
| 2024 } |
| 2025 for (XmlTagNode node in _tagNodes) { |
| 2026 node.accept(visitor); |
| 2027 } |
| 2028 } |
| 2029 } |
| 2030 |
| 2031 /** |
| 2032 * The interface `XmlVisitor` defines the behavior of objects that can be used t
o visit an |
| 2033 * [XmlNode] structure. |
| 2034 */ |
| 2035 abstract class XmlVisitor<R> { |
| 2036 R visitHtmlScriptTagNode(HtmlScriptTagNode node); |
| 2037 |
| 2038 R visitHtmlUnit(HtmlUnit htmlUnit); |
| 2039 |
| 2040 R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode); |
| 2041 |
| 2042 R visitXmlTagNode(XmlTagNode xmlTagNode); |
| 2043 } | 2043 } |
| OLD | NEW |