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

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

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

Powered by Google App Engine
This is Rietveld 408576698