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

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

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

Powered by Google App Engine
This is Rietveld 408576698