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