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

Side by Side Diff: pkg/analyzer/lib/src/generated/html.dart

Issue 1502213002: Remove deprecated code (Closed) Base URL: https://github.com/dart-lang/sdk.git@analyzer-breaking-0.27
Patch Set: Created 5 years 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(
405 Token nodeStart,
406 Token tag,
407 List<XmlAttributeNode> attributes,
408 Token attributeEnd,
409 List<XmlTagNode> tagNodes,
410 Token contentEnd,
411 Token closingTag,
412 Token nodeEnd) {
413 if (_isScriptNode(tag, attributes, tagNodes)) {
414 HtmlScriptTagNode tagNode = new HtmlScriptTagNode(nodeStart, tag,
415 attributes, attributeEnd, tagNodes, contentEnd, closingTag, nodeEnd);
416 String contents = tagNode.content;
417 int contentOffset = attributeEnd.end;
418 LineInfo_Location location = _lineInfo.getLocation(contentOffset);
419 sc.Scanner scanner = new sc.Scanner(source,
420 new sc.SubSequenceReader(contents, contentOffset), _errorListener);
421 scanner.setSourceStart(location.lineNumber, location.columnNumber);
422 sc.Token firstToken = scanner.tokenize();
423 Parser parser = new Parser(source, _errorListener);
424 CompilationUnit unit = parser.parseCompilationUnit(firstToken);
425 unit.lineInfo = _lineInfo;
426 tagNode.script = unit;
427 return tagNode;
428 }
429 return new XmlTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes,
430 contentEnd, closingTag, nodeEnd);
431 }
432
433 @override
434 bool isSelfClosing(Token tag) => SELF_CLOSING.contains(tag.lexeme);
435
436 /**
437 * Parse the given tokens.
438 *
439 * @param token the first token in the stream of tokens to be parsed
440 * @param lineInfo the line information created by the scanner
441 * @return the parse result (not `null`)
442 */
443 HtmlUnit parse(Token token, LineInfo lineInfo) {
444 this._lineInfo = lineInfo;
445 List<XmlTagNode> tagNodes = parseTopTagNodes(token);
446 return new HtmlUnit(token, tagNodes, currentToken);
447 }
448
449 /**
450 * Determine if the specified node is a Dart script.
451 *
452 * @param node the node to be tested (not `null`)
453 * @return `true` if the node is a Dart script
454 */
455 bool _isScriptNode(
456 Token tag, List<XmlAttributeNode> attributes, List<XmlTagNode> tagNodes) {
457 if (tagNodes.length != 0 || tag.lexeme != _SCRIPT) {
458 return false;
459 }
460 for (XmlAttributeNode attribute in attributes) {
461 if (attribute.name == _TYPE) {
462 Token valueToken = attribute.valueToken;
463 if (valueToken != null) {
464 String value = valueToken.lexeme;
465 if (value == _APPLICATION_DART_IN_DOUBLE_QUOTES ||
466 value == _APPLICATION_DART_IN_SINGLE_QUOTES) {
467 return true;
468 }
469 }
470 }
471 }
472 return false;
473 }
474
475 /**
476 * Given the contents of an embedded expression that occurs at the given offse t, parse it as a
477 * Dart expression. The contents should not include the expression's delimiter s.
478 *
479 * @param source the source that contains that given token
480 * @param token the token to start parsing from
481 * @return the Dart expression that was parsed
482 */
483 static Expression parseEmbeddedExpression(
484 Source source, sc.Token token, AnalysisErrorListener errorListener) {
485 Parser parser = new Parser(source, errorListener);
486 return parser.parseExpression(token);
487 }
488
489 /**
490 * Given the contents of an embedded expression that occurs at the given offse t, scans it as a
491 * Dart code.
492 *
493 * @param source the source of that contains the given contents
494 * @param contents the contents to scan
495 * @param contentOffset the offset of the contents in the larger file
496 * @return the first Dart token
497 */
498 static sc.Token scanDartSource(Source source, LineInfo lineInfo,
499 String contents, int contentOffset, AnalysisErrorListener errorListener) {
500 LineInfo_Location location = lineInfo.getLocation(contentOffset);
501 sc.Scanner scanner = new sc.Scanner(source,
502 new sc.SubSequenceReader(contents, contentOffset), errorListener);
503 scanner.setSourceStart(location.lineNumber, location.columnNumber);
504 return scanner.tokenize();
505 }
506 }
507
508 /**
509 * Instances of the class `HtmlScriptTagNode` represent a script tag within an H TML file that
510 * references a Dart script.
511 */
512 @deprecated
513 class HtmlScriptTagNode extends XmlTagNode {
514 /**
515 * The AST structure representing the Dart code within this tag.
516 */
517 CompilationUnit _script;
518
519 /**
520 * The element representing this script.
521 */
522 HtmlScriptElement scriptElement;
523
524 /**
525 * Initialize a newly created node to represent a script tag within an HTML fi le that references a
526 * Dart script.
527 *
528 * @param nodeStart the token marking the beginning of the tag
529 * @param tag the name of the tag
530 * @param attributes the attributes in the tag
531 * @param attributeEnd the token terminating the region where attributes can b e
532 * @param tagNodes the children of the tag
533 * @param contentEnd the token that starts the closing tag
534 * @param closingTag the name of the tag that occurs in the closing tag
535 * @param nodeEnd the last token in the tag
536 */
537 HtmlScriptTagNode(
538 Token nodeStart,
539 Token tag,
540 List<XmlAttributeNode> attributes,
541 Token attributeEnd,
542 List<XmlTagNode> tagNodes,
543 Token contentEnd,
544 Token closingTag,
545 Token nodeEnd)
546 : super(nodeStart, tag, attributes, attributeEnd, tagNodes, contentEnd,
547 closingTag, nodeEnd);
548
549 /**
550 * Return the AST structure representing the Dart code within this tag, or `nu ll` if this
551 * tag references an external script.
552 *
553 * @return the AST structure representing the Dart code within this tag
554 */
555 CompilationUnit get script => _script;
556
557 /**
558 * Set the AST structure representing the Dart code within this tag to the giv en compilation unit.
559 *
560 * @param unit the AST structure representing the Dart code within this tag
561 */
562 void set script(CompilationUnit unit) {
563 _script = unit;
564 }
565
566 @override
567 accept(XmlVisitor visitor) => visitor.visitHtmlScriptTagNode(this);
568 }
569
570 /**
571 * Instances of the class `HtmlUnit` represent the contents of an HTML file.
572 */
573 @deprecated
574 class HtmlUnit extends XmlNode {
575 /**
576 * The first token in the token stream that was parsed to form this HTML unit.
577 */
578 final Token beginToken;
579
580 /**
581 * The last token in the token stream that was parsed to form this compilation unit. This token
582 * should always have a type of [TokenType.EOF].
583 */
584 final Token endToken;
585
586 /**
587 * The tag nodes contained in the receiver (not `null`, contains no `null`s).
588 */
589 List<XmlTagNode> _tagNodes;
590
591 /**
592 * Construct a new instance representing the content of an HTML file.
593 *
594 * @param beginToken the first token in the file (not `null`)
595 * @param tagNodes child tag nodes of the receiver (not `null`, contains no `n ull`s)
596 * @param endToken the last token in the token stream which should be of type
597 * [TokenType.EOF]
598 */
599 HtmlUnit(this.beginToken, List<XmlTagNode> tagNodes, this.endToken) {
600 this._tagNodes = becomeParentOfAll(tagNodes);
601 }
602
603 /**
604 * Return the element associated with this HTML unit.
605 *
606 * @return the element or `null` if the receiver is not resolved
607 */
608 @override
609 HtmlElement get element => super.element as HtmlElement;
610
611 @override
612 void set element(Element element) {
613 if (element != null && element is! HtmlElement) {
614 throw new IllegalArgumentException(
615 "HtmlElement expected, but ${element.runtimeType} given");
616 }
617 super.element = element;
618 }
619
620 /**
621 * Answer the tag nodes contained in the receiver. Callers should not manipula te the returned list
622 * to edit the AST structure.
623 *
624 * @return the children (not `null`, contains no `null`s)
625 */
626 List<XmlTagNode> get tagNodes => _tagNodes;
627
628 @override
629 accept(XmlVisitor visitor) => visitor.visitHtmlUnit(this);
630
631 @override
632 void visitChildren(XmlVisitor visitor) {
633 for (XmlTagNode node in _tagNodes) {
634 node.accept(visitor);
635 }
636 }
637 }
638
639 /**
640 * Instances of the class `RecursiveXmlVisitor` implement an XML visitor that wi ll recursively
641 * visit all of the nodes in an XML structure. For example, using an instance of this class to visit
642 * a [XmlTagNode] will also cause all of the contained [XmlAttributeNode]s and
643 * [XmlTagNode]s to be visited.
644 *
645 * Subclasses that override a visit method must either invoke the overridden vis it method or must
646 * explicitly ask the visited node to visit its children. Failure to do so will cause the children
647 * of the visited node to not be visited.
648 */
649 class RecursiveXmlVisitor<R> implements XmlVisitor<R> {
650 @override
651 R visitHtmlScriptTagNode(HtmlScriptTagNode node) {
652 node.visitChildren(this);
653 return null;
654 }
655
656 @override
657 R visitHtmlUnit(HtmlUnit node) {
658 node.visitChildren(this);
659 return null;
660 }
661
662 @override
663 R visitXmlAttributeNode(XmlAttributeNode node) {
664 node.visitChildren(this);
665 return null;
666 }
667
668 @override
669 R visitXmlTagNode(XmlTagNode node) {
670 node.visitChildren(this);
671 return null;
672 }
673 }
674
675 /**
676 * Instances of the class `SimpleXmlVisitor` implement an AST visitor that will do nothing
677 * when visiting an AST node. It is intended to be a superclass for classes that use the visitor
678 * pattern primarily as a dispatch mechanism (and hence don't need to recursivel y visit a whole
679 * structure) and that only need to visit a small number of node types.
680 */
681 class SimpleXmlVisitor<R> implements XmlVisitor<R> {
682 @override
683 R visitHtmlScriptTagNode(HtmlScriptTagNode node) => null;
684
685 @override
686 R visitHtmlUnit(HtmlUnit htmlUnit) => null;
687
688 @override
689 R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode) => null;
690
691 @override
692 R visitXmlTagNode(XmlTagNode xmlTagNode) => null;
693 }
694
695 /**
696 * Instances of the class `StringScanner` implement a scanner that reads from a string. The
697 * scanning logic is in the superclass.
698 */
699 class StringScanner extends AbstractScanner {
700 /**
701 * The string from which characters will be read.
702 */
703 final String _string;
704
705 /**
706 * The number of characters in the string.
707 */
708 int _stringLength = 0;
709
710 /**
711 * The index, relative to the string, of the last character that was read.
712 */
713 int _charOffset = 0;
714
715 /**
716 * Initialize a newly created scanner to scan the characters in the given stri ng.
717 *
718 * @param source the source being scanned
719 * @param string the string from which characters will be read
720 */
721 StringScanner(Source source, this._string) : super(source) {
722 this._stringLength = _string.length;
723 this._charOffset = -1;
724 }
725
726 @override
727 int get offset => _charOffset;
728
729 void set offset(int offset) {
730 _charOffset = offset;
731 }
732
733 @override
734 int advance() {
735 if (++_charOffset < _stringLength) {
736 return _string.codeUnitAt(_charOffset);
737 }
738 _charOffset = _stringLength;
739 return -1;
740 }
741
742 @override
743 String getString(int start, int endDelta) =>
744 _string.substring(start, _charOffset + 1 + endDelta).toString();
745
746 @override
747 int peek() {
748 if (_charOffset + 1 < _stringLength) {
749 return _string.codeUnitAt(_charOffset + 1);
750 }
751 return -1;
752 }
753 }
754
755 /**
756 * Instances of the class `Token` represent a token that was scanned from the in put. Each
757 * token knows which token follows it, acting as the head of a linked list of to kens.
758 */
759 class Token {
760 /**
761 * The offset from the beginning of the file to the first character in the tok en.
762 */
763 final int offset;
764
765 /**
766 * The previous token in the token stream.
767 */
768 Token previous;
769
770 /**
771 * The next token in the token stream.
772 */
773 Token _next;
774
775 /**
776 * The type of the token.
777 */
778 final TokenType type;
779
780 /**
781 * The lexeme represented by this token.
782 */
783 String _value;
784
785 /**
786 * Initialize a newly created token.
787 *
788 * @param type the token type (not `null`)
789 * @param offset the offset from the beginning of the file to the first charac ter in the token
790 */
791 Token.con1(TokenType type, int offset) : this.con2(type, offset, type.lexeme);
792
793 /**
794 * Initialize a newly created token.
795 *
796 * @param type the token type (not `null`)
797 * @param offset the offset from the beginning of the file to the first charac ter in the token
798 * @param value the lexeme represented by this token (not `null`)
799 */
800 Token.con2(this.type, this.offset, String value) {
801 this._value = StringUtilities.intern(value);
802 }
803
804 /**
805 * Return the offset from the beginning of the file to the character after las t character of the
806 * token.
807 *
808 * @return the offset from the beginning of the file to the first character af ter last character
809 * of the token
810 */
811 int get end => offset + length;
812
813 /**
814 * Return `true` if this token is a synthetic token. A synthetic token is a to ken that was
815 * introduced by the parser in order to recover from an error in the code. Syn thetic tokens always
816 * have a length of zero (`0`).
817 *
818 * @return `true` if this token is a synthetic token
819 */
820 bool get isSynthetic => length == 0;
821
822 /**
823 * Return the number of characters in the node's source range.
824 *
825 * @return the number of characters in the node's source range
826 */
827 int get length => lexeme.length;
828
829 /**
830 * Return the lexeme that represents this token.
831 *
832 * @return the lexeme (not `null`)
833 */
834 String get lexeme => _value;
835
836 /**
837 * Return the next token in the token stream.
838 *
839 * @return the next token in the token stream
840 */
841 Token get next => _next;
842
843 /**
844 * Set the next token in the token stream to the given token. This has the sid e-effect of setting
845 * this token to be the previous token for the given token.
846 *
847 * @param token the next token in the token stream
848 * @return the token that was passed in
849 */
850 Token setNext(Token token) {
851 _next = token;
852 token.previous = this;
853 return token;
854 }
855
856 @override
857 String toString() => lexeme;
858 }
859
860 /**
861 * The enumeration `TokenType` defines the types of tokens that can be returned by the
862 * scanner.
863 */
864 class TokenType extends Enum<TokenType> {
865 /**
866 * The type of the token that marks the end of the input.
867 */
868 static const TokenType EOF = const TokenType_EOF('EOF', 0, "");
869
870 static const TokenType EQ = const TokenType('EQ', 1, "=");
871
872 static const TokenType GT = const TokenType('GT', 2, ">");
873
874 static const TokenType LT_SLASH = const TokenType('LT_SLASH', 3, "</");
875
876 static const TokenType LT = const TokenType('LT', 4, "<");
877
878 static const TokenType SLASH_GT = const TokenType('SLASH_GT', 5, "/>");
879
880 static const TokenType COMMENT = const TokenType('COMMENT', 6, null);
881
882 static const TokenType DECLARATION = const TokenType('DECLARATION', 7, null);
883
884 static const TokenType DIRECTIVE = const TokenType('DIRECTIVE', 8, null);
885
886 static const TokenType STRING = const TokenType('STRING', 9, null);
887
888 static const TokenType TAG = const TokenType('TAG', 10, null);
889
890 static const TokenType TEXT = const TokenType('TEXT', 11, null);
891
892 static const List<TokenType> values = const [
893 EOF,
894 EQ,
895 GT,
896 LT_SLASH,
897 LT,
898 SLASH_GT,
899 COMMENT,
900 DECLARATION,
901 DIRECTIVE,
902 STRING,
903 TAG,
904 TEXT
905 ];
906
907 /**
908 * The lexeme that defines this type of token, or `null` if there is more than one possible
909 * lexeme for this type of token.
910 */
911 final String lexeme;
912
913 const TokenType(String name, int ordinal, this.lexeme) : super(name, ordinal);
914 }
915
916 class TokenType_EOF extends TokenType {
917 const TokenType_EOF(String name, int ordinal, String arg0)
918 : super(name, ordinal, arg0);
919
920 @override
921 String toString() => "-eof-";
922 }
923
924 /**
925 * Instances of the class `ToSourceVisitor` write a source representation of a v isited XML
926 * node (and all of it's children) to a writer.
927 */
928 class ToSourceVisitor implements XmlVisitor<Object> {
929 /**
930 * The writer to which the source is to be written.
931 */
932 final PrintWriter _writer;
933
934 /**
935 * Initialize a newly created visitor to write source code representing the vi sited nodes to the
936 * given writer.
937 *
938 * @param writer the writer to which the source is to be written
939 */
940 ToSourceVisitor(this._writer);
941
942 @override
943 Object visitHtmlScriptTagNode(HtmlScriptTagNode node) =>
944 visitXmlTagNode(node);
945
946 @override
947 Object visitHtmlUnit(HtmlUnit node) {
948 for (XmlTagNode child in node.tagNodes) {
949 _visit(child);
950 }
951 return null;
952 }
953
954 @override
955 Object visitXmlAttributeNode(XmlAttributeNode node) {
956 String name = node.name;
957 Token value = node.valueToken;
958 if (name.length == 0) {
959 _writer.print("__");
960 } else {
961 _writer.print(name);
962 }
963 _writer.print("=");
964 if (value == null) {
965 _writer.print("__");
966 } else {
967 _writer.print(value.lexeme);
968 }
969 return null;
970 }
971
972 @override
973 Object visitXmlTagNode(XmlTagNode node) {
974 _writer.print("<");
975 String tagName = node.tag;
976 _writer.print(tagName);
977 for (XmlAttributeNode attribute in node.attributes) {
978 _writer.print(" ");
979 _visit(attribute);
980 }
981 _writer.print(node.attributeEnd.lexeme);
982 if (node.closingTag != null) {
983 for (XmlTagNode child in node.tagNodes) {
984 _visit(child);
985 }
986 _writer.print("</");
987 _writer.print(tagName);
988 _writer.print(">");
989 }
990 return null;
991 }
992
993 /**
994 * Safely visit the given node.
995 *
996 * @param node the node to be visited
997 */
998 void _visit(XmlNode node) {
999 if (node != null) {
1000 node.accept(this);
1001 }
1002 }
1003 }
1004
1005 /**
1006 * Instances of `XmlAttributeNode` represent name/value pairs owned by an [XmlTa gNode].
1007 */
1008 class XmlAttributeNode extends XmlNode {
1009 /**
1010 * An empty list of XML attribute nodes.
1011 */
1012 static const List<XmlAttributeNode> EMPTY_LIST = const <XmlAttributeNode>[];
1013
1014 final Token _name;
1015
1016 final Token equals;
1017
1018 final Token _value;
1019
1020 List<XmlExpression> expressions = XmlExpression.EMPTY_ARRAY;
1021
1022 /**
1023 * Construct a new instance representing an XML attribute.
1024 *
1025 * @param name the name token (not `null`). This may be a zero length token if the attribute
1026 * is badly formed.
1027 * @param equals the equals sign or `null` if none
1028 * @param value the value token (not `null`)
1029 */
1030 XmlAttributeNode(this._name, this.equals, this._value);
1031
1032 @override
1033 Token get beginToken => _name;
1034
1035 @override
1036 Token get endToken => _value;
1037
1038 /**
1039 * Answer the attribute name. This may be a zero length string if the attribut e is badly formed.
1040 *
1041 * @return the name (not `null`)
1042 */
1043 String get name => _name.lexeme;
1044
1045 /**
1046 * Answer the attribute name token. This may be a zero length token if the att ribute is badly
1047 * formed.
1048 *
1049 * @return the name token (not `null`)
1050 */
1051 Token get nameToken => _name;
1052
1053 /**
1054 * Answer the lexeme for the value token without the leading and trailing quot es.
1055 *
1056 * @return the text or `null` if the value is not specified
1057 */
1058 String get text {
1059 if (_value == null) {
1060 return null;
1061 }
1062 //TODO (danrubel): replace HTML character encodings with the actual
1063 // characters
1064 String text = _value.lexeme;
1065 int len = text.length;
1066 if (len > 0) {
1067 if (text.codeUnitAt(0) == 0x22) {
1068 if (len > 1 && text.codeUnitAt(len - 1) == 0x22) {
1069 return text.substring(1, len - 1);
1070 } else {
1071 return text.substring(1);
1072 }
1073 } else if (text.codeUnitAt(0) == 0x27) {
1074 if (len > 1 && text.codeUnitAt(len - 1) == 0x27) {
1075 return text.substring(1, len - 1);
1076 } else {
1077 return text.substring(1);
1078 }
1079 }
1080 }
1081 return text;
1082 }
1083
1084 /**
1085 * Answer the offset of the value after the leading quote.
1086 *
1087 * @return the offset of the value, or `-1` if the value is not specified
1088 */
1089 int get textOffset {
1090 if (_value == null) {
1091 return -1;
1092 }
1093 String text = _value.lexeme;
1094 if (StringUtilities.startsWithChar(text, 0x22) ||
1095 StringUtilities.startsWithChar(text, 0x27)) {
1096 return _value.offset + 1;
1097 }
1098 return _value.offset;
1099 }
1100
1101 /**
1102 * Answer the attribute value token. A properly formed value will start and en d with matching
1103 * quote characters, but the value returned may not be properly formed.
1104 *
1105 * @return the value token or `null` if this represents a badly formed attribu te
1106 */
1107 Token get valueToken => _value;
1108
1109 @override
1110 accept(XmlVisitor visitor) => visitor.visitXmlAttributeNode(this);
1111
1112 @override
1113 void visitChildren(XmlVisitor visitor) {
1114 // no children to visit
1115 }
1116 }
1117
1118 /**
1119 * Instances of the class `XmlExpression` represent an abstract expression embed ded into
1120 * [XmlNode].
1121 */
1122 abstract class XmlExpression {
1123 /**
1124 * An empty list of expressions.
1125 */
1126 static const List<XmlExpression> EMPTY_ARRAY = const <XmlExpression>[];
1127
1128 /**
1129 * Return the offset of the character immediately following the last character of this
1130 * expression's source range. This is equivalent to `getOffset() + getLength() `.
1131 *
1132 * @return the offset of the character just past the expression's source range
1133 */
1134 int get end;
1135
1136 /**
1137 * Return the number of characters in the expression's source range.
1138 */
1139 int get length;
1140
1141 /**
1142 * Return the offset of the first character in the expression's source range.
1143 */
1144 int get offset;
1145
1146 /**
1147 * Check if the given offset belongs to the expression's source range.
1148 */
1149 bool contains(int offset) => this.offset <= offset && offset < end;
1150
1151 /**
1152 * Return the [Reference] at the given offset.
1153 *
1154 * @param offset the offset from the beginning of the file
1155 * @return the [Reference] at the given offset, maybe `null`
1156 */
1157 XmlExpression_Reference getReference(int offset);
1158 }
1159
1160 /**
1161 * The reference to the [Element].
1162 */
1163 class XmlExpression_Reference {
1164 Element element;
1165
1166 int offset = 0;
1167
1168 int length = 0;
1169
1170 XmlExpression_Reference(this.element, this.offset, this.length);
1171 }
1172
1173 /**
1174 * The abstract class `XmlNode` defines behavior common to all XML/HTML nodes.
1175 */
1176 abstract class XmlNode {
1177 /**
1178 * The parent of the node, or `null` if the node is the root of an AST structu re.
1179 */
1180 XmlNode _parent;
1181
1182 /**
1183 * The element associated with this node or `null` if the receiver is not reso lved.
1184 */
1185 Element _element;
1186
1187 /**
1188 * Return the first token included in this node's source range.
1189 *
1190 * @return the first token or `null` if none
1191 */
1192 Token get beginToken;
1193
1194 /**
1195 * Return the element associated with this node.
1196 *
1197 * @return the element or `null` if the receiver is not resolved
1198 */
1199 Element get element => _element;
1200
1201 /**
1202 * Set the element associated with this node.
1203 *
1204 * @param element the element
1205 */
1206 void set element(Element element) {
1207 this._element = element;
1208 }
1209
1210 /**
1211 * Return the offset of the character immediately following the last character of this node's
1212 * source range. This is equivalent to `node.getOffset() + node.getLength()`. For an html
1213 * unit this will be equal to the length of the unit's source.
1214 *
1215 * @return the offset of the character just past the node's source range
1216 */
1217 int get end => offset + length;
1218
1219 /**
1220 * Return the last token included in this node's source range.
1221 *
1222 * @return the last token or `null` if none
1223 */
1224 Token get endToken;
1225
1226 /**
1227 * Return the number of characters in the node's source range.
1228 *
1229 * @return the number of characters in the node's source range
1230 */
1231 int get length {
1232 Token beginToken = this.beginToken;
1233 Token endToken = this.endToken;
1234 if (beginToken == null || endToken == null) {
1235 return -1;
1236 }
1237 return endToken.offset + endToken.length - beginToken.offset;
1238 }
1239
1240 /**
1241 * Return the offset from the beginning of the file to the first character in the node's source
1242 * range.
1243 *
1244 * @return the offset from the beginning of the file to the first character in the node's source
1245 * range
1246 */
1247 int get offset {
1248 Token beginToken = this.beginToken;
1249 if (beginToken == null) {
1250 return -1;
1251 }
1252 return this.beginToken.offset;
1253 }
1254
1255 /**
1256 * Return this node's parent node, or `null` if this node is the root of an AS T structure.
1257 *
1258 * Note that the relationship between an AST node and its parent node may chan ge over the lifetime
1259 * of a node.
1260 *
1261 * @return the parent of this node, or `null` if none
1262 */
1263 XmlNode get parent => _parent;
1264
1265 /**
1266 * Set the parent of this node to the given node.
1267 *
1268 * @param newParent the node that is to be made the parent of this node
1269 */
1270 void set parent(XmlNode newParent) {
1271 XmlNode current = newParent;
1272 while (current != null) {
1273 if (identical(current, this)) {
1274 AnalysisEngine.instance.logger.logError(
1275 "Circular structure while setting an XML node's parent",
1276 new CaughtException(
1277 new ArgumentError(_buildRecursiveStructureMessage(newParent)),
1278 null));
1279 return;
1280 }
1281 current = current.parent;
1282 }
1283 _parent = newParent;
1284 }
1285
1286 /**
1287 * Use the given visitor to visit this node.
1288 *
1289 * @param visitor the visitor that will visit this node
1290 * @return the value returned by the visitor as a result of visiting this node
1291 */
1292 accept(XmlVisitor visitor);
1293
1294 /**
1295 * Make this node the parent of the given child node.
1296 *
1297 * @param child the node that will become a child of this node
1298 * @return the node that was made a child of this node
1299 */
1300 XmlNode becomeParentOf(XmlNode child) {
1301 if (child != null) {
1302 XmlNode node = child;
1303 node.parent = this;
1304 }
1305 return child;
1306 }
1307
1308 /**
1309 * Make this node the parent of the given child nodes.
1310 *
1311 * @param children the nodes that will become the children of this node
1312 * @param ifEmpty the (empty) nodes to return if "children" is empty
1313 * @return the nodes that were made children of this node
1314 */
1315 List becomeParentOfAll(List children, {List ifEmpty}) {
1316 if (children == null || children.isEmpty) {
1317 if (ifEmpty != null) {
1318 return ifEmpty;
1319 }
1320 }
1321 if (children != null) {
1322 children.forEach((XmlNode node) {
1323 node.parent = this;
1324 });
1325 }
1326 return children;
1327 }
1328
1329 @override
1330 String toString() {
1331 PrintStringWriter writer = new PrintStringWriter();
1332 accept(new ToSourceVisitor(writer));
1333 return writer.toString();
1334 }
1335
1336 /**
1337 * Use the given visitor to visit all of the children of this node. The childr en will be visited
1338 * in source order.
1339 *
1340 * @param visitor the visitor that will be used to visit the children of this node
1341 */
1342 void visitChildren(XmlVisitor visitor);
1343
1344 /**
1345 * This method exists for debugging purposes only.
1346 */
1347 void _appendIdentifier(StringBuffer buffer, XmlNode node) {
1348 if (node is XmlTagNode) {
1349 buffer.write(node.tag);
1350 } else if (node is XmlAttributeNode) {
1351 buffer.write(node.name);
1352 } else {
1353 buffer.write("htmlUnit");
1354 }
1355 }
1356
1357 /**
1358 * This method exists for debugging purposes only.
1359 */
1360 String _buildRecursiveStructureMessage(XmlNode newParent) {
1361 StringBuffer buffer = new StringBuffer();
1362 buffer.write("Attempt to create recursive structure: ");
1363 XmlNode current = newParent;
1364 while (current != null) {
1365 if (!identical(current, newParent)) {
1366 buffer.write(" -> ");
1367 }
1368 if (identical(current, this)) {
1369 buffer.writeCharCode(0x2A);
1370 _appendIdentifier(buffer, current);
1371 buffer.writeCharCode(0x2A);
1372 } else {
1373 _appendIdentifier(buffer, current);
1374 }
1375 current = current.parent;
1376 }
1377 return buffer.toString();
1378 }
1379 }
1380
1381 /**
1382 * Instances of the class `XmlParser` are used to parse tokens into a AST struct ure comprised
1383 * of [XmlNode]s.
1384 */
1385 class XmlParser {
1386 /**
1387 * The source being parsed.
1388 */
1389 final Source source;
1390
1391 /**
1392 * The next token to be parsed.
1393 */
1394 Token _currentToken;
1395
1396 /**
1397 * Construct a parser for the specified source.
1398 *
1399 * @param source the source being parsed
1400 */
1401 XmlParser(this.source);
1402
1403 /**
1404 * Answer the current token.
1405 *
1406 * @return the current token
1407 */
1408 Token get currentToken => _currentToken;
1409
1410 /**
1411 * Create a node representing an attribute.
1412 *
1413 * @param name the name of the attribute
1414 * @param equals the equals sign, or `null` if there is no value
1415 * @param value the value of the attribute
1416 * @return the node that was created
1417 */
1418 XmlAttributeNode createAttributeNode(Token name, Token equals, Token value) =>
1419 new XmlAttributeNode(name, equals, value);
1420
1421 /**
1422 * Create a node representing a tag.
1423 *
1424 * @param nodeStart the token marking the beginning of the tag
1425 * @param tag the name of the tag
1426 * @param attributes the attributes in the tag
1427 * @param attributeEnd the token terminating the region where attributes can b e
1428 * @param tagNodes the children of the tag
1429 * @param contentEnd the token that starts the closing tag
1430 * @param closingTag the name of the tag that occurs in the closing tag
1431 * @param nodeEnd the last token in the tag
1432 * @return the node that was created
1433 */
1434 XmlTagNode createTagNode(
1435 Token nodeStart,
1436 Token tag,
1437 List<XmlAttributeNode> attributes,
1438 Token attributeEnd,
1439 List<XmlTagNode> tagNodes,
1440 Token contentEnd,
1441 Token closingTag,
1442 Token nodeEnd) =>
1443 new XmlTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes,
1444 contentEnd, closingTag, nodeEnd);
1445
1446 /**
1447 * Answer `true` if the specified tag is self closing and thus should never ha ve content or
1448 * child tag nodes.
1449 *
1450 * @param tag the tag (not `null`)
1451 * @return `true` if self closing
1452 */
1453 bool isSelfClosing(Token tag) => false;
1454
1455 /**
1456 * Parse the entire token stream and in the process, advance the current token to the end of the
1457 * token stream.
1458 *
1459 * @return the list of tag nodes found (not `null`, contains no `null`)
1460 */
1461 List<XmlTagNode> parseTopTagNodes(Token firstToken) {
1462 _currentToken = firstToken;
1463 List<XmlTagNode> tagNodes = new List<XmlTagNode>();
1464 TokenType type = _currentToken.type;
1465 while (type != TokenType.EOF) {
1466 if (type == TokenType.LT) {
1467 tagNodes.add(_parseTagNode());
1468 } else if (type == TokenType.DECLARATION ||
1469 type == TokenType.DIRECTIVE ||
1470 type == TokenType.COMMENT) {
1471 // ignored tokens
1472 _currentToken = _currentToken.next;
1473 } else {
1474 _reportUnexpectedToken();
1475 _currentToken = _currentToken.next;
1476 }
1477 type = _currentToken.type;
1478 }
1479 return tagNodes;
1480 }
1481
1482 /**
1483 * Insert a synthetic token of the specified type before the current token
1484 *
1485 * @param type the type of token to be inserted (not `null`)
1486 * @return the synthetic token that was inserted (not `null`)
1487 */
1488 Token _insertSyntheticToken(TokenType type) {
1489 Token token = new Token.con2(type, _currentToken.offset, "");
1490 _currentToken.previous.setNext(token);
1491 token.setNext(_currentToken);
1492 return token;
1493 }
1494
1495 /**
1496 * Parse the token stream for an attribute. This method advances the current t oken over the
1497 * attribute, but should not be called if the [currentToken] is not [TokenType .TAG].
1498 *
1499 * @return the attribute (not `null`)
1500 */
1501 XmlAttributeNode _parseAttribute() {
1502 // Assume the current token is a tag
1503 Token name = _currentToken;
1504 _currentToken = _currentToken.next;
1505 // Equals sign
1506 Token equals;
1507 if (_currentToken.type == TokenType.EQ) {
1508 equals = _currentToken;
1509 _currentToken = _currentToken.next;
1510 } else {
1511 _reportUnexpectedToken();
1512 equals = _insertSyntheticToken(TokenType.EQ);
1513 }
1514 // String value
1515 Token value;
1516 if (_currentToken.type == TokenType.STRING) {
1517 value = _currentToken;
1518 _currentToken = _currentToken.next;
1519 } else {
1520 _reportUnexpectedToken();
1521 value = _insertSyntheticToken(TokenType.STRING);
1522 }
1523 return createAttributeNode(name, equals, value);
1524 }
1525
1526 /**
1527 * Parse the stream for a sequence of attributes. This method advances the cur rent token to the
1528 * next [TokenType.GT], [TokenType.SLASH_GT], or [TokenType.EOF].
1529 *
1530 * @return a collection of zero or more attributes (not `null`, contains no `n ull`s)
1531 */
1532 List<XmlAttributeNode> _parseAttributes() {
1533 TokenType type = _currentToken.type;
1534 if (type == TokenType.GT ||
1535 type == TokenType.SLASH_GT ||
1536 type == TokenType.EOF) {
1537 return XmlTagNode.NO_ATTRIBUTES;
1538 }
1539 List<XmlAttributeNode> attributes = new List<XmlAttributeNode>();
1540 while (type != TokenType.GT &&
1541 type != TokenType.SLASH_GT &&
1542 type != TokenType.EOF) {
1543 if (type == TokenType.TAG) {
1544 attributes.add(_parseAttribute());
1545 } else {
1546 _reportUnexpectedToken();
1547 _currentToken = _currentToken.next;
1548 }
1549 type = _currentToken.type;
1550 }
1551 return attributes;
1552 }
1553
1554 /**
1555 * Parse the stream for a sequence of tag nodes existing within a parent tag n ode. This method
1556 * advances the current token to the next [TokenType.LT_SLASH] or [TokenType.E OF].
1557 *
1558 * @return a list of nodes (not `null`, contains no `null`s)
1559 */
1560 List<XmlTagNode> _parseChildTagNodes() {
1561 TokenType type = _currentToken.type;
1562 if (type == TokenType.LT_SLASH || type == TokenType.EOF) {
1563 return XmlTagNode.NO_TAG_NODES;
1564 }
1565 List<XmlTagNode> nodes = new List<XmlTagNode>();
1566 while (type != TokenType.LT_SLASH && type != TokenType.EOF) {
1567 if (type == TokenType.LT) {
1568 nodes.add(_parseTagNode());
1569 } else if (type == TokenType.COMMENT) {
1570 // ignored token
1571 _currentToken = _currentToken.next;
1572 } else {
1573 _reportUnexpectedToken();
1574 _currentToken = _currentToken.next;
1575 }
1576 type = _currentToken.type;
1577 }
1578 return nodes;
1579 }
1580
1581 /**
1582 * Parse the token stream for the next tag node. This method advances current token over the
1583 * parsed tag node, but should only be called if the current token is [TokenTy pe.LT]
1584 *
1585 * @return the tag node or `null` if none found
1586 */
1587 XmlTagNode _parseTagNode() {
1588 // Assume that the current node is a tag node start TokenType.LT
1589 Token nodeStart = _currentToken;
1590 _currentToken = _currentToken.next;
1591 // Get the tag or create a synthetic tag and report an error
1592 Token tag;
1593 if (_currentToken.type == TokenType.TAG) {
1594 tag = _currentToken;
1595 _currentToken = _currentToken.next;
1596 } else {
1597 _reportUnexpectedToken();
1598 tag = _insertSyntheticToken(TokenType.TAG);
1599 }
1600 // Parse the attributes
1601 List<XmlAttributeNode> attributes = _parseAttributes();
1602 // Token ending attribute list
1603 Token attributeEnd;
1604 if (_currentToken.type == TokenType.GT ||
1605 _currentToken.type == TokenType.SLASH_GT) {
1606 attributeEnd = _currentToken;
1607 _currentToken = _currentToken.next;
1608 } else {
1609 _reportUnexpectedToken();
1610 attributeEnd = _insertSyntheticToken(TokenType.SLASH_GT);
1611 }
1612 // If the node has no children, then return the node
1613 if (attributeEnd.type == TokenType.SLASH_GT || isSelfClosing(tag)) {
1614 return createTagNode(nodeStart, tag, attributes, attributeEnd,
1615 XmlTagNode.NO_TAG_NODES, _currentToken, null, attributeEnd);
1616 }
1617 // Parse the child tag nodes
1618 List<XmlTagNode> tagNodes = _parseChildTagNodes();
1619 // Token ending child tag nodes
1620 Token contentEnd;
1621 if (_currentToken.type == TokenType.LT_SLASH) {
1622 contentEnd = _currentToken;
1623 _currentToken = _currentToken.next;
1624 } else {
1625 // TODO (danrubel): handle self closing HTML elements by inserting
1626 // synthetic tokens but not reporting an error
1627 _reportUnexpectedToken();
1628 contentEnd = _insertSyntheticToken(TokenType.LT_SLASH);
1629 }
1630 // Closing tag
1631 Token closingTag;
1632 if (_currentToken.type == TokenType.TAG) {
1633 closingTag = _currentToken;
1634 _currentToken = _currentToken.next;
1635 } else {
1636 _reportUnexpectedToken();
1637 closingTag = _insertSyntheticToken(TokenType.TAG);
1638 }
1639 // Token ending node
1640 Token nodeEnd;
1641 if (_currentToken.type == TokenType.GT) {
1642 nodeEnd = _currentToken;
1643 _currentToken = _currentToken.next;
1644 } else {
1645 _reportUnexpectedToken();
1646 nodeEnd = _insertSyntheticToken(TokenType.GT);
1647 }
1648 return createTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes,
1649 contentEnd, closingTag, nodeEnd);
1650 }
1651
1652 /**
1653 * Report the current token as unexpected
1654 */
1655 void _reportUnexpectedToken() {
1656 // TODO (danrubel): report unexpected token
1657 }
1658 }
1659
1660 /**
1661 * Instances of `XmlTagNode` represent XML or HTML elements such as `` and
1662 * `<body foo="bar"> ... </body>`.
1663 */
1664 class XmlTagNode extends XmlNode {
1665 /**
1666 * Constant representing empty list of attributes.
1667 */
1668 static List<XmlAttributeNode> NO_ATTRIBUTES =
1669 new UnmodifiableListView(new List<XmlAttributeNode>());
1670
1671 /**
1672 * Constant representing empty list of tag nodes.
1673 */
1674 static List<XmlTagNode> NO_TAG_NODES =
1675 new UnmodifiableListView(new List<XmlTagNode>());
1676
1677 /**
1678 * The starting [TokenType.LT] token (not `null`).
1679 */
1680 final Token nodeStart;
1681
1682 /**
1683 * The [TokenType.TAG] token after the starting '&lt;' (not `null`).
1684 */
1685 final Token _tag;
1686
1687 /**
1688 * The attributes contained by the receiver (not `null`, contains no `null`s).
1689 */
1690 List<XmlAttributeNode> _attributes;
1691
1692 /**
1693 * The [TokenType.GT] or [TokenType.SLASH_GT] token after the attributes (not
1694 * `null`). The token may be the same token as [nodeEnd] if there are no child
1695 * [tagNodes].
1696 */
1697 final Token attributeEnd;
1698
1699 /**
1700 * The tag nodes contained in the receiver (not `null`, contains no `null`s).
1701 */
1702 List<XmlTagNode> _tagNodes;
1703
1704 /**
1705 * The token (not `null`) after the content, which may be
1706 * * (1) [TokenType.LT_SLASH] for nodes with open and close tags, or
1707 * * (2) the [TokenType.LT] nodeStart of the next sibling node if this node is self
1708 * closing or the attributeEnd is [TokenType.SLASH_GT], or
1709 * * (3) [TokenType.EOF] if the node does not have a closing tag and is the la st node in
1710 * the stream [TokenType.LT_SLASH] token after the content, or `null` if there is no
1711 * content and the attributes ended with [TokenType.SLASH_GT].
1712 */
1713 final Token contentEnd;
1714
1715 /**
1716 * The closing [TokenType.TAG] after the child elements or `null` if there is no
1717 * content and the attributes ended with [TokenType.SLASH_GT]
1718 */
1719 final Token closingTag;
1720
1721 /**
1722 * The ending [TokenType.GT] or [TokenType.SLASH_GT] token (not `null`).
1723 */
1724 final Token nodeEnd;
1725
1726 /**
1727 * The expressions that are embedded in the tag's content.
1728 */
1729 List<XmlExpression> expressions = XmlExpression.EMPTY_ARRAY;
1730
1731 /**
1732 * Construct a new instance representing an XML or HTML element
1733 *
1734 * @param nodeStart the starting [TokenType.LT] token (not `null`)
1735 * @param tag the [TokenType.TAG] token after the starting '&lt;' (not `null`) .
1736 * @param attributes the attributes associated with this element or [NO_ATTRIB UTES] (not
1737 * `null`, contains no `null`s)
1738 * @param attributeEnd The [TokenType.GT] or [TokenType.SLASH_GT] token after the
1739 * attributes (not `null`). The token may be the same token as [nodeE nd] if
1740 * there are no child [tagNodes].
1741 * @param tagNodes child tag nodes of the receiver or [NO_TAG_NODES] (not `nul l`,
1742 * contains no `null`s)
1743 * @param contentEnd the token (not `null`) after the content, which may be
1744 * * (1) [TokenType.LT_SLASH] for nodes with open and close tags, or
1745 * * (2) the [TokenType.LT] nodeStart of the next sibling node if thi s node is
1746 * self closing or the attributeEnd is [TokenType.SLASH_GT], or
1747 * * (3) [TokenType.EOF] if the node does not have a closing tag and is the last
1748 * node in the stream [TokenType.LT_SLASH] token after the content, o r `null`
1749 * if there is no content and the attributes ended with [TokenType.SL ASH_GT].
1750 * @param closingTag the closing [TokenType.TAG] after the child elements or ` null` if
1751 * there is no content and the attributes ended with [TokenType.SLASH _GT]
1752 * @param nodeEnd the ending [TokenType.GT] or [TokenType.SLASH_GT] token (not
1753 * `null`)
1754 */
1755 XmlTagNode(
1756 this.nodeStart,
1757 this._tag,
1758 List<XmlAttributeNode> attributes,
1759 this.attributeEnd,
1760 List<XmlTagNode> tagNodes,
1761 this.contentEnd,
1762 this.closingTag,
1763 this.nodeEnd) {
1764 this._attributes = becomeParentOfAll(attributes, ifEmpty: NO_ATTRIBUTES);
1765 this._tagNodes = becomeParentOfAll(tagNodes, ifEmpty: NO_TAG_NODES);
1766 }
1767
1768 /**
1769 * Answer the receiver's attributes. Callers should not manipulate the returne d list to edit the
1770 * AST structure.
1771 *
1772 * @return the attributes (not `null`, contains no `null`s)
1773 */
1774 List<XmlAttributeNode> get attributes => _attributes;
1775
1776 @override
1777 Token get beginToken => nodeStart;
1778
1779 /**
1780 * Return a string representing the content contained in the receiver. This
1781 * includes the textual representation of any child tag nodes ([getTagNodes]).
1782 * Whitespace between '&lt;', '&lt;/', and '>', '/>' is discarded, but all
1783 * other whitespace is preserved.
1784 */
1785 String get content {
1786 Token token = attributeEnd.next;
1787 if (identical(token, contentEnd)) {
1788 return "";
1789 }
1790 // TODO(danrubel) Handle CDATA and replace HTML character encodings with
1791 // the actual characters.
1792 String content = token.lexeme;
1793 token = token.next;
1794 if (identical(token, contentEnd)) {
1795 return content;
1796 }
1797 StringBuffer buffer = new StringBuffer();
1798 buffer.write(content);
1799 while (!identical(token, contentEnd)) {
1800 buffer.write(token.lexeme);
1801 token = token.next;
1802 }
1803 return buffer.toString();
1804 }
1805
1806 @override
1807 Token get endToken {
1808 if (nodeEnd != null) {
1809 return nodeEnd;
1810 }
1811 if (closingTag != null) {
1812 return closingTag;
1813 }
1814 if (contentEnd != null) {
1815 return contentEnd;
1816 }
1817 if (!_tagNodes.isEmpty) {
1818 return _tagNodes[_tagNodes.length - 1].endToken;
1819 }
1820 if (attributeEnd != null) {
1821 return attributeEnd;
1822 }
1823 if (!_attributes.isEmpty) {
1824 return _attributes[_attributes.length - 1].endToken;
1825 }
1826 return _tag;
1827 }
1828
1829 /**
1830 * Answer the tag name after the starting '&lt;'.
1831 *
1832 * @return the tag name (not `null`)
1833 */
1834 String get tag => _tag.lexeme;
1835
1836 /**
1837 * Answer the tag nodes contained in the receiver. Callers should not manipula te the returned list
1838 * to edit the AST structure.
1839 *
1840 * @return the children (not `null`, contains no `null`s)
1841 */
1842 List<XmlTagNode> get tagNodes => _tagNodes;
1843
1844 /**
1845 * Answer the [TokenType.TAG] token after the starting '&lt;'.
1846 *
1847 * @return the token (not `null`)
1848 */
1849 Token get tagToken => _tag;
1850
1851 @override
1852 accept(XmlVisitor visitor) => visitor.visitXmlTagNode(this);
1853
1854 /**
1855 * Answer the attribute with the specified name.
1856 *
1857 * @param name the attribute name
1858 * @return the attribute or `null` if no matching attribute is found
1859 */
1860 XmlAttributeNode getAttribute(String name) {
1861 for (XmlAttributeNode attribute in _attributes) {
1862 if (attribute.name == name) {
1863 return attribute;
1864 }
1865 }
1866 return null;
1867 }
1868
1869 /**
1870 * Find the attribute with the given name (see [getAttribute] and answer the l exeme
1871 * for the attribute's value token without the leading and trailing quotes (se e
1872 * [XmlAttributeNode.getText]).
1873 *
1874 * @param name the attribute name
1875 * @return the attribute text or `null` if no matching attribute is found
1876 */
1877 String getAttributeText(String name) {
1878 XmlAttributeNode attribute = getAttribute(name);
1879 return attribute != null ? attribute.text : null;
1880 }
1881
1882 @override
1883 void visitChildren(XmlVisitor visitor) {
1884 for (XmlAttributeNode node in _attributes) {
1885 node.accept(visitor);
1886 }
1887 for (XmlTagNode node in _tagNodes) {
1888 node.accept(visitor);
1889 }
1890 }
1891 }
1892
1893 /**
1894 * The interface `XmlVisitor` defines the behavior of objects that can be used t o visit an
1895 * [XmlNode] structure.
1896 */
1897 abstract class XmlVisitor<R> {
1898 R visitHtmlScriptTagNode(HtmlScriptTagNode node);
1899
1900 R visitHtmlUnit(HtmlUnit htmlUnit);
1901
1902 R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode);
1903
1904 R visitXmlTagNode(XmlTagNode xmlTagNode);
1905 }
OLDNEW
« no previous file with comments | « pkg/analyzer/lib/src/generated/error.dart ('k') | pkg/analyzer/lib/src/generated/java_core.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698