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

Side by Side Diff: mojo/public/dart/third_party/analyzer/lib/src/generated/html.dart

Issue 1346773002: Stop running pub get at gclient sync time and fix build bugs (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 library engine.html;
6
7 import 'dart:collection';
8
9 import 'ast.dart';
10 import 'element.dart';
11 import 'engine.dart' show AnalysisOptions, AnalysisEngine;
12 import 'error.dart' show AnalysisErrorListener;
13 import 'java_core.dart';
14 import 'java_engine.dart';
15 import 'parser.dart' show Parser;
16 import 'scanner.dart' as sc show Scanner, SubSequenceReader, Token;
17 import 'source.dart';
18
19 /**
20 * The abstract class `AbstractScanner` implements a scanner for HTML code. Subc lasses are
21 * required to implement the interface used to access the characters being scann ed.
22 */
23 abstract class AbstractScanner {
24 static List<String> _NO_PASS_THROUGH_ELEMENTS = <String>[];
25
26 /**
27 * The source being scanned.
28 */
29 final Source source;
30
31 /**
32 * The token pointing to the head of the linked list of tokens.
33 */
34 Token _tokens;
35
36 /**
37 * The last token that was scanned.
38 */
39 Token _tail;
40
41 /**
42 * A list containing the offsets of the first character of each line in the so urce code.
43 */
44 List<int> _lineStarts = new List<int>();
45
46 /**
47 * An array of element tags for which the content between tags should be consi der a single token.
48 */
49 List<String> _passThroughElements = _NO_PASS_THROUGH_ELEMENTS;
50
51 /**
52 * Initialize a newly created scanner.
53 *
54 * @param source the source being scanned
55 */
56 AbstractScanner(this.source) {
57 _tokens = new Token.con1(TokenType.EOF, -1);
58 _tokens.setNext(_tokens);
59 _tail = _tokens;
60 recordStartOfLine();
61 }
62
63 /**
64 * Return an array containing the offsets of the first character of each line in the source code.
65 *
66 * @return an array containing the offsets of the first character of each line in the source code
67 */
68 List<int> get lineStarts => _lineStarts;
69
70 /**
71 * Return the current offset relative to the beginning of the file. Return the initial offset if
72 * the scanner has not yet scanned the source code, and one (1) past the end o f the source code if
73 * the source code has been scanned.
74 *
75 * @return the current offset of the scanner in the source
76 */
77 int get offset;
78
79 /**
80 * Set array of element tags for which the content between tags should be cons ider a single token.
81 */
82 void set passThroughElements(List<String> passThroughElements) {
83 this._passThroughElements = passThroughElements != null
84 ? passThroughElements
85 : _NO_PASS_THROUGH_ELEMENTS;
86 }
87
88 /**
89 * Advance the current position and return the character at the new current po sition.
90 *
91 * @return the character at the new current position
92 */
93 int advance();
94
95 /**
96 * Return the substring of the source code between the start offset and the mo dified current
97 * position. The current position is modified by adding the end delta.
98 *
99 * @param start the offset to the beginning of the string, relative to the sta rt of the file
100 * @param endDelta the number of character after the current location to be in cluded in the
101 * string, or the number of characters before the current location to be excluded if the
102 * offset is negative
103 * @return the specified substring of the source code
104 */
105 String getString(int start, int endDelta);
106
107 /**
108 * Return the character at the current position without changing the current p osition.
109 *
110 * @return the character at the current position
111 */
112 int peek();
113
114 /**
115 * Record the fact that we are at the beginning of a new line in the source.
116 */
117 void recordStartOfLine() {
118 _lineStarts.add(offset);
119 }
120
121 /**
122 * Scan the source code to produce a list of tokens representing the source.
123 *
124 * @return the first token in the list of tokens that were produced
125 */
126 Token tokenize() {
127 _scan();
128 _appendEofToken();
129 return _firstToken();
130 }
131
132 void _appendEofToken() {
133 Token eofToken = new Token.con1(TokenType.EOF, offset);
134 // The EOF token points to itself so that there is always infinite
135 // look-ahead.
136 eofToken.setNext(eofToken);
137 _tail = _tail.setNext(eofToken);
138 }
139
140 Token _emit(Token token) {
141 _tail.setNext(token);
142 _tail = token;
143 return token;
144 }
145
146 Token _emitWithOffset(TokenType type, int start) =>
147 _emit(new Token.con1(type, start));
148
149 Token _emitWithOffsetAndLength(TokenType type, int start, int count) =>
150 _emit(new Token.con2(type, start, getString(start, count)));
151
152 Token _firstToken() => _tokens.next;
153
154 int _recordStartOfLineAndAdvance(int c) {
155 if (c == 0xD) {
156 c = advance();
157 if (c == 0xA) {
158 c = advance();
159 }
160 recordStartOfLine();
161 } else if (c == 0xA) {
162 c = advance();
163 recordStartOfLine();
164 } else {
165 c = advance();
166 }
167 return c;
168 }
169
170 void _scan() {
171 bool inBrackets = false;
172 String endPassThrough = null;
173 int c = advance();
174 while (c >= 0) {
175 int start = offset;
176 if (c == 0x3C) {
177 c = advance();
178 if (c == 0x21) {
179 c = advance();
180 if (c == 0x2D && peek() == 0x2D) {
181 // handle a comment
182 c = advance();
183 int dashCount = 1;
184 while (c >= 0) {
185 if (c == 0x2D) {
186 dashCount++;
187 } else if (c == 0x3E && dashCount >= 2) {
188 c = advance();
189 break;
190 } else {
191 dashCount = 0;
192 }
193 c = _recordStartOfLineAndAdvance(c);
194 }
195 _emitWithOffsetAndLength(TokenType.COMMENT, start, -1);
196 // Capture <!--> and <!---> as tokens but report an error
197 if (_tail.length < 7) {
198 // TODO (danrubel): Report invalid HTML comment
199 }
200 } else {
201 // handle a declaration
202 while (c >= 0) {
203 if (c == 0x3E) {
204 c = advance();
205 break;
206 }
207 c = _recordStartOfLineAndAdvance(c);
208 }
209 _emitWithOffsetAndLength(TokenType.DECLARATION, start, -1);
210 if (!StringUtilities.endsWithChar(_tail.lexeme, 0x3E)) {
211 // TODO (danrubel): Report missing '>' in directive
212 }
213 }
214 } else if (c == 0x3F) {
215 // handle a directive
216 while (c >= 0) {
217 if (c == 0x3F) {
218 c = advance();
219 if (c == 0x3E) {
220 c = advance();
221 break;
222 }
223 } else {
224 c = _recordStartOfLineAndAdvance(c);
225 }
226 }
227 _emitWithOffsetAndLength(TokenType.DIRECTIVE, start, -1);
228 if (_tail.length < 4) {
229 // TODO (danrubel): Report invalid directive
230 }
231 } else if (c == 0x2F) {
232 _emitWithOffset(TokenType.LT_SLASH, start);
233 inBrackets = true;
234 c = advance();
235 } else {
236 inBrackets = true;
237 _emitWithOffset(TokenType.LT, start);
238 // ignore whitespace in braces
239 while (Character.isWhitespace(c)) {
240 c = _recordStartOfLineAndAdvance(c);
241 }
242 // get tag
243 if (Character.isLetterOrDigit(c)) {
244 int tagStart = offset;
245 c = advance();
246 while (Character.isLetterOrDigit(c) || c == 0x2D || c == 0x5F) {
247 c = advance();
248 }
249 _emitWithOffsetAndLength(TokenType.TAG, tagStart, -1);
250 // check tag against passThrough elements
251 String tag = _tail.lexeme;
252 for (String str in _passThroughElements) {
253 if (str == tag) {
254 endPassThrough = "</$str>";
255 break;
256 }
257 }
258 }
259 }
260 } else if (c == 0x3E) {
261 _emitWithOffset(TokenType.GT, start);
262 inBrackets = false;
263 c = advance();
264 // if passThrough != null, read until we match it
265 if (endPassThrough != null) {
266 bool endFound = false;
267 int len = endPassThrough.length;
268 int firstC = endPassThrough.codeUnitAt(0);
269 int index = 0;
270 int nextC = firstC;
271 while (c >= 0) {
272 if (c == nextC) {
273 index++;
274 if (index == len) {
275 endFound = true;
276 break;
277 }
278 nextC = endPassThrough.codeUnitAt(index);
279 } else if (c == firstC) {
280 index = 1;
281 nextC = endPassThrough.codeUnitAt(1);
282 } else {
283 index = 0;
284 nextC = firstC;
285 }
286 c = _recordStartOfLineAndAdvance(c);
287 }
288 if (start + 1 < offset) {
289 if (endFound) {
290 _emitWithOffsetAndLength(TokenType.TEXT, start + 1, -len);
291 _emitWithOffset(TokenType.LT_SLASH, offset - len + 1);
292 _emitWithOffsetAndLength(TokenType.TAG, offset - len + 3, -1);
293 } else {
294 _emitWithOffsetAndLength(TokenType.TEXT, start + 1, -1);
295 }
296 }
297 endPassThrough = null;
298 }
299 } else if (c == 0x2F && peek() == 0x3E) {
300 advance();
301 _emitWithOffset(TokenType.SLASH_GT, start);
302 inBrackets = false;
303 c = advance();
304 } else if (!inBrackets) {
305 c = _recordStartOfLineAndAdvance(c);
306 while (c != 0x3C && c >= 0) {
307 c = _recordStartOfLineAndAdvance(c);
308 }
309 _emitWithOffsetAndLength(TokenType.TEXT, start, -1);
310 } else if (c == 0x22 || c == 0x27) {
311 // read a string
312 int endQuote = c;
313 c = advance();
314 while (c >= 0) {
315 if (c == endQuote) {
316 c = advance();
317 break;
318 }
319 c = _recordStartOfLineAndAdvance(c);
320 }
321 _emitWithOffsetAndLength(TokenType.STRING, start, -1);
322 } else if (c == 0x3D) {
323 // a non-char token
324 _emitWithOffset(TokenType.EQ, start);
325 c = advance();
326 } else if (Character.isWhitespace(c)) {
327 // ignore whitespace in braces
328 do {
329 c = _recordStartOfLineAndAdvance(c);
330 } while (Character.isWhitespace(c));
331 } else if (Character.isLetterOrDigit(c)) {
332 c = advance();
333 while (Character.isLetterOrDigit(c) || c == 0x2D || c == 0x5F) {
334 c = advance();
335 }
336 _emitWithOffsetAndLength(TokenType.TAG, start, -1);
337 } else {
338 // a non-char token
339 _emitWithOffsetAndLength(TokenType.TEXT, start, 0);
340 c = advance();
341 }
342 }
343 }
344 }
345
346 /**
347 * Instances of the class `HtmlParser` are used to parse tokens into a AST struc ture comprised
348 * of [XmlNode]s.
349 */
350 class HtmlParser extends XmlParser {
351 static String _APPLICATION_DART_IN_DOUBLE_QUOTES = "\"application/dart\"";
352
353 static String _APPLICATION_DART_IN_SINGLE_QUOTES = "'application/dart'";
354
355 static String _SCRIPT = "script";
356
357 static String _TYPE = "type";
358
359 /**
360 * A set containing the names of tags that do not have a closing tag.
361 */
362 static Set<String> SELF_CLOSING = new HashSet<String>.from(<String>[
363 "area",
364 "base",
365 "basefont",
366 "br",
367 "col",
368 "frame",
369 "hr",
370 "img",
371 "input",
372 "link",
373 "meta",
374 "param",
375 "!"
376 ]);
377
378 /**
379 * The line information associated with the source being parsed.
380 */
381 LineInfo _lineInfo;
382
383 /**
384 * The error listener to which errors will be reported.
385 */
386 final AnalysisErrorListener _errorListener;
387
388 final AnalysisOptions _options;
389
390 /**
391 * Construct a parser for the specified source.
392 *
393 * [source] is the source being parsed. [_errorListener] is the error
394 * listener to which errors will be reported. [_options] is the analysis
395 * options which should be used for parsing.
396 */
397 HtmlParser(Source source, this._errorListener, this._options) : super(source);
398
399 @override
400 XmlAttributeNode createAttributeNode(Token name, Token equals, Token value) =>
401 new XmlAttributeNode(name, equals, value);
402
403 @override
404 XmlTagNode createTagNode(Token nodeStart, Token tag,
405 List<XmlAttributeNode> attributes, Token attributeEnd,
406 List<XmlTagNode> tagNodes, Token contentEnd, Token closingTag,
407 Token nodeEnd) {
408 if (_isScriptNode(tag, attributes, tagNodes)) {
409 HtmlScriptTagNode tagNode = new HtmlScriptTagNode(nodeStart, tag,
410 attributes, attributeEnd, tagNodes, contentEnd, closingTag, nodeEnd);
411 String contents = tagNode.content;
412 int contentOffset = attributeEnd.end;
413 LineInfo_Location location = _lineInfo.getLocation(contentOffset);
414 sc.Scanner scanner = new sc.Scanner(source,
415 new sc.SubSequenceReader(contents, contentOffset), _errorListener);
416 scanner.setSourceStart(location.lineNumber, location.columnNumber);
417 sc.Token firstToken = scanner.tokenize();
418 Parser parser = new Parser(source, _errorListener);
419 CompilationUnit unit = parser.parseCompilationUnit(firstToken);
420 unit.lineInfo = _lineInfo;
421 tagNode.script = unit;
422 return tagNode;
423 }
424 return new XmlTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes,
425 contentEnd, closingTag, nodeEnd);
426 }
427
428 @override
429 bool isSelfClosing(Token tag) => SELF_CLOSING.contains(tag.lexeme);
430
431 /**
432 * Parse the given tokens.
433 *
434 * @param token the first token in the stream of tokens to be parsed
435 * @param lineInfo the line information created by the scanner
436 * @return the parse result (not `null`)
437 */
438 HtmlUnit parse(Token token, LineInfo lineInfo) {
439 this._lineInfo = lineInfo;
440 List<XmlTagNode> tagNodes = parseTopTagNodes(token);
441 return new HtmlUnit(token, tagNodes, currentToken);
442 }
443
444 /**
445 * Determine if the specified node is a Dart script.
446 *
447 * @param node the node to be tested (not `null`)
448 * @return `true` if the node is a Dart script
449 */
450 bool _isScriptNode(
451 Token tag, List<XmlAttributeNode> attributes, List<XmlTagNode> tagNodes) {
452 if (tagNodes.length != 0 || tag.lexeme != _SCRIPT) {
453 return false;
454 }
455 for (XmlAttributeNode attribute in attributes) {
456 if (attribute.name == _TYPE) {
457 Token valueToken = attribute.valueToken;
458 if (valueToken != null) {
459 String value = valueToken.lexeme;
460 if (value == _APPLICATION_DART_IN_DOUBLE_QUOTES ||
461 value == _APPLICATION_DART_IN_SINGLE_QUOTES) {
462 return true;
463 }
464 }
465 }
466 }
467 return false;
468 }
469
470 /**
471 * Given the contents of an embedded expression that occurs at the given offse t, parse it as a
472 * Dart expression. The contents should not include the expression's delimiter s.
473 *
474 * @param source the source that contains that given token
475 * @param token the token to start parsing from
476 * @return the Dart expression that was parsed
477 */
478 static Expression parseEmbeddedExpression(
479 Source source, sc.Token token, AnalysisErrorListener errorListener) {
480 Parser parser = new Parser(source, errorListener);
481 return parser.parseExpression(token);
482 }
483
484 /**
485 * Given the contents of an embedded expression that occurs at the given offse t, scans it as a
486 * Dart code.
487 *
488 * @param source the source of that contains the given contents
489 * @param contents the contents to scan
490 * @param contentOffset the offset of the contents in the larger file
491 * @return the first Dart token
492 */
493 static sc.Token scanDartSource(Source source, LineInfo lineInfo,
494 String contents, int contentOffset, AnalysisErrorListener errorListener) {
495 LineInfo_Location location = lineInfo.getLocation(contentOffset);
496 sc.Scanner scanner = new sc.Scanner(source,
497 new sc.SubSequenceReader(contents, contentOffset), errorListener);
498 scanner.setSourceStart(location.lineNumber, location.columnNumber);
499 return scanner.tokenize();
500 }
501 }
502
503 /**
504 * Instances of the class `HtmlScriptTagNode` represent a script tag within an H TML file that
505 * references a Dart script.
506 */
507 @deprecated
508 class HtmlScriptTagNode extends XmlTagNode {
509 /**
510 * The AST structure representing the Dart code within this tag.
511 */
512 CompilationUnit _script;
513
514 /**
515 * The element representing this script.
516 */
517 HtmlScriptElement scriptElement;
518
519 /**
520 * Initialize a newly created node to represent a script tag within an HTML fi le that references a
521 * Dart script.
522 *
523 * @param nodeStart the token marking the beginning of the tag
524 * @param tag the name of the tag
525 * @param attributes the attributes in the tag
526 * @param attributeEnd the token terminating the region where attributes can b e
527 * @param tagNodes the children of the tag
528 * @param contentEnd the token that starts the closing tag
529 * @param closingTag the name of the tag that occurs in the closing tag
530 * @param nodeEnd the last token in the tag
531 */
532 HtmlScriptTagNode(Token nodeStart, Token tag,
533 List<XmlAttributeNode> attributes, Token attributeEnd,
534 List<XmlTagNode> tagNodes, Token contentEnd, Token closingTag,
535 Token nodeEnd)
536 : super(nodeStart, tag, attributes, attributeEnd, tagNodes, contentEnd,
537 closingTag, nodeEnd);
538
539 /**
540 * Return the AST structure representing the Dart code within this tag, or `nu ll` if this
541 * tag references an external script.
542 *
543 * @return the AST structure representing the Dart code within this tag
544 */
545 CompilationUnit get script => _script;
546
547 /**
548 * Set the AST structure representing the Dart code within this tag to the giv en compilation unit.
549 *
550 * @param unit the AST structure representing the Dart code within this tag
551 */
552 void set script(CompilationUnit unit) {
553 _script = unit;
554 }
555
556 @override
557 accept(XmlVisitor visitor) => visitor.visitHtmlScriptTagNode(this);
558 }
559
560 /**
561 * Instances of the class `HtmlUnit` represent the contents of an HTML file.
562 */
563 @deprecated
564 class HtmlUnit extends XmlNode {
565 /**
566 * The first token in the token stream that was parsed to form this HTML unit.
567 */
568 final Token beginToken;
569
570 /**
571 * The last token in the token stream that was parsed to form this compilation unit. This token
572 * should always have a type of [TokenType.EOF].
573 */
574 final Token endToken;
575
576 /**
577 * The tag nodes contained in the receiver (not `null`, contains no `null`s).
578 */
579 List<XmlTagNode> _tagNodes;
580
581 /**
582 * Construct a new instance representing the content of an HTML file.
583 *
584 * @param beginToken the first token in the file (not `null`)
585 * @param tagNodes child tag nodes of the receiver (not `null`, contains no `n ull`s)
586 * @param endToken the last token in the token stream which should be of type
587 * [TokenType.EOF]
588 */
589 HtmlUnit(this.beginToken, List<XmlTagNode> tagNodes, this.endToken) {
590 this._tagNodes = becomeParentOfAll(tagNodes);
591 }
592
593 /**
594 * Return the element associated with this HTML unit.
595 *
596 * @return the element or `null` if the receiver is not resolved
597 */
598 @override
599 HtmlElement get element => super.element as HtmlElement;
600
601 @override
602 void set element(Element element) {
603 if (element != null && element is! HtmlElement) {
604 throw new IllegalArgumentException(
605 "HtmlElement expected, but ${element.runtimeType} given");
606 }
607 super.element = element;
608 }
609
610 /**
611 * Answer the tag nodes contained in the receiver. Callers should not manipula te the returned list
612 * to edit the AST structure.
613 *
614 * @return the children (not `null`, contains no `null`s)
615 */
616 List<XmlTagNode> get tagNodes => _tagNodes;
617
618 @override
619 accept(XmlVisitor visitor) => visitor.visitHtmlUnit(this);
620
621 @override
622 void visitChildren(XmlVisitor visitor) {
623 for (XmlTagNode node in _tagNodes) {
624 node.accept(visitor);
625 }
626 }
627 }
628
629 /**
630 * Instances of the class `RecursiveXmlVisitor` implement an XML visitor that wi ll recursively
631 * visit all of the nodes in an XML structure. For example, using an instance of this class to visit
632 * a [XmlTagNode] will also cause all of the contained [XmlAttributeNode]s and
633 * [XmlTagNode]s to be visited.
634 *
635 * Subclasses that override a visit method must either invoke the overridden vis it method or must
636 * explicitly ask the visited node to visit its children. Failure to do so will cause the children
637 * of the visited node to not be visited.
638 */
639 class RecursiveXmlVisitor<R> implements XmlVisitor<R> {
640 @override
641 R visitHtmlScriptTagNode(HtmlScriptTagNode node) {
642 node.visitChildren(this);
643 return null;
644 }
645
646 @override
647 R visitHtmlUnit(HtmlUnit node) {
648 node.visitChildren(this);
649 return null;
650 }
651
652 @override
653 R visitXmlAttributeNode(XmlAttributeNode node) {
654 node.visitChildren(this);
655 return null;
656 }
657
658 @override
659 R visitXmlTagNode(XmlTagNode node) {
660 node.visitChildren(this);
661 return null;
662 }
663 }
664
665 /**
666 * Instances of the class `SimpleXmlVisitor` implement an AST visitor that will do nothing
667 * when visiting an AST node. It is intended to be a superclass for classes that use the visitor
668 * pattern primarily as a dispatch mechanism (and hence don't need to recursivel y visit a whole
669 * structure) and that only need to visit a small number of node types.
670 */
671 class SimpleXmlVisitor<R> implements XmlVisitor<R> {
672 @override
673 R visitHtmlScriptTagNode(HtmlScriptTagNode node) => null;
674
675 @override
676 R visitHtmlUnit(HtmlUnit htmlUnit) => null;
677
678 @override
679 R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode) => null;
680
681 @override
682 R visitXmlTagNode(XmlTagNode xmlTagNode) => null;
683 }
684
685 /**
686 * Instances of the class `StringScanner` implement a scanner that reads from a string. The
687 * scanning logic is in the superclass.
688 */
689 class StringScanner extends AbstractScanner {
690 /**
691 * The string from which characters will be read.
692 */
693 final String _string;
694
695 /**
696 * The number of characters in the string.
697 */
698 int _stringLength = 0;
699
700 /**
701 * The index, relative to the string, of the last character that was read.
702 */
703 int _charOffset = 0;
704
705 /**
706 * Initialize a newly created scanner to scan the characters in the given stri ng.
707 *
708 * @param source the source being scanned
709 * @param string the string from which characters will be read
710 */
711 StringScanner(Source source, this._string) : super(source) {
712 this._stringLength = _string.length;
713 this._charOffset = -1;
714 }
715
716 @override
717 int get offset => _charOffset;
718
719 void set offset(int offset) {
720 _charOffset = offset;
721 }
722
723 @override
724 int advance() {
725 if (++_charOffset < _stringLength) {
726 return _string.codeUnitAt(_charOffset);
727 }
728 _charOffset = _stringLength;
729 return -1;
730 }
731
732 @override
733 String getString(int start, int endDelta) =>
734 _string.substring(start, _charOffset + 1 + endDelta).toString();
735
736 @override
737 int peek() {
738 if (_charOffset + 1 < _stringLength) {
739 return _string.codeUnitAt(_charOffset + 1);
740 }
741 return -1;
742 }
743 }
744
745 /**
746 * Instances of the class `Token` represent a token that was scanned from the in put. Each
747 * token knows which token follows it, acting as the head of a linked list of to kens.
748 */
749 class Token {
750 /**
751 * The offset from the beginning of the file to the first character in the tok en.
752 */
753 final int offset;
754
755 /**
756 * The previous token in the token stream.
757 */
758 Token previous;
759
760 /**
761 * The next token in the token stream.
762 */
763 Token _next;
764
765 /**
766 * The type of the token.
767 */
768 final TokenType type;
769
770 /**
771 * The lexeme represented by this token.
772 */
773 String _value;
774
775 /**
776 * Initialize a newly created token.
777 *
778 * @param type the token type (not `null`)
779 * @param offset the offset from the beginning of the file to the first charac ter in the token
780 */
781 Token.con1(TokenType type, int offset) : this.con2(type, offset, type.lexeme);
782
783 /**
784 * Initialize a newly created token.
785 *
786 * @param type the token type (not `null`)
787 * @param offset the offset from the beginning of the file to the first charac ter in the token
788 * @param value the lexeme represented by this token (not `null`)
789 */
790 Token.con2(this.type, this.offset, String value) {
791 this._value = StringUtilities.intern(value);
792 }
793
794 /**
795 * Return the offset from the beginning of the file to the character after las t character of the
796 * token.
797 *
798 * @return the offset from the beginning of the file to the first character af ter last character
799 * of the token
800 */
801 int get end => offset + length;
802
803 /**
804 * Return `true` if this token is a synthetic token. A synthetic token is a to ken that was
805 * introduced by the parser in order to recover from an error in the code. Syn thetic tokens always
806 * have a length of zero (`0`).
807 *
808 * @return `true` if this token is a synthetic token
809 */
810 bool get isSynthetic => length == 0;
811
812 /**
813 * Return the number of characters in the node's source range.
814 *
815 * @return the number of characters in the node's source range
816 */
817 int get length => lexeme.length;
818
819 /**
820 * Return the lexeme that represents this token.
821 *
822 * @return the lexeme (not `null`)
823 */
824 String get lexeme => _value;
825
826 /**
827 * Return the next token in the token stream.
828 *
829 * @return the next token in the token stream
830 */
831 Token get next => _next;
832
833 /**
834 * Set the next token in the token stream to the given token. This has the sid e-effect of setting
835 * this token to be the previous token for the given token.
836 *
837 * @param token the next token in the token stream
838 * @return the token that was passed in
839 */
840 Token setNext(Token token) {
841 _next = token;
842 token.previous = this;
843 return token;
844 }
845
846 @override
847 String toString() => lexeme;
848 }
849
850 /**
851 * The enumeration `TokenType` defines the types of tokens that can be returned by the
852 * scanner.
853 */
854 class TokenType extends Enum<TokenType> {
855 /**
856 * The type of the token that marks the end of the input.
857 */
858 static const TokenType EOF = const TokenType_EOF('EOF', 0, "");
859
860 static const TokenType EQ = const TokenType('EQ', 1, "=");
861
862 static const TokenType GT = const TokenType('GT', 2, ">");
863
864 static const TokenType LT_SLASH = const TokenType('LT_SLASH', 3, "</");
865
866 static const TokenType LT = const TokenType('LT', 4, "<");
867
868 static const TokenType SLASH_GT = const TokenType('SLASH_GT', 5, "/>");
869
870 static const TokenType COMMENT = const TokenType('COMMENT', 6, null);
871
872 static const TokenType DECLARATION = const TokenType('DECLARATION', 7, null);
873
874 static const TokenType DIRECTIVE = const TokenType('DIRECTIVE', 8, null);
875
876 static const TokenType STRING = const TokenType('STRING', 9, null);
877
878 static const TokenType TAG = const TokenType('TAG', 10, null);
879
880 static const TokenType TEXT = const TokenType('TEXT', 11, null);
881
882 static const List<TokenType> values = const [
883 EOF,
884 EQ,
885 GT,
886 LT_SLASH,
887 LT,
888 SLASH_GT,
889 COMMENT,
890 DECLARATION,
891 DIRECTIVE,
892 STRING,
893 TAG,
894 TEXT
895 ];
896
897 /**
898 * The lexeme that defines this type of token, or `null` if there is more than one possible
899 * lexeme for this type of token.
900 */
901 final String lexeme;
902
903 const TokenType(String name, int ordinal, this.lexeme) : super(name, ordinal);
904 }
905
906 class TokenType_EOF extends TokenType {
907 const TokenType_EOF(String name, int ordinal, String arg0)
908 : super(name, ordinal, arg0);
909
910 @override
911 String toString() => "-eof-";
912 }
913
914 /**
915 * Instances of the class `ToSourceVisitor` write a source representation of a v isited XML
916 * node (and all of it's children) to a writer.
917 */
918 class ToSourceVisitor implements XmlVisitor<Object> {
919 /**
920 * The writer to which the source is to be written.
921 */
922 final PrintWriter _writer;
923
924 /**
925 * Initialize a newly created visitor to write source code representing the vi sited nodes to the
926 * given writer.
927 *
928 * @param writer the writer to which the source is to be written
929 */
930 ToSourceVisitor(this._writer);
931
932 @override
933 Object visitHtmlScriptTagNode(HtmlScriptTagNode node) =>
934 visitXmlTagNode(node);
935
936 @override
937 Object visitHtmlUnit(HtmlUnit node) {
938 for (XmlTagNode child in node.tagNodes) {
939 _visit(child);
940 }
941 return null;
942 }
943
944 @override
945 Object visitXmlAttributeNode(XmlAttributeNode node) {
946 String name = node.name;
947 Token value = node.valueToken;
948 if (name.length == 0) {
949 _writer.print("__");
950 } else {
951 _writer.print(name);
952 }
953 _writer.print("=");
954 if (value == null) {
955 _writer.print("__");
956 } else {
957 _writer.print(value.lexeme);
958 }
959 return null;
960 }
961
962 @override
963 Object visitXmlTagNode(XmlTagNode node) {
964 _writer.print("<");
965 String tagName = node.tag;
966 _writer.print(tagName);
967 for (XmlAttributeNode attribute in node.attributes) {
968 _writer.print(" ");
969 _visit(attribute);
970 }
971 _writer.print(node.attributeEnd.lexeme);
972 if (node.closingTag != null) {
973 for (XmlTagNode child in node.tagNodes) {
974 _visit(child);
975 }
976 _writer.print("</");
977 _writer.print(tagName);
978 _writer.print(">");
979 }
980 return null;
981 }
982
983 /**
984 * Safely visit the given node.
985 *
986 * @param node the node to be visited
987 */
988 void _visit(XmlNode node) {
989 if (node != null) {
990 node.accept(this);
991 }
992 }
993 }
994
995 /**
996 * Instances of `XmlAttributeNode` represent name/value pairs owned by an [XmlTa gNode].
997 */
998 class XmlAttributeNode extends XmlNode {
999 /**
1000 * An empty list of XML attribute nodes.
1001 */
1002 static const List<XmlAttributeNode> EMPTY_LIST = const <XmlAttributeNode>[];
1003
1004 final Token _name;
1005
1006 final Token equals;
1007
1008 final Token _value;
1009
1010 List<XmlExpression> expressions = XmlExpression.EMPTY_ARRAY;
1011
1012 /**
1013 * Construct a new instance representing an XML attribute.
1014 *
1015 * @param name the name token (not `null`). This may be a zero length token if the attribute
1016 * is badly formed.
1017 * @param equals the equals sign or `null` if none
1018 * @param value the value token (not `null`)
1019 */
1020 XmlAttributeNode(this._name, this.equals, this._value);
1021
1022 @override
1023 Token get beginToken => _name;
1024
1025 @override
1026 Token get endToken => _value;
1027
1028 /**
1029 * Answer the attribute name. This may be a zero length string if the attribut e is badly formed.
1030 *
1031 * @return the name (not `null`)
1032 */
1033 String get name => _name.lexeme;
1034
1035 /**
1036 * Answer the attribute name token. This may be a zero length token if the att ribute is badly
1037 * formed.
1038 *
1039 * @return the name token (not `null`)
1040 */
1041 Token get nameToken => _name;
1042
1043 /**
1044 * Answer the lexeme for the value token without the leading and trailing quot es.
1045 *
1046 * @return the text or `null` if the value is not specified
1047 */
1048 String get text {
1049 if (_value == null) {
1050 return null;
1051 }
1052 //TODO (danrubel): replace HTML character encodings with the actual
1053 // characters
1054 String text = _value.lexeme;
1055 int len = text.length;
1056 if (len > 0) {
1057 if (text.codeUnitAt(0) == 0x22) {
1058 if (len > 1 && text.codeUnitAt(len - 1) == 0x22) {
1059 return text.substring(1, len - 1);
1060 } else {
1061 return text.substring(1);
1062 }
1063 } else if (text.codeUnitAt(0) == 0x27) {
1064 if (len > 1 && text.codeUnitAt(len - 1) == 0x27) {
1065 return text.substring(1, len - 1);
1066 } else {
1067 return text.substring(1);
1068 }
1069 }
1070 }
1071 return text;
1072 }
1073
1074 /**
1075 * Answer the offset of the value after the leading quote.
1076 *
1077 * @return the offset of the value, or `-1` if the value is not specified
1078 */
1079 int get textOffset {
1080 if (_value == null) {
1081 return -1;
1082 }
1083 String text = _value.lexeme;
1084 if (StringUtilities.startsWithChar(text, 0x22) ||
1085 StringUtilities.startsWithChar(text, 0x27)) {
1086 return _value.offset + 1;
1087 }
1088 return _value.offset;
1089 }
1090
1091 /**
1092 * Answer the attribute value token. A properly formed value will start and en d with matching
1093 * quote characters, but the value returned may not be properly formed.
1094 *
1095 * @return the value token or `null` if this represents a badly formed attribu te
1096 */
1097 Token get valueToken => _value;
1098
1099 @override
1100 accept(XmlVisitor visitor) => visitor.visitXmlAttributeNode(this);
1101
1102 @override
1103 void visitChildren(XmlVisitor visitor) {
1104 // no children to visit
1105 }
1106 }
1107
1108 /**
1109 * Instances of the class `XmlExpression` represent an abstract expression embed ded into
1110 * [XmlNode].
1111 */
1112 abstract class XmlExpression {
1113 /**
1114 * An empty list of expressions.
1115 */
1116 static const List<XmlExpression> EMPTY_ARRAY = const <XmlExpression>[];
1117
1118 /**
1119 * Return the offset of the character immediately following the last character of this
1120 * expression's source range. This is equivalent to `getOffset() + getLength() `.
1121 *
1122 * @return the offset of the character just past the expression's source range
1123 */
1124 int get end;
1125
1126 /**
1127 * Return the number of characters in the expression's source range.
1128 */
1129 int get length;
1130
1131 /**
1132 * Return the offset of the first character in the expression's source range.
1133 */
1134 int get offset;
1135
1136 /**
1137 * Check if the given offset belongs to the expression's source range.
1138 */
1139 bool contains(int offset) => this.offset <= offset && offset < end;
1140
1141 /**
1142 * Return the [Reference] at the given offset.
1143 *
1144 * @param offset the offset from the beginning of the file
1145 * @return the [Reference] at the given offset, maybe `null`
1146 */
1147 XmlExpression_Reference getReference(int offset);
1148 }
1149
1150 /**
1151 * The reference to the [Element].
1152 */
1153 class XmlExpression_Reference {
1154 Element element;
1155
1156 int offset = 0;
1157
1158 int length = 0;
1159
1160 XmlExpression_Reference(this.element, this.offset, this.length);
1161 }
1162
1163 /**
1164 * The abstract class `XmlNode` defines behavior common to all XML/HTML nodes.
1165 */
1166 abstract class XmlNode {
1167 /**
1168 * The parent of the node, or `null` if the node is the root of an AST structu re.
1169 */
1170 XmlNode _parent;
1171
1172 /**
1173 * The element associated with this node or `null` if the receiver is not reso lved.
1174 */
1175 Element _element;
1176
1177 /**
1178 * Return the first token included in this node's source range.
1179 *
1180 * @return the first token or `null` if none
1181 */
1182 Token get beginToken;
1183
1184 /**
1185 * Return the element associated with this node.
1186 *
1187 * @return the element or `null` if the receiver is not resolved
1188 */
1189 Element get element => _element;
1190
1191 /**
1192 * Set the element associated with this node.
1193 *
1194 * @param element the element
1195 */
1196 void set element(Element element) {
1197 this._element = element;
1198 }
1199
1200 /**
1201 * Return the offset of the character immediately following the last character of this node's
1202 * source range. This is equivalent to `node.getOffset() + node.getLength()`. For an html
1203 * unit this will be equal to the length of the unit's source.
1204 *
1205 * @return the offset of the character just past the node's source range
1206 */
1207 int get end => offset + length;
1208
1209 /**
1210 * Return the last token included in this node's source range.
1211 *
1212 * @return the last token or `null` if none
1213 */
1214 Token get endToken;
1215
1216 /**
1217 * Return the number of characters in the node's source range.
1218 *
1219 * @return the number of characters in the node's source range
1220 */
1221 int get length {
1222 Token beginToken = this.beginToken;
1223 Token endToken = this.endToken;
1224 if (beginToken == null || endToken == null) {
1225 return -1;
1226 }
1227 return endToken.offset + endToken.length - beginToken.offset;
1228 }
1229
1230 /**
1231 * Return the offset from the beginning of the file to the first character in the node's source
1232 * range.
1233 *
1234 * @return the offset from the beginning of the file to the first character in the node's source
1235 * range
1236 */
1237 int get offset {
1238 Token beginToken = this.beginToken;
1239 if (beginToken == null) {
1240 return -1;
1241 }
1242 return this.beginToken.offset;
1243 }
1244
1245 /**
1246 * Return this node's parent node, or `null` if this node is the root of an AS T structure.
1247 *
1248 * Note that the relationship between an AST node and its parent node may chan ge over the lifetime
1249 * of a node.
1250 *
1251 * @return the parent of this node, or `null` if none
1252 */
1253 XmlNode get parent => _parent;
1254
1255 /**
1256 * Set the parent of this node to the given node.
1257 *
1258 * @param newParent the node that is to be made the parent of this node
1259 */
1260 void set parent(XmlNode newParent) {
1261 XmlNode current = newParent;
1262 while (current != null) {
1263 if (identical(current, this)) {
1264 AnalysisEngine.instance.logger.logError(
1265 "Circular structure while setting an XML node's parent",
1266 new CaughtException(
1267 new ArgumentError(_buildRecursiveStructureMessage(newParent)),
1268 null));
1269 return;
1270 }
1271 current = current.parent;
1272 }
1273 _parent = newParent;
1274 }
1275
1276 /**
1277 * Use the given visitor to visit this node.
1278 *
1279 * @param visitor the visitor that will visit this node
1280 * @return the value returned by the visitor as a result of visiting this node
1281 */
1282 accept(XmlVisitor visitor);
1283
1284 /**
1285 * Make this node the parent of the given child node.
1286 *
1287 * @param child the node that will become a child of this node
1288 * @return the node that was made a child of this node
1289 */
1290 XmlNode becomeParentOf(XmlNode child) {
1291 if (child != null) {
1292 XmlNode node = child;
1293 node.parent = this;
1294 }
1295 return child;
1296 }
1297
1298 /**
1299 * Make this node the parent of the given child nodes.
1300 *
1301 * @param children the nodes that will become the children of this node
1302 * @param ifEmpty the (empty) nodes to return if "children" is empty
1303 * @return the nodes that were made children of this node
1304 */
1305 List becomeParentOfAll(List children, {List ifEmpty}) {
1306 if (children == null || children.isEmpty) {
1307 if (ifEmpty != null) {
1308 return ifEmpty;
1309 }
1310 }
1311 if (children != null) {
1312 children.forEach((XmlNode node) {
1313 node.parent = this;
1314 });
1315 }
1316 return children;
1317 }
1318
1319 @override
1320 String toString() {
1321 PrintStringWriter writer = new PrintStringWriter();
1322 accept(new ToSourceVisitor(writer));
1323 return writer.toString();
1324 }
1325
1326 /**
1327 * Use the given visitor to visit all of the children of this node. The childr en will be visited
1328 * in source order.
1329 *
1330 * @param visitor the visitor that will be used to visit the children of this node
1331 */
1332 void visitChildren(XmlVisitor visitor);
1333
1334 /**
1335 * This method exists for debugging purposes only.
1336 */
1337 void _appendIdentifier(StringBuffer buffer, XmlNode node) {
1338 if (node is XmlTagNode) {
1339 buffer.write(node.tag);
1340 } else if (node is XmlAttributeNode) {
1341 buffer.write(node.name);
1342 } else {
1343 buffer.write("htmlUnit");
1344 }
1345 }
1346
1347 /**
1348 * This method exists for debugging purposes only.
1349 */
1350 String _buildRecursiveStructureMessage(XmlNode newParent) {
1351 StringBuffer buffer = new StringBuffer();
1352 buffer.write("Attempt to create recursive structure: ");
1353 XmlNode current = newParent;
1354 while (current != null) {
1355 if (!identical(current, newParent)) {
1356 buffer.write(" -> ");
1357 }
1358 if (identical(current, this)) {
1359 buffer.writeCharCode(0x2A);
1360 _appendIdentifier(buffer, current);
1361 buffer.writeCharCode(0x2A);
1362 } else {
1363 _appendIdentifier(buffer, current);
1364 }
1365 current = current.parent;
1366 }
1367 return buffer.toString();
1368 }
1369 }
1370
1371 /**
1372 * Instances of the class `XmlParser` are used to parse tokens into a AST struct ure comprised
1373 * of [XmlNode]s.
1374 */
1375 class XmlParser {
1376 /**
1377 * The source being parsed.
1378 */
1379 final Source source;
1380
1381 /**
1382 * The next token to be parsed.
1383 */
1384 Token _currentToken;
1385
1386 /**
1387 * Construct a parser for the specified source.
1388 *
1389 * @param source the source being parsed
1390 */
1391 XmlParser(this.source);
1392
1393 /**
1394 * Answer the current token.
1395 *
1396 * @return the current token
1397 */
1398 Token get currentToken => _currentToken;
1399
1400 /**
1401 * Create a node representing an attribute.
1402 *
1403 * @param name the name of the attribute
1404 * @param equals the equals sign, or `null` if there is no value
1405 * @param value the value of the attribute
1406 * @return the node that was created
1407 */
1408 XmlAttributeNode createAttributeNode(Token name, Token equals, Token value) =>
1409 new XmlAttributeNode(name, equals, value);
1410
1411 /**
1412 * Create a node representing a tag.
1413 *
1414 * @param nodeStart the token marking the beginning of the tag
1415 * @param tag the name of the tag
1416 * @param attributes the attributes in the tag
1417 * @param attributeEnd the token terminating the region where attributes can b e
1418 * @param tagNodes the children of the tag
1419 * @param contentEnd the token that starts the closing tag
1420 * @param closingTag the name of the tag that occurs in the closing tag
1421 * @param nodeEnd the last token in the tag
1422 * @return the node that was created
1423 */
1424 XmlTagNode createTagNode(Token nodeStart, Token tag,
1425 List<XmlAttributeNode> attributes, Token attributeEnd,
1426 List<XmlTagNode> tagNodes, Token contentEnd, Token closingTag,
1427 Token nodeEnd) => new XmlTagNode(nodeStart, tag, attributes, attributeEnd,
1428 tagNodes, contentEnd, closingTag, nodeEnd);
1429
1430 /**
1431 * Answer `true` if the specified tag is self closing and thus should never ha ve content or
1432 * child tag nodes.
1433 *
1434 * @param tag the tag (not `null`)
1435 * @return `true` if self closing
1436 */
1437 bool isSelfClosing(Token tag) => false;
1438
1439 /**
1440 * Parse the entire token stream and in the process, advance the current token to the end of the
1441 * token stream.
1442 *
1443 * @return the list of tag nodes found (not `null`, contains no `null`)
1444 */
1445 List<XmlTagNode> parseTopTagNodes(Token firstToken) {
1446 _currentToken = firstToken;
1447 List<XmlTagNode> tagNodes = new List<XmlTagNode>();
1448 TokenType type = _currentToken.type;
1449 while (type != TokenType.EOF) {
1450 if (type == TokenType.LT) {
1451 tagNodes.add(_parseTagNode());
1452 } else if (type == TokenType.DECLARATION ||
1453 type == TokenType.DIRECTIVE ||
1454 type == TokenType.COMMENT) {
1455 // ignored tokens
1456 _currentToken = _currentToken.next;
1457 } else {
1458 _reportUnexpectedToken();
1459 _currentToken = _currentToken.next;
1460 }
1461 type = _currentToken.type;
1462 }
1463 return tagNodes;
1464 }
1465
1466 /**
1467 * Insert a synthetic token of the specified type before the current token
1468 *
1469 * @param type the type of token to be inserted (not `null`)
1470 * @return the synthetic token that was inserted (not `null`)
1471 */
1472 Token _insertSyntheticToken(TokenType type) {
1473 Token token = new Token.con2(type, _currentToken.offset, "");
1474 _currentToken.previous.setNext(token);
1475 token.setNext(_currentToken);
1476 return token;
1477 }
1478
1479 /**
1480 * Parse the token stream for an attribute. This method advances the current t oken over the
1481 * attribute, but should not be called if the [currentToken] is not [TokenType .TAG].
1482 *
1483 * @return the attribute (not `null`)
1484 */
1485 XmlAttributeNode _parseAttribute() {
1486 // Assume the current token is a tag
1487 Token name = _currentToken;
1488 _currentToken = _currentToken.next;
1489 // Equals sign
1490 Token equals;
1491 if (_currentToken.type == TokenType.EQ) {
1492 equals = _currentToken;
1493 _currentToken = _currentToken.next;
1494 } else {
1495 _reportUnexpectedToken();
1496 equals = _insertSyntheticToken(TokenType.EQ);
1497 }
1498 // String value
1499 Token value;
1500 if (_currentToken.type == TokenType.STRING) {
1501 value = _currentToken;
1502 _currentToken = _currentToken.next;
1503 } else {
1504 _reportUnexpectedToken();
1505 value = _insertSyntheticToken(TokenType.STRING);
1506 }
1507 return createAttributeNode(name, equals, value);
1508 }
1509
1510 /**
1511 * Parse the stream for a sequence of attributes. This method advances the cur rent token to the
1512 * next [TokenType.GT], [TokenType.SLASH_GT], or [TokenType.EOF].
1513 *
1514 * @return a collection of zero or more attributes (not `null`, contains no `n ull`s)
1515 */
1516 List<XmlAttributeNode> _parseAttributes() {
1517 TokenType type = _currentToken.type;
1518 if (type == TokenType.GT ||
1519 type == TokenType.SLASH_GT ||
1520 type == TokenType.EOF) {
1521 return XmlTagNode.NO_ATTRIBUTES;
1522 }
1523 List<XmlAttributeNode> attributes = new List<XmlAttributeNode>();
1524 while (type != TokenType.GT &&
1525 type != TokenType.SLASH_GT &&
1526 type != TokenType.EOF) {
1527 if (type == TokenType.TAG) {
1528 attributes.add(_parseAttribute());
1529 } else {
1530 _reportUnexpectedToken();
1531 _currentToken = _currentToken.next;
1532 }
1533 type = _currentToken.type;
1534 }
1535 return attributes;
1536 }
1537
1538 /**
1539 * Parse the stream for a sequence of tag nodes existing within a parent tag n ode. This method
1540 * advances the current token to the next [TokenType.LT_SLASH] or [TokenType.E OF].
1541 *
1542 * @return a list of nodes (not `null`, contains no `null`s)
1543 */
1544 List<XmlTagNode> _parseChildTagNodes() {
1545 TokenType type = _currentToken.type;
1546 if (type == TokenType.LT_SLASH || type == TokenType.EOF) {
1547 return XmlTagNode.NO_TAG_NODES;
1548 }
1549 List<XmlTagNode> nodes = new List<XmlTagNode>();
1550 while (type != TokenType.LT_SLASH && type != TokenType.EOF) {
1551 if (type == TokenType.LT) {
1552 nodes.add(_parseTagNode());
1553 } else if (type == TokenType.COMMENT) {
1554 // ignored token
1555 _currentToken = _currentToken.next;
1556 } else {
1557 _reportUnexpectedToken();
1558 _currentToken = _currentToken.next;
1559 }
1560 type = _currentToken.type;
1561 }
1562 return nodes;
1563 }
1564
1565 /**
1566 * Parse the token stream for the next tag node. This method advances current token over the
1567 * parsed tag node, but should only be called if the current token is [TokenTy pe.LT]
1568 *
1569 * @return the tag node or `null` if none found
1570 */
1571 XmlTagNode _parseTagNode() {
1572 // Assume that the current node is a tag node start TokenType.LT
1573 Token nodeStart = _currentToken;
1574 _currentToken = _currentToken.next;
1575 // Get the tag or create a synthetic tag and report an error
1576 Token tag;
1577 if (_currentToken.type == TokenType.TAG) {
1578 tag = _currentToken;
1579 _currentToken = _currentToken.next;
1580 } else {
1581 _reportUnexpectedToken();
1582 tag = _insertSyntheticToken(TokenType.TAG);
1583 }
1584 // Parse the attributes
1585 List<XmlAttributeNode> attributes = _parseAttributes();
1586 // Token ending attribute list
1587 Token attributeEnd;
1588 if (_currentToken.type == TokenType.GT ||
1589 _currentToken.type == TokenType.SLASH_GT) {
1590 attributeEnd = _currentToken;
1591 _currentToken = _currentToken.next;
1592 } else {
1593 _reportUnexpectedToken();
1594 attributeEnd = _insertSyntheticToken(TokenType.SLASH_GT);
1595 }
1596 // If the node has no children, then return the node
1597 if (attributeEnd.type == TokenType.SLASH_GT || isSelfClosing(tag)) {
1598 return createTagNode(nodeStart, tag, attributes, attributeEnd,
1599 XmlTagNode.NO_TAG_NODES, _currentToken, null, attributeEnd);
1600 }
1601 // Parse the child tag nodes
1602 List<XmlTagNode> tagNodes = _parseChildTagNodes();
1603 // Token ending child tag nodes
1604 Token contentEnd;
1605 if (_currentToken.type == TokenType.LT_SLASH) {
1606 contentEnd = _currentToken;
1607 _currentToken = _currentToken.next;
1608 } else {
1609 // TODO (danrubel): handle self closing HTML elements by inserting
1610 // synthetic tokens but not reporting an error
1611 _reportUnexpectedToken();
1612 contentEnd = _insertSyntheticToken(TokenType.LT_SLASH);
1613 }
1614 // Closing tag
1615 Token closingTag;
1616 if (_currentToken.type == TokenType.TAG) {
1617 closingTag = _currentToken;
1618 _currentToken = _currentToken.next;
1619 } else {
1620 _reportUnexpectedToken();
1621 closingTag = _insertSyntheticToken(TokenType.TAG);
1622 }
1623 // Token ending node
1624 Token nodeEnd;
1625 if (_currentToken.type == TokenType.GT) {
1626 nodeEnd = _currentToken;
1627 _currentToken = _currentToken.next;
1628 } else {
1629 _reportUnexpectedToken();
1630 nodeEnd = _insertSyntheticToken(TokenType.GT);
1631 }
1632 return createTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes,
1633 contentEnd, closingTag, nodeEnd);
1634 }
1635
1636 /**
1637 * Report the current token as unexpected
1638 */
1639 void _reportUnexpectedToken() {
1640 // TODO (danrubel): report unexpected token
1641 }
1642 }
1643
1644 /**
1645 * Instances of `XmlTagNode` represent XML or HTML elements such as `` and
1646 * `<body foo="bar"> ... </body>`.
1647 */
1648 class XmlTagNode extends XmlNode {
1649 /**
1650 * Constant representing empty list of attributes.
1651 */
1652 static List<XmlAttributeNode> NO_ATTRIBUTES =
1653 new UnmodifiableListView(new List<XmlAttributeNode>());
1654
1655 /**
1656 * Constant representing empty list of tag nodes.
1657 */
1658 static List<XmlTagNode> NO_TAG_NODES =
1659 new UnmodifiableListView(new List<XmlTagNode>());
1660
1661 /**
1662 * The starting [TokenType.LT] token (not `null`).
1663 */
1664 final Token nodeStart;
1665
1666 /**
1667 * The [TokenType.TAG] token after the starting '&lt;' (not `null`).
1668 */
1669 final Token _tag;
1670
1671 /**
1672 * The attributes contained by the receiver (not `null`, contains no `null`s).
1673 */
1674 List<XmlAttributeNode> _attributes;
1675
1676 /**
1677 * The [TokenType.GT] or [TokenType.SLASH_GT] token after the attributes (not
1678 * `null`). The token may be the same token as [nodeEnd] if there are no child
1679 * [tagNodes].
1680 */
1681 final Token attributeEnd;
1682
1683 /**
1684 * The tag nodes contained in the receiver (not `null`, contains no `null`s).
1685 */
1686 List<XmlTagNode> _tagNodes;
1687
1688 /**
1689 * The token (not `null`) after the content, which may be
1690 * * (1) [TokenType.LT_SLASH] for nodes with open and close tags, or
1691 * * (2) the [TokenType.LT] nodeStart of the next sibling node if this node is self
1692 * closing or the attributeEnd is [TokenType.SLASH_GT], or
1693 * * (3) [TokenType.EOF] if the node does not have a closing tag and is the la st node in
1694 * the stream [TokenType.LT_SLASH] token after the content, or `null` if there is no
1695 * content and the attributes ended with [TokenType.SLASH_GT].
1696 */
1697 final Token contentEnd;
1698
1699 /**
1700 * The closing [TokenType.TAG] after the child elements or `null` if there is no
1701 * content and the attributes ended with [TokenType.SLASH_GT]
1702 */
1703 final Token closingTag;
1704
1705 /**
1706 * The ending [TokenType.GT] or [TokenType.SLASH_GT] token (not `null`).
1707 */
1708 final Token nodeEnd;
1709
1710 /**
1711 * The expressions that are embedded in the tag's content.
1712 */
1713 List<XmlExpression> expressions = XmlExpression.EMPTY_ARRAY;
1714
1715 /**
1716 * Construct a new instance representing an XML or HTML element
1717 *
1718 * @param nodeStart the starting [TokenType.LT] token (not `null`)
1719 * @param tag the [TokenType.TAG] token after the starting '&lt;' (not `null`) .
1720 * @param attributes the attributes associated with this element or [NO_ATTRIB UTES] (not
1721 * `null`, contains no `null`s)
1722 * @param attributeEnd The [TokenType.GT] or [TokenType.SLASH_GT] token after the
1723 * attributes (not `null`). The token may be the same token as [nodeE nd] if
1724 * there are no child [tagNodes].
1725 * @param tagNodes child tag nodes of the receiver or [NO_TAG_NODES] (not `nul l`,
1726 * contains no `null`s)
1727 * @param contentEnd the token (not `null`) after the content, which may be
1728 * * (1) [TokenType.LT_SLASH] for nodes with open and close tags, or
1729 * * (2) the [TokenType.LT] nodeStart of the next sibling node if thi s node is
1730 * self closing or the attributeEnd is [TokenType.SLASH_GT], or
1731 * * (3) [TokenType.EOF] if the node does not have a closing tag and is the last
1732 * node in the stream [TokenType.LT_SLASH] token after the content, o r `null`
1733 * if there is no content and the attributes ended with [TokenType.SL ASH_GT].
1734 * @param closingTag the closing [TokenType.TAG] after the child elements or ` null` if
1735 * there is no content and the attributes ended with [TokenType.SLASH _GT]
1736 * @param nodeEnd the ending [TokenType.GT] or [TokenType.SLASH_GT] token (not
1737 * `null`)
1738 */
1739 XmlTagNode(this.nodeStart, this._tag, List<XmlAttributeNode> attributes,
1740 this.attributeEnd, List<XmlTagNode> tagNodes, this.contentEnd,
1741 this.closingTag, this.nodeEnd) {
1742 this._attributes = becomeParentOfAll(attributes, ifEmpty: NO_ATTRIBUTES);
1743 this._tagNodes = becomeParentOfAll(tagNodes, ifEmpty: NO_TAG_NODES);
1744 }
1745
1746 /**
1747 * Answer the receiver's attributes. Callers should not manipulate the returne d list to edit the
1748 * AST structure.
1749 *
1750 * @return the attributes (not `null`, contains no `null`s)
1751 */
1752 List<XmlAttributeNode> get attributes => _attributes;
1753
1754 @override
1755 Token get beginToken => nodeStart;
1756
1757 /**
1758 * Return a string representing the content contained in the receiver. This
1759 * includes the textual representation of any child tag nodes ([getTagNodes]).
1760 * Whitespace between '&lt;', '&lt;/', and '>', '/>' is discarded, but all
1761 * other whitespace is preserved.
1762 */
1763 String get content {
1764 Token token = attributeEnd.next;
1765 if (identical(token, contentEnd)) {
1766 return "";
1767 }
1768 // TODO(danrubel) Handle CDATA and replace HTML character encodings with
1769 // the actual characters.
1770 String content = token.lexeme;
1771 token = token.next;
1772 if (identical(token, contentEnd)) {
1773 return content;
1774 }
1775 StringBuffer buffer = new StringBuffer();
1776 buffer.write(content);
1777 while (!identical(token, contentEnd)) {
1778 buffer.write(token.lexeme);
1779 token = token.next;
1780 }
1781 return buffer.toString();
1782 }
1783
1784 @override
1785 Token get endToken {
1786 if (nodeEnd != null) {
1787 return nodeEnd;
1788 }
1789 if (closingTag != null) {
1790 return closingTag;
1791 }
1792 if (contentEnd != null) {
1793 return contentEnd;
1794 }
1795 if (!_tagNodes.isEmpty) {
1796 return _tagNodes[_tagNodes.length - 1].endToken;
1797 }
1798 if (attributeEnd != null) {
1799 return attributeEnd;
1800 }
1801 if (!_attributes.isEmpty) {
1802 return _attributes[_attributes.length - 1].endToken;
1803 }
1804 return _tag;
1805 }
1806
1807 /**
1808 * Answer the tag name after the starting '&lt;'.
1809 *
1810 * @return the tag name (not `null`)
1811 */
1812 String get tag => _tag.lexeme;
1813
1814 /**
1815 * Answer the tag nodes contained in the receiver. Callers should not manipula te the returned list
1816 * to edit the AST structure.
1817 *
1818 * @return the children (not `null`, contains no `null`s)
1819 */
1820 List<XmlTagNode> get tagNodes => _tagNodes;
1821
1822 /**
1823 * Answer the [TokenType.TAG] token after the starting '&lt;'.
1824 *
1825 * @return the token (not `null`)
1826 */
1827 Token get tagToken => _tag;
1828
1829 @override
1830 accept(XmlVisitor visitor) => visitor.visitXmlTagNode(this);
1831
1832 /**
1833 * Answer the attribute with the specified name.
1834 *
1835 * @param name the attribute name
1836 * @return the attribute or `null` if no matching attribute is found
1837 */
1838 XmlAttributeNode getAttribute(String name) {
1839 for (XmlAttributeNode attribute in _attributes) {
1840 if (attribute.name == name) {
1841 return attribute;
1842 }
1843 }
1844 return null;
1845 }
1846
1847 /**
1848 * Find the attribute with the given name (see [getAttribute] and answer the l exeme
1849 * for the attribute's value token without the leading and trailing quotes (se e
1850 * [XmlAttributeNode.getText]).
1851 *
1852 * @param name the attribute name
1853 * @return the attribute text or `null` if no matching attribute is found
1854 */
1855 String getAttributeText(String name) {
1856 XmlAttributeNode attribute = getAttribute(name);
1857 return attribute != null ? attribute.text : null;
1858 }
1859
1860 @override
1861 void visitChildren(XmlVisitor visitor) {
1862 for (XmlAttributeNode node in _attributes) {
1863 node.accept(visitor);
1864 }
1865 for (XmlTagNode node in _tagNodes) {
1866 node.accept(visitor);
1867 }
1868 }
1869 }
1870
1871 /**
1872 * The interface `XmlVisitor` defines the behavior of objects that can be used t o visit an
1873 * [XmlNode] structure.
1874 */
1875 abstract class XmlVisitor<R> {
1876 R visitHtmlScriptTagNode(HtmlScriptTagNode node);
1877
1878 R visitHtmlUnit(HtmlUnit htmlUnit);
1879
1880 R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode);
1881
1882 R visitXmlTagNode(XmlTagNode xmlTagNode);
1883 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698