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