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

Unified Diff: pkg/analyzer/lib/src/generated/html.dart

Issue 257773008: New analyzer snapshot, based on r35422. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Update pubspec.yaml Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/analyzer/lib/src/generated/html.dart
diff --git a/pkg/analyzer/lib/src/generated/html.dart b/pkg/analyzer/lib/src/generated/html.dart
index 8438a541bf140cd50c68b638e0ed187569e2f7e1..2f1e762721903bb704c15f3f6bfab3acb022ca71 100644
--- a/pkg/analyzer/lib/src/generated/html.dart
+++ b/pkg/analyzer/lib/src/generated/html.dart
@@ -19,1635 +19,1522 @@ import 'element.dart';
import 'engine.dart' show AnalysisEngine, AngularHtmlUnitResolver, ExpressionVisitor;
/**
- * Instances of the class `Token` represent a token that was scanned from the input. Each
- * token knows which token follows it, acting as the head of a linked list of tokens.
+ * Instances of the class `XmlExpression` represent an abstract expression embedded into
+ * [XmlNode].
*/
-class Token {
- /**
- * The offset from the beginning of the file to the first character in the token.
- */
- final int offset;
-
- /**
- * The previous token in the token stream.
- */
- Token previous;
-
- /**
- * The next token in the token stream.
- */
- Token _next;
-
- /**
- * The type of the token.
- */
- final TokenType type;
-
- /**
- * The lexeme represented by this token.
- */
- String _value;
-
- /**
- * Initialize a newly created token.
- *
- * @param type the token type (not `null`)
- * @param offset the offset from the beginning of the file to the first character in the token
- */
- Token.con1(TokenType type, int offset) : this.con2(type, offset, type.lexeme);
-
- /**
- * Initialize a newly created token.
- *
- * @param type the token type (not `null`)
- * @param offset the offset from the beginning of the file to the first character in the token
- * @param value the lexeme represented by this token (not `null`)
- */
- Token.con2(this.type, this.offset, String value) {
- this._value = StringUtilities.intern(value);
- }
-
+abstract class XmlExpression {
/**
- * Return the offset from the beginning of the file to the character after last character of the
- * token.
- *
- * @return the offset from the beginning of the file to the first character after last character
- * of the token
+ * An empty array of expressions.
*/
- int get end => offset + length;
+ static List<XmlExpression> EMPTY_ARRAY = new List<XmlExpression>(0);
/**
- * Return the number of characters in the node's source range.
- *
- * @return the number of characters in the node's source range
+ * Check if the given offset belongs to the expression's source range.
*/
- int get length => lexeme.length;
+ bool contains(int offset) => this.offset <= offset && offset < end;
/**
- * Return the lexeme that represents this token.
+ * Return the offset of the character immediately following the last character of this
+ * expression's source range. This is equivalent to `getOffset() + getLength()`.
*
- * @return the lexeme (not `null`)
+ * @return the offset of the character just past the expression's source range
*/
- String get lexeme => _value;
+ int get end;
/**
- * Return the next token in the token stream.
- *
- * @return the next token in the token stream
+ * Return the number of characters in the expression's source range.
*/
- Token get next => _next;
+ int get length;
/**
- * Return `true` if this token is a synthetic token. A synthetic token is a token that was
- * introduced by the parser in order to recover from an error in the code. Synthetic tokens always
- * have a length of zero (`0`).
- *
- * @return `true` if this token is a synthetic token
+ * Return the offset of the first character in the expression's source range.
*/
- bool get isSynthetic => length == 0;
+ int get offset;
/**
- * Set the next token in the token stream to the given token. This has the side-effect of setting
- * this token to be the previous token for the given token.
+ * Return the [Reference] at the given offset.
*
- * @param token the next token in the token stream
- * @return the token that was passed in
+ * @param offset the offset from the beginning of the file
+ * @return the [Reference] at the given offset, maybe `null`
*/
- Token setNext(Token token) {
- _next = token;
- token.previous = this;
- return token;
- }
-
- @override
- String toString() => lexeme;
+ XmlExpression_Reference getReference(int offset);
}
/**
- * Implementation of [XmlExpression] for an [Expression] embedded without any wrapping
- * characters.
+ * The reference to the [Element].
*/
-class RawXmlExpression extends XmlExpression {
- final Expression expression;
-
- RawXmlExpression(this.expression);
-
- @override
- int get end => expression.end;
+class XmlExpression_Reference {
+ Element element;
- @override
- int get length => expression.length;
+ int offset = 0;
- @override
- int get offset => expression.offset;
+ int length = 0;
- @override
- XmlExpression_Reference getReference(int offset) {
- AstNode node = new NodeLocator.con1(offset).searchWithin(expression);
- if (node != null) {
- Element element = ElementLocator.locate(node);
- return new XmlExpression_Reference(element, node.offset, node.length);
- }
- return null;
+ XmlExpression_Reference(Element element, int offset, int length) {
+ this.element = element;
+ this.offset = offset;
+ this.length = length;
}
}
/**
- * Instances of the class `RecursiveXmlVisitor` implement an XML visitor that will recursively
- * visit all of the nodes in an XML structure. For example, using an instance of this class to visit
- * a [XmlTagNode] will also cause all of the contained [XmlAttributeNode]s and
- * [XmlTagNode]s to be visited.
- *
- * Subclasses that override a visit method must either invoke the overridden visit method or must
- * explicitly ask the visited node to visit its children. Failure to do so will cause the children
- * of the visited node to not be visited.
+ * Instances of the class `SimpleXmlVisitor` implement an AST visitor that will do nothing
+ * when visiting an AST node. It is intended to be a superclass for classes that use the visitor
+ * pattern primarily as a dispatch mechanism (and hence don't need to recursively visit a whole
+ * structure) and that only need to visit a small number of node types.
*/
-class RecursiveXmlVisitor<R> implements XmlVisitor<R> {
+class SimpleXmlVisitor<R> implements XmlVisitor<R> {
@override
- R visitHtmlScriptTagNode(HtmlScriptTagNode node) {
- node.visitChildren(this);
- return null;
- }
+ R visitHtmlScriptTagNode(HtmlScriptTagNode node) => null;
@override
- R visitHtmlUnit(HtmlUnit node) {
- node.visitChildren(this);
- return null;
- }
+ R visitHtmlUnit(HtmlUnit htmlUnit) => null;
@override
- R visitXmlAttributeNode(XmlAttributeNode node) {
- node.visitChildren(this);
- return null;
- }
+ R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode) => null;
@override
- R visitXmlTagNode(XmlTagNode node) {
- node.visitChildren(this);
- return null;
- }
+ R visitXmlTagNode(XmlTagNode xmlTagNode) => null;
}
/**
- * Utilities locating [Expression]s and [Element]s in [HtmlUnit].
+ * The abstract class `AbstractScanner` implements a scanner for HTML code. Subclasses are
+ * required to implement the interface used to access the characters being scanned.
*/
-class HtmlUnitUtils {
+abstract class AbstractScanner {
+ static List<String> _NO_PASS_THROUGH_ELEMENTS = <String> [];
+
/**
- * Returns the [XmlAttributeNode] that is part of the given [HtmlUnit] and encloses
- * the given offset.
+ * The source being scanned.
*/
- static XmlAttributeNode getAttributeNode(HtmlUnit htmlUnit, int offset) {
- if (htmlUnit == null) {
- return null;
- }
- List<XmlAttributeNode> result = [null];
- try {
- htmlUnit.accept(new RecursiveXmlVisitor_HtmlUnitUtils_getAttributeNode(offset, result));
- } on HtmlUnitUtils_FoundAttributeNodeError catch (e) {
- return result[0];
- }
- return null;
- }
+ final Source source;
/**
- * Returns the best [Element] of the given [Expression].
+ * The token pointing to the head of the linked list of tokens.
*/
- static Element getElement(Expression expression) {
- if (expression == null) {
- return null;
- }
- return ElementLocator.locate(expression);
- }
+ Token _tokens;
/**
- * Returns the [Element] of the [Expression] in the given [HtmlUnit], enclosing
- * the given offset.
+ * The last token that was scanned.
*/
- static Element getElementAtOffset(HtmlUnit htmlUnit, int offset) {
- Expression expression = getExpression(htmlUnit, offset);
- return getElement(expression);
- }
+ Token _tail;
/**
- * Returns the [Element] to open when requested at the given [Expression].
+ * A list containing the offsets of the first character of each line in the source code.
*/
- static Element getElementToOpen(HtmlUnit htmlUnit, Expression expression) {
- Element element = getElement(expression);
- {
- AngularElement angularElement = AngularHtmlUnitResolver.getAngularElement(element);
- if (angularElement != null) {
- return angularElement;
- }
- }
- return element;
- }
+ List<int> _lineStarts = new List<int>();
/**
- * Returns the [XmlTagNode] that is part of the given [HtmlUnit] and encloses the
- * given offset.
+ * An array of element tags for which the content between tags should be consider a single token.
*/
- static XmlTagNode getEnclosingTagNode(HtmlUnit htmlUnit, int offset) {
- if (htmlUnit == null) {
- return null;
- }
- List<XmlTagNode> result = [null];
- try {
- htmlUnit.accept(new RecursiveXmlVisitor_HtmlUnitUtils_getEnclosingTagNode(offset, result));
- } on HtmlUnitUtils_FoundTagNodeError catch (e) {
- return result[0];
- }
- return null;
- }
+ List<String> _passThroughElements = _NO_PASS_THROUGH_ELEMENTS;
/**
- * Returns the [Expression] that is part of the given [HtmlUnit] and encloses the
- * given offset.
+ * Initialize a newly created scanner.
+ *
+ * @param source the source being scanned
*/
- static Expression getExpression(HtmlUnit htmlUnit, int offset) {
- if (htmlUnit == null) {
- return null;
- }
- List<Expression> result = [null];
- try {
- // TODO(scheglov) this code is very Angular specific
- htmlUnit.accept(new ExpressionVisitor_HtmlUnitUtils_getExpression(offset, result));
- } on HtmlUnitUtils_FoundExpressionError catch (e) {
- return result[0];
- }
- return null;
+ AbstractScanner(this.source) {
+ _tokens = new Token.con1(TokenType.EOF, -1);
+ _tokens.setNext(_tokens);
+ _tail = _tokens;
+ recordStartOfLine();
}
/**
- * Returns the [XmlTagNode] that is part of the given [HtmlUnit] and its open or
- * closing tag name encloses the given offset.
- */
- static XmlTagNode getTagNode(HtmlUnit htmlUnit, int offset) {
- XmlTagNode node = getEnclosingTagNode(htmlUnit, offset);
- // do we have an enclosing tag at all?
- if (node == null) {
- return null;
- }
- // is "offset" in the open tag?
- Token openTag = node.tagToken;
- if (openTag.offset <= offset && offset <= openTag.end) {
- return node;
- }
- // is "offset" in the open tag?
- Token closeTag = node.closingTag;
- if (closeTag != null && closeTag.offset <= offset && offset <= closeTag.end) {
- return node;
- }
- // not on a tag name
- return null;
- }
-
- /**
- * Returns the [Expression] that is part of the given root [AstNode] and encloses the
- * given offset.
- */
- static Expression _getExpressionAt(AstNode root, int offset) {
- if (root.offset <= offset && offset <= root.end) {
- AstNode dartNode = new NodeLocator.con1(offset).searchWithin(root);
- if (dartNode is Expression) {
- return dartNode;
- }
- }
- return null;
- }
-}
-
-class HtmlUnitUtils_FoundAttributeNodeError extends Error {
-}
-
-class HtmlUnitUtils_FoundExpressionError extends Error {
-}
-
-class HtmlUnitUtils_FoundTagNodeError extends Error {
-}
-
-class RecursiveXmlVisitor_HtmlUnitUtils_getAttributeNode extends RecursiveXmlVisitor<Object> {
- int offset = 0;
-
- List<XmlAttributeNode> result;
-
- RecursiveXmlVisitor_HtmlUnitUtils_getAttributeNode(this.offset, this.result) : super();
-
- @override
- Object visitXmlAttributeNode(XmlAttributeNode node) {
- Token nameToken = node.nameToken;
- if (nameToken.offset <= offset && offset <= nameToken.end) {
- result[0] = node;
- throw new HtmlUnitUtils_FoundAttributeNodeError();
- }
- return super.visitXmlAttributeNode(node);
- }
-}
-
-class RecursiveXmlVisitor_HtmlUnitUtils_getEnclosingTagNode extends RecursiveXmlVisitor<Object> {
- int offset = 0;
-
- List<XmlTagNode> result;
-
- RecursiveXmlVisitor_HtmlUnitUtils_getEnclosingTagNode(this.offset, this.result) : super();
-
- @override
- Object visitXmlTagNode(XmlTagNode node) {
- if (node.offset <= offset && offset < node.end) {
- result[0] = node;
- super.visitXmlTagNode(node);
- throw new HtmlUnitUtils_FoundTagNodeError();
- }
- return null;
- }
-}
-
-class ExpressionVisitor_HtmlUnitUtils_getExpression extends ExpressionVisitor {
- int offset = 0;
-
- List<Expression> result;
-
- ExpressionVisitor_HtmlUnitUtils_getExpression(this.offset, this.result) : super();
-
- @override
- void visitExpression(Expression expression) {
- Expression at = HtmlUnitUtils._getExpressionAt(expression, offset);
- if (at != null) {
- result[0] = at;
- throw new HtmlUnitUtils_FoundExpressionError();
- }
- }
-}
-
-/**
- * Instances of the class `HtmlScriptTagNode` represent a script tag within an HTML file that
- * references a Dart script.
- */
-class HtmlScriptTagNode extends XmlTagNode {
- /**
- * The AST structure representing the Dart code within this tag.
- */
- CompilationUnit _script;
-
- /**
- * The element representing this script.
- */
- HtmlScriptElement scriptElement;
-
- /**
- * Initialize a newly created node to represent a script tag within an HTML file that references a
- * Dart script.
+ * Return an array containing the offsets of the first character of each line in the source code.
*
- * @param nodeStart the token marking the beginning of the tag
- * @param tag the name of the tag
- * @param attributes the attributes in the tag
- * @param attributeEnd the token terminating the region where attributes can be
- * @param tagNodes the children of the tag
- * @param contentEnd the token that starts the closing tag
- * @param closingTag the name of the tag that occurs in the closing tag
- * @param nodeEnd the last token in the tag
+ * @return an array containing the offsets of the first character of each line in the source code
*/
- HtmlScriptTagNode(Token nodeStart, Token tag, List<XmlAttributeNode> attributes, Token attributeEnd, List<XmlTagNode> tagNodes, Token contentEnd, Token closingTag, Token nodeEnd) : super(nodeStart, tag, attributes, attributeEnd, tagNodes, contentEnd, closingTag, nodeEnd);
-
- @override
- accept(XmlVisitor visitor) => visitor.visitHtmlScriptTagNode(this);
+ List<int> get lineStarts => _lineStarts;
/**
- * Return the AST structure representing the Dart code within this tag, or `null` if this
- * tag references an external script.
+ * Return the current offset relative to the beginning of the file. Return the initial offset if
+ * the scanner has not yet scanned the source code, and one (1) past the end of the source code if
+ * the source code has been scanned.
*
- * @return the AST structure representing the Dart code within this tag
+ * @return the current offset of the scanner in the source
*/
- CompilationUnit get script => _script;
+ int get offset;
/**
- * Set the AST structure representing the Dart code within this tag to the given compilation unit.
- *
- * @param unit the AST structure representing the Dart code within this tag
+ * Set array of element tags for which the content between tags should be consider a single token.
*/
- void set script(CompilationUnit unit) {
- _script = unit;
+ void set passThroughElements(List<String> passThroughElements) {
+ this._passThroughElements = passThroughElements != null ? passThroughElements : _NO_PASS_THROUGH_ELEMENTS;
}
-}
-
-/**
- * The abstract class `XmlNode` defines behavior common to all XML/HTML nodes.
- */
-abstract class XmlNode {
- /**
- * The parent of the node, or `null` if the node is the root of an AST structure.
- */
- XmlNode _parent;
/**
- * The element associated with this node or `null` if the receiver is not resolved.
- */
- Element _element;
-
- /**
- * Use the given visitor to visit this node.
+ * Scan the source code to produce a list of tokens representing the source.
*
- * @param visitor the visitor that will visit this node
- * @return the value returned by the visitor as a result of visiting this node
+ * @return the first token in the list of tokens that were produced
*/
- accept(XmlVisitor visitor);
+ Token tokenize() {
+ _scan();
+ _appendEofToken();
+ return _firstToken();
+ }
/**
- * Return the first token included in this node's source range.
+ * Advance the current position and return the character at the new current position.
*
- * @return the first token or `null` if none
+ * @return the character at the new current position
*/
- Token get beginToken;
+ int advance();
/**
- * Return the element associated with this node.
+ * Return the substring of the source code between the start offset and the modified current
+ * position. The current position is modified by adding the end delta.
*
- * @return the element or `null` if the receiver is not resolved
+ * @param start the offset to the beginning of the string, relative to the start of the file
+ * @param endDelta the number of character after the current location to be included in the
+ * string, or the number of characters before the current location to be excluded if the
+ * offset is negative
+ * @return the specified substring of the source code
*/
- Element get element => _element;
+ String getString(int start, int endDelta);
/**
- * Return the offset of the character immediately following the last character of this node's
- * source range. This is equivalent to `node.getOffset() + node.getLength()`. For an html
- * unit this will be equal to the length of the unit's source.
+ * Return the character at the current position without changing the current position.
*
- * @return the offset of the character just past the node's source range
+ * @return the character at the current position
*/
- int get end => offset + length;
+ int peek();
/**
- * Return the last token included in this node's source range.
- *
- * @return the last token or `null` if none
+ * Record the fact that we are at the beginning of a new line in the source.
*/
- Token get endToken;
+ void recordStartOfLine() {
+ _lineStarts.add(offset);
+ }
- /**
- * Return the number of characters in the node's source range.
- *
- * @return the number of characters in the node's source range
- */
- int get length {
- Token beginToken = this.beginToken;
- Token endToken = this.endToken;
- if (beginToken == null || endToken == null) {
- return -1;
- }
- return endToken.offset + endToken.length - beginToken.offset;
+ void _appendEofToken() {
+ Token eofToken = new Token.con1(TokenType.EOF, offset);
+ // The EOF token points to itself so that there is always infinite look-ahead.
+ eofToken.setNext(eofToken);
+ _tail = _tail.setNext(eofToken);
}
- /**
- * Return the offset from the beginning of the file to the first character in the node's source
- * range.
- *
- * @return the offset from the beginning of the file to the first character in the node's source
- * range
- */
- int get offset {
- Token beginToken = this.beginToken;
- if (beginToken == null) {
- return -1;
- }
- return this.beginToken.offset;
+ Token _emit(Token token) {
+ _tail.setNext(token);
+ _tail = token;
+ return token;
}
- /**
- * Return this node's parent node, or `null` if this node is the root of an AST structure.
- *
- * Note that the relationship between an AST node and its parent node may change over the lifetime
- * of a node.
- *
- * @return the parent of this node, or `null` if none
- */
- XmlNode get parent => _parent;
+ Token _emitWithOffset(TokenType type, int start) => _emit(new Token.con1(type, start));
- /**
- * Set the element associated with this node.
- *
- * @param element the element
- */
- void set element(Element element) {
- this._element = element;
+ Token _emitWithOffsetAndLength(TokenType type, int start, int count) => _emit(new Token.con2(type, start, getString(start, count)));
+
+ Token _firstToken() => _tokens.next;
+
+ int _recordStartOfLineAndAdvance(int c) {
+ if (c == 0xD) {
+ c = advance();
+ if (c == 0xA) {
+ c = advance();
+ }
+ recordStartOfLine();
+ } else if (c == 0xA) {
+ c = advance();
+ recordStartOfLine();
+ } else {
+ c = advance();
+ }
+ return c;
}
- @override
- String toString() {
- PrintStringWriter writer = new PrintStringWriter();
- accept(new ToSourceVisitor(writer));
- return writer.toString();
+ void _scan() {
+ bool inBrackets = false;
+ String endPassThrough = null;
+ int c = advance();
+ while (c >= 0) {
+ int start = offset;
+ if (c == 0x3C) {
+ c = advance();
+ if (c == 0x21) {
+ c = advance();
+ if (c == 0x2D && peek() == 0x2D) {
+ // handle a comment
+ c = advance();
+ int dashCount = 1;
+ while (c >= 0) {
+ if (c == 0x2D) {
+ dashCount++;
+ } else if (c == 0x3E && dashCount >= 2) {
+ c = advance();
+ break;
+ } else {
+ dashCount = 0;
+ }
+ c = _recordStartOfLineAndAdvance(c);
+ }
+ _emitWithOffsetAndLength(TokenType.COMMENT, start, -1);
+ // Capture <!--> and <!---> as tokens but report an error
+ if (_tail.length < 7) {
+ }
+ } else {
+ // handle a declaration
+ while (c >= 0) {
+ if (c == 0x3E) {
+ c = advance();
+ break;
+ }
+ c = _recordStartOfLineAndAdvance(c);
+ }
+ _emitWithOffsetAndLength(TokenType.DECLARATION, start, -1);
+ if (!StringUtilities.endsWithChar(_tail.lexeme, 0x3E)) {
+ }
+ }
+ } else if (c == 0x3F) {
+ // handle a directive
+ while (c >= 0) {
+ if (c == 0x3F) {
+ c = advance();
+ if (c == 0x3E) {
+ c = advance();
+ break;
+ }
+ } else {
+ c = _recordStartOfLineAndAdvance(c);
+ }
+ }
+ _emitWithOffsetAndLength(TokenType.DIRECTIVE, start, -1);
+ if (_tail.length < 4) {
+ }
+ } else if (c == 0x2F) {
+ _emitWithOffset(TokenType.LT_SLASH, start);
+ inBrackets = true;
+ c = advance();
+ } else {
+ inBrackets = true;
+ _emitWithOffset(TokenType.LT, start);
+ // ignore whitespace in braces
+ while (Character.isWhitespace(c)) {
+ c = _recordStartOfLineAndAdvance(c);
+ }
+ // get tag
+ if (Character.isLetterOrDigit(c)) {
+ int tagStart = offset;
+ c = advance();
+ while (Character.isLetterOrDigit(c) || c == 0x2D || c == 0x5F) {
+ c = advance();
+ }
+ _emitWithOffsetAndLength(TokenType.TAG, tagStart, -1);
+ // check tag against passThrough elements
+ String tag = _tail.lexeme;
+ for (String str in _passThroughElements) {
+ if (str == tag) {
+ endPassThrough = "</${str}>";
+ break;
+ }
+ }
+ }
+ }
+ } else if (c == 0x3E) {
+ _emitWithOffset(TokenType.GT, start);
+ inBrackets = false;
+ c = advance();
+ // if passThrough != null, read until we match it
+ if (endPassThrough != null) {
+ bool endFound = false;
+ int len = endPassThrough.length;
+ int firstC = endPassThrough.codeUnitAt(0);
+ int index = 0;
+ int nextC = firstC;
+ while (c >= 0) {
+ if (c == nextC) {
+ index++;
+ if (index == len) {
+ endFound = true;
+ break;
+ }
+ nextC = endPassThrough.codeUnitAt(index);
+ } else if (c == firstC) {
+ index = 1;
+ nextC = endPassThrough.codeUnitAt(1);
+ } else {
+ index = 0;
+ nextC = firstC;
+ }
+ c = _recordStartOfLineAndAdvance(c);
+ }
+ if (start + 1 < offset) {
+ if (endFound) {
+ _emitWithOffsetAndLength(TokenType.TEXT, start + 1, -len);
+ _emitWithOffset(TokenType.LT_SLASH, offset - len + 1);
+ _emitWithOffsetAndLength(TokenType.TAG, offset - len + 3, -1);
+ } else {
+ _emitWithOffsetAndLength(TokenType.TEXT, start + 1, -1);
+ }
+ }
+ endPassThrough = null;
+ }
+ } else if (c == 0x2F && peek() == 0x3E) {
+ advance();
+ _emitWithOffset(TokenType.SLASH_GT, start);
+ inBrackets = false;
+ c = advance();
+ } else if (!inBrackets) {
+ c = _recordStartOfLineAndAdvance(c);
+ while (c != 0x3C && c >= 0) {
+ c = _recordStartOfLineAndAdvance(c);
+ }
+ _emitWithOffsetAndLength(TokenType.TEXT, start, -1);
+ } else if (c == 0x22 || c == 0x27) {
+ // read a string
+ int endQuote = c;
+ c = advance();
+ while (c >= 0) {
+ if (c == endQuote) {
+ c = advance();
+ break;
+ }
+ c = _recordStartOfLineAndAdvance(c);
+ }
+ _emitWithOffsetAndLength(TokenType.STRING, start, -1);
+ } else if (c == 0x3D) {
+ // a non-char token
+ _emitWithOffset(TokenType.EQ, start);
+ c = advance();
+ } else if (Character.isWhitespace(c)) {
+ // ignore whitespace in braces
+ do {
+ c = _recordStartOfLineAndAdvance(c);
+ } while (Character.isWhitespace(c));
+ } else if (Character.isLetterOrDigit(c)) {
+ c = advance();
+ while (Character.isLetterOrDigit(c) || c == 0x2D || c == 0x5F) {
+ c = advance();
+ }
+ _emitWithOffsetAndLength(TokenType.TAG, start, -1);
+ } else {
+ // a non-char token
+ _emitWithOffsetAndLength(TokenType.TEXT, start, 0);
+ c = advance();
+ }
+ }
}
+}
+/**
+ * Instances of the class `Token` represent a token that was scanned from the input. Each
+ * token knows which token follows it, acting as the head of a linked list of tokens.
+ */
+class Token {
/**
- * Use the given visitor to visit all of the children of this node. The children will be visited
- * in source order.
- *
- * @param visitor the visitor that will be used to visit the children of this node
+ * The offset from the beginning of the file to the first character in the token.
*/
- void visitChildren(XmlVisitor visitor);
+ final int offset;
/**
- * Make this node the parent of the given child node.
+ * The previous token in the token stream.
+ */
+ Token previous;
+
+ /**
+ * The next token in the token stream.
+ */
+ Token _next;
+
+ /**
+ * The type of the token.
+ */
+ final TokenType type;
+
+ /**
+ * The lexeme represented by this token.
+ */
+ String _value;
+
+ /**
+ * Initialize a newly created token.
*
- * @param child the node that will become a child of this node
- * @return the node that was made a child of this node
+ * @param type the token type (not `null`)
+ * @param offset the offset from the beginning of the file to the first character in the token
*/
- XmlNode becomeParentOf(XmlNode child) {
- if (child != null) {
- XmlNode node = child;
- node.parent = this;
- }
- return child;
- }
+ Token.con1(TokenType type, int offset) : this.con2(type, offset, type.lexeme);
/**
- * Make this node the parent of the given child nodes.
+ * Initialize a newly created token.
*
- * @param children the nodes that will become the children of this node
- * @param ifEmpty the (empty) nodes to return if "children" is empty
- * @return the nodes that were made children of this node
+ * @param type the token type (not `null`)
+ * @param offset the offset from the beginning of the file to the first character in the token
+ * @param value the lexeme represented by this token (not `null`)
*/
- List becomeParentOfAll(List children, {List ifEmpty}) {
- if (children == null || children.isEmpty) {
- if (ifEmpty != null) {
- return ifEmpty;
- }
- }
- if (children != null) {
- for (JavaIterator iter = new JavaIterator(children); iter.hasNext;) {
- XmlNode node = iter.next();
- node.parent = this;
- }
- // This will create ArrayList for exactly given number of elements.
- return new List.from(children);
- }
- return children;
+ Token.con2(this.type, this.offset, String value) {
+ this._value = StringUtilities.intern(value);
}
/**
- * This method exists for debugging purposes only.
+ * Return the offset from the beginning of the file to the character after last character of the
+ * token.
+ *
+ * @return the offset from the beginning of the file to the first character after last character
+ * of the token
*/
- void _appendIdentifier(JavaStringBuilder builder, XmlNode node) {
- if (node is XmlTagNode) {
- builder.append(node.tag);
- } else if (node is XmlAttributeNode) {
- builder.append(node.name);
- } else {
- builder.append("htmlUnit");
- }
- }
+ int get end => offset + length;
/**
- * This method exists for debugging purposes only.
+ * Return the number of characters in the node's source range.
+ *
+ * @return the number of characters in the node's source range
*/
- String _buildRecursiveStructureMessage(XmlNode newParent) {
- JavaStringBuilder builder = new JavaStringBuilder();
- builder.append("Attempt to create recursive structure: ");
- XmlNode current = newParent;
- while (current != null) {
- if (!identical(current, newParent)) {
- builder.append(" -> ");
- }
- if (identical(current, this)) {
- builder.appendChar(0x2A);
- _appendIdentifier(builder, current);
- builder.appendChar(0x2A);
- } else {
- _appendIdentifier(builder, current);
- }
- current = current.parent;
- }
- return builder.toString();
- }
+ int get length => lexeme.length;
+
+ /**
+ * Return the lexeme that represents this token.
+ *
+ * @return the lexeme (not `null`)
+ */
+ String get lexeme => _value;
/**
- * Set the parent of this node to the given node.
+ * Return the next token in the token stream.
*
- * @param newParent the node that is to be made the parent of this node
+ * @return the next token in the token stream
*/
- void set parent(XmlNode newParent) {
- XmlNode current = newParent;
- while (current != null) {
- if (identical(current, this)) {
- AnalysisEngine.instance.logger.logError2("Circular structure while setting an XML node's parent", new IllegalArgumentException(_buildRecursiveStructureMessage(newParent)));
- return;
- }
- current = current.parent;
- }
- _parent = newParent;
+ Token get next => _next;
+
+ /**
+ * Return `true` if this token is a synthetic token. A synthetic token is a token that was
+ * introduced by the parser in order to recover from an error in the code. Synthetic tokens always
+ * have a length of zero (`0`).
+ *
+ * @return `true` if this token is a synthetic token
+ */
+ bool get isSynthetic => length == 0;
+
+ /**
+ * Set the next token in the token stream to the given token. This has the side-effect of setting
+ * this token to be the previous token for the given token.
+ *
+ * @param token the next token in the token stream
+ * @return the token that was passed in
+ */
+ Token setNext(Token token) {
+ _next = token;
+ token.previous = this;
+ return token;
}
+
+ @override
+ String toString() => lexeme;
}
/**
- * Instances of the class `SimpleXmlVisitor` implement an AST visitor that will do nothing
- * when visiting an AST node. It is intended to be a superclass for classes that use the visitor
- * pattern primarily as a dispatch mechanism (and hence don't need to recursively visit a whole
- * structure) and that only need to visit a small number of node types.
+ * Instances of `XmlTagNode` represent XML or HTML elements such as `` and
+ * `<body foo="bar"> ... </body>`.
*/
-class SimpleXmlVisitor<R> implements XmlVisitor<R> {
- @override
- R visitHtmlScriptTagNode(HtmlScriptTagNode node) => null;
+class XmlTagNode extends XmlNode {
+ /**
+ * Constant representing empty list of attributes.
+ */
+ static List<XmlAttributeNode> NO_ATTRIBUTES = new UnmodifiableListView(new List<XmlAttributeNode>());
- @override
- R visitHtmlUnit(HtmlUnit htmlUnit) => null;
+ /**
+ * Constant representing empty list of tag nodes.
+ */
+ static List<XmlTagNode> NO_TAG_NODES = new UnmodifiableListView(new List<XmlTagNode>());
- @override
- R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode) => null;
+ /**
+ * The starting [TokenType#LT] token (not `null`).
+ */
+ final Token nodeStart;
- @override
- R visitXmlTagNode(XmlTagNode xmlTagNode) => null;
-}
+ /**
+ * The [TokenType#TAG] token after the starting '&lt;' (not `null`).
+ */
+ final Token _tag;
-/**
- * The abstract class `AbstractScanner` implements a scanner for HTML code. Subclasses are
- * required to implement the interface used to access the characters being scanned.
- */
-abstract class AbstractScanner {
- static List<String> _NO_PASS_THROUGH_ELEMENTS = <String> [];
+ /**
+ * The attributes contained by the receiver (not `null`, contains no `null`s).
+ */
+ List<XmlAttributeNode> _attributes;
/**
- * The source being scanned.
+ * The [TokenType#GT] or [TokenType#SLASH_GT] token after the attributes (not
+ * `null`). The token may be the same token as [nodeEnd] if there are no child
+ * [tagNodes].
*/
- final Source source;
+ final Token attributeEnd;
/**
- * The token pointing to the head of the linked list of tokens.
+ * The tag nodes contained in the receiver (not `null`, contains no `null`s).
*/
- Token _tokens;
+ List<XmlTagNode> _tagNodes;
/**
- * The last token that was scanned.
+ * The token (not `null`) after the content, which may be
+ * * (1) [TokenType#LT_SLASH] for nodes with open and close tags, or
+ * * (2) the [TokenType#LT] nodeStart of the next sibling node if this node is self
+ * closing or the attributeEnd is [TokenType#SLASH_GT], or
+ * * (3) [TokenType#EOF] if the node does not have a closing tag and is the last node in
+ * the stream [TokenType#LT_SLASH] token after the content, or `null` if there is no
+ * content and the attributes ended with [TokenType#SLASH_GT].
*/
- Token _tail;
+ final Token contentEnd;
/**
- * A list containing the offsets of the first character of each line in the source code.
+ * The closing [TokenType#TAG] after the child elements or `null` if there is no
+ * content and the attributes ended with [TokenType#SLASH_GT]
*/
- List<int> _lineStarts = new List<int>();
+ final Token closingTag;
/**
- * An array of element tags for which the content between tags should be consider a single token.
+ * The ending [TokenType#GT] or [TokenType#SLASH_GT] token (not `null`).
*/
- List<String> _passThroughElements = _NO_PASS_THROUGH_ELEMENTS;
+ final Token nodeEnd;
/**
- * Initialize a newly created scanner.
- *
- * @param source the source being scanned
+ * The expressions that are embedded in the tag's content.
*/
- AbstractScanner(this.source) {
- _tokens = new Token.con1(TokenType.EOF, -1);
- _tokens.setNext(_tokens);
- _tail = _tokens;
- recordStartOfLine();
- }
+ List<XmlExpression> expressions = XmlExpression.EMPTY_ARRAY;
/**
- * Return an array containing the offsets of the first character of each line in the source code.
+ * Construct a new instance representing an XML or HTML element
*
- * @return an array containing the offsets of the first character of each line in the source code
+ * @param nodeStart the starting [TokenType#LT] token (not `null`)
+ * @param tag the [TokenType#TAG] token after the starting '&lt;' (not `null`).
+ * @param attributes the attributes associated with this element or [NO_ATTRIBUTES] (not
+ * `null`, contains no `null`s)
+ * @param attributeEnd The [TokenType#GT] or [TokenType#SLASH_GT] token after the
+ * attributes (not `null`). The token may be the same token as [nodeEnd] if
+ * there are no child [tagNodes].
+ * @param tagNodes child tag nodes of the receiver or [NO_TAG_NODES] (not `null`,
+ * contains no `null`s)
+ * @param contentEnd the token (not `null`) after the content, which may be
+ * * (1) [TokenType#LT_SLASH] for nodes with open and close tags, or
+ * * (2) the [TokenType#LT] nodeStart of the next sibling node if this node is
+ * self closing or the attributeEnd is [TokenType#SLASH_GT], or
+ * * (3) [TokenType#EOF] if the node does not have a closing tag and is the last
+ * node in the stream [TokenType#LT_SLASH] token after the content, or `null`
+ * if there is no content and the attributes ended with [TokenType#SLASH_GT].
+ * @param closingTag the closing [TokenType#TAG] after the child elements or `null` if
+ * there is no content and the attributes ended with [TokenType#SLASH_GT]
+ * @param nodeEnd the ending [TokenType#GT] or [TokenType#SLASH_GT] token (not
+ * `null`)
*/
- List<int> get lineStarts => _lineStarts;
+ XmlTagNode(this.nodeStart, this._tag, List<XmlAttributeNode> attributes, this.attributeEnd, List<XmlTagNode> tagNodes, this.contentEnd, this.closingTag, this.nodeEnd) {
+ this._attributes = becomeParentOfAll(attributes, ifEmpty: NO_ATTRIBUTES);
+ this._tagNodes = becomeParentOfAll(tagNodes, ifEmpty: NO_TAG_NODES);
+ }
+
+ @override
+ accept(XmlVisitor visitor) => visitor.visitXmlTagNode(this);
/**
- * Return the current offset relative to the beginning of the file. Return the initial offset if
- * the scanner has not yet scanned the source code, and one (1) past the end of the source code if
- * the source code has been scanned.
+ * Answer the attribute with the specified name.
*
- * @return the current offset of the scanner in the source
+ * @param name the attribute name
+ * @return the attribute or `null` if no matching attribute is found
*/
- int get offset;
+ XmlAttributeNode getAttribute(String name) {
+ for (XmlAttributeNode attribute in _attributes) {
+ if (attribute.name == name) {
+ return attribute;
+ }
+ }
+ return null;
+ }
/**
- * Set array of element tags for which the content between tags should be consider a single token.
+ * Answer the receiver's attributes. Callers should not manipulate the returned list to edit the
+ * AST structure.
+ *
+ * @return the attributes (not `null`, contains no `null`s)
*/
- void set passThroughElements(List<String> passThroughElements) {
- this._passThroughElements = passThroughElements != null ? passThroughElements : _NO_PASS_THROUGH_ELEMENTS;
- }
+ List<XmlAttributeNode> get attributes => _attributes;
/**
- * Scan the source code to produce a list of tokens representing the source.
+ * Find the attribute with the given name (see [getAttribute] and answer the lexeme
+ * for the attribute's value token without the leading and trailing quotes (see
+ * [XmlAttributeNode#getText]).
*
- * @return the first token in the list of tokens that were produced
+ * @param name the attribute name
+ * @return the attribute text or `null` if no matching attribute is found
*/
- Token tokenize() {
- _scan();
- _appendEofToken();
- return _firstToken();
+ String getAttributeText(String name) {
+ XmlAttributeNode attribute = getAttribute(name);
+ return attribute != null ? attribute.text : null;
}
+ @override
+ Token get beginToken => nodeStart;
+
/**
- * Advance the current position and return the character at the new current position.
+ * Answer a string representing the content contained in the receiver. This includes the textual
+ * representation of any child tag nodes ([getTagNodes]). Whitespace between '&lt;',
+ * '&lt;/', and '>', '/>' is discarded, but all other whitespace is preserved.
*
- * @return the character at the new current position
+ * @return the content (not `null`)
*/
- int advance();
+ String get content {
+ Token token = attributeEnd.next;
+ if (identical(token, contentEnd)) {
+ return "";
+ }
+ //TODO (danrubel): handle CDATA and replace HTML character encodings with the actual characters
+ String content = token.lexeme;
+ token = token.next;
+ if (identical(token, contentEnd)) {
+ return content;
+ }
+ JavaStringBuilder buffer = new JavaStringBuilder();
+ while (!identical(token, contentEnd)) {
+ buffer.append(token.lexeme);
+ token = token.next;
+ }
+ return buffer.toString();
+ }
+
+ @override
+ Token get endToken {
+ if (nodeEnd != null) {
+ return nodeEnd;
+ }
+ if (closingTag != null) {
+ return closingTag;
+ }
+ if (contentEnd != null) {
+ return contentEnd;
+ }
+ if (!_tagNodes.isEmpty) {
+ return _tagNodes[_tagNodes.length - 1].endToken;
+ }
+ if (attributeEnd != null) {
+ return attributeEnd;
+ }
+ if (!_attributes.isEmpty) {
+ return _attributes[_attributes.length - 1].endToken;
+ }
+ return _tag;
+ }
/**
- * Return the substring of the source code between the start offset and the modified current
- * position. The current position is modified by adding the end delta.
+ * Answer the tag name after the starting '&lt;'.
*
- * @param start the offset to the beginning of the string, relative to the start of the file
- * @param endDelta the number of character after the current location to be included in the
- * string, or the number of characters before the current location to be excluded if the
- * offset is negative
- * @return the specified substring of the source code
+ * @return the tag name (not `null`)
*/
- String getString(int start, int endDelta);
+ String get tag => _tag.lexeme;
/**
- * Return the character at the current position without changing the current position.
+ * Answer the tag nodes contained in the receiver. Callers should not manipulate the returned list
+ * to edit the AST structure.
*
- * @return the character at the current position
+ * @return the children (not `null`, contains no `null`s)
*/
- int peek();
+ List<XmlTagNode> get tagNodes => _tagNodes;
/**
- * Record the fact that we are at the beginning of a new line in the source.
+ * Answer the [TokenType#TAG] token after the starting '&lt;'.
+ *
+ * @return the token (not `null`)
*/
- void recordStartOfLine() {
- _lineStarts.add(offset);
- }
-
- void _appendEofToken() {
- Token eofToken = new Token.con1(TokenType.EOF, offset);
- // The EOF token points to itself so that there is always infinite look-ahead.
- eofToken.setNext(eofToken);
- _tail = _tail.setNext(eofToken);
- }
-
- Token _emit(Token token) {
- _tail.setNext(token);
- _tail = token;
- return token;
- }
-
- Token _emitWithOffset(TokenType type, int start) => _emit(new Token.con1(type, start));
-
- Token _emitWithOffsetAndLength(TokenType type, int start, int count) => _emit(new Token.con2(type, start, getString(start, count)));
-
- Token _firstToken() => _tokens.next;
+ Token get tagToken => _tag;
- int _recordStartOfLineAndAdvance(int c) {
- if (c == 0xD) {
- c = advance();
- if (c == 0xA) {
- c = advance();
- }
- recordStartOfLine();
- } else if (c == 0xA) {
- c = advance();
- recordStartOfLine();
- } else {
- c = advance();
+ @override
+ void visitChildren(XmlVisitor visitor) {
+ for (XmlAttributeNode node in _attributes) {
+ node.accept(visitor);
}
- return c;
- }
-
- void _scan() {
- bool inBrackets = false;
- String endPassThrough = null;
- int c = advance();
- while (c >= 0) {
- int start = offset;
- if (c == 0x3C) {
- c = advance();
- if (c == 0x21) {
- c = advance();
- if (c == 0x2D && peek() == 0x2D) {
- // handle a comment
- c = advance();
- int dashCount = 1;
- while (c >= 0) {
- if (c == 0x2D) {
- dashCount++;
- } else if (c == 0x3E && dashCount >= 2) {
- c = advance();
- break;
- } else {
- dashCount = 0;
- }
- c = _recordStartOfLineAndAdvance(c);
- }
- _emitWithOffsetAndLength(TokenType.COMMENT, start, -1);
- // Capture <!--> and <!---> as tokens but report an error
- if (_tail.length < 7) {
- }
- } else {
- // handle a declaration
- while (c >= 0) {
- if (c == 0x3E) {
- c = advance();
- break;
- }
- c = _recordStartOfLineAndAdvance(c);
- }
- _emitWithOffsetAndLength(TokenType.DECLARATION, start, -1);
- if (!StringUtilities.endsWithChar(_tail.lexeme, 0x3E)) {
- }
- }
- } else if (c == 0x3F) {
- // handle a directive
- while (c >= 0) {
- if (c == 0x3F) {
- c = advance();
- if (c == 0x3E) {
- c = advance();
- break;
- }
- } else {
- c = _recordStartOfLineAndAdvance(c);
- }
- }
- _emitWithOffsetAndLength(TokenType.DIRECTIVE, start, -1);
- if (_tail.length < 4) {
- }
- } else if (c == 0x2F) {
- _emitWithOffset(TokenType.LT_SLASH, start);
- inBrackets = true;
- c = advance();
- } else {
- inBrackets = true;
- _emitWithOffset(TokenType.LT, start);
- // ignore whitespace in braces
- while (Character.isWhitespace(c)) {
- c = _recordStartOfLineAndAdvance(c);
- }
- // get tag
- if (Character.isLetterOrDigit(c)) {
- int tagStart = offset;
- c = advance();
- while (Character.isLetterOrDigit(c) || c == 0x2D || c == 0x5F) {
- c = advance();
- }
- _emitWithOffsetAndLength(TokenType.TAG, tagStart, -1);
- // check tag against passThrough elements
- String tag = _tail.lexeme;
- for (String str in _passThroughElements) {
- if (str == tag) {
- endPassThrough = "</${str}>";
- break;
- }
- }
- }
- }
- } else if (c == 0x3E) {
- _emitWithOffset(TokenType.GT, start);
- inBrackets = false;
- c = advance();
- // if passThrough != null, read until we match it
- if (endPassThrough != null) {
- bool endFound = false;
- int len = endPassThrough.length;
- int firstC = endPassThrough.codeUnitAt(0);
- int index = 0;
- int nextC = firstC;
- while (c >= 0) {
- if (c == nextC) {
- index++;
- if (index == len) {
- endFound = true;
- break;
- }
- nextC = endPassThrough.codeUnitAt(index);
- } else if (c == firstC) {
- index = 1;
- nextC = endPassThrough.codeUnitAt(1);
- } else {
- index = 0;
- nextC = firstC;
- }
- c = _recordStartOfLineAndAdvance(c);
- }
- if (start + 1 < offset) {
- if (endFound) {
- _emitWithOffsetAndLength(TokenType.TEXT, start + 1, -len);
- _emitWithOffset(TokenType.LT_SLASH, offset - len + 1);
- _emitWithOffsetAndLength(TokenType.TAG, offset - len + 3, -1);
- } else {
- _emitWithOffsetAndLength(TokenType.TEXT, start + 1, -1);
- }
- }
- endPassThrough = null;
- }
- } else if (c == 0x2F && peek() == 0x3E) {
- advance();
- _emitWithOffset(TokenType.SLASH_GT, start);
- inBrackets = false;
- c = advance();
- } else if (!inBrackets) {
- c = _recordStartOfLineAndAdvance(c);
- while (c != 0x3C && c >= 0) {
- c = _recordStartOfLineAndAdvance(c);
- }
- _emitWithOffsetAndLength(TokenType.TEXT, start, -1);
- } else if (c == 0x22 || c == 0x27) {
- // read a string
- int endQuote = c;
- c = advance();
- while (c >= 0) {
- if (c == endQuote) {
- c = advance();
- break;
- }
- c = _recordStartOfLineAndAdvance(c);
- }
- _emitWithOffsetAndLength(TokenType.STRING, start, -1);
- } else if (c == 0x3D) {
- // a non-char token
- _emitWithOffset(TokenType.EQ, start);
- c = advance();
- } else if (Character.isWhitespace(c)) {
- // ignore whitespace in braces
- do {
- c = _recordStartOfLineAndAdvance(c);
- } while (Character.isWhitespace(c));
- } else if (Character.isLetterOrDigit(c)) {
- c = advance();
- while (Character.isLetterOrDigit(c) || c == 0x2D || c == 0x5F) {
- c = advance();
- }
- _emitWithOffsetAndLength(TokenType.TAG, start, -1);
- } else {
- // a non-char token
- _emitWithOffsetAndLength(TokenType.TEXT, start, 0);
- c = advance();
- }
+ for (XmlTagNode node in _tagNodes) {
+ node.accept(visitor);
}
}
}
/**
- * Instances of the class `StringScanner` implement a scanner that reads from a string. The
- * scanning logic is in the superclass.
+ * Instances of the class `XmlParser` are used to parse tokens into a AST structure comprised
+ * of [XmlNode]s.
*/
-class StringScanner extends AbstractScanner {
+class XmlParser {
/**
- * The string from which characters will be read.
+ * The source being parsed.
*/
- final String _string;
+ final Source source;
+
+ /**
+ * The next token to be parsed.
+ */
+ Token _currentToken;
+
+ /**
+ * Construct a parser for the specified source.
+ *
+ * @param source the source being parsed
+ */
+ XmlParser(this.source);
+
+ /**
+ * Create a node representing an attribute.
+ *
+ * @param name the name of the attribute
+ * @param equals the equals sign, or `null` if there is no value
+ * @param value the value of the attribute
+ * @return the node that was created
+ */
+ XmlAttributeNode createAttributeNode(Token name, Token equals, Token value) => new XmlAttributeNode(name, equals, value);
/**
- * The number of characters in the string.
+ * Create a node representing a tag.
+ *
+ * @param nodeStart the token marking the beginning of the tag
+ * @param tag the name of the tag
+ * @param attributes the attributes in the tag
+ * @param attributeEnd the token terminating the region where attributes can be
+ * @param tagNodes the children of the tag
+ * @param contentEnd the token that starts the closing tag
+ * @param closingTag the name of the tag that occurs in the closing tag
+ * @param nodeEnd the last token in the tag
+ * @return the node that was created
*/
- int _stringLength = 0;
+ XmlTagNode createTagNode(Token nodeStart, Token tag, List<XmlAttributeNode> attributes, Token attributeEnd, List<XmlTagNode> tagNodes, Token contentEnd, Token closingTag, Token nodeEnd) => new XmlTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes, contentEnd, closingTag, nodeEnd);
/**
- * The index, relative to the string, of the last character that was read.
+ * Answer `true` if the specified tag is self closing and thus should never have content or
+ * child tag nodes.
+ *
+ * @param tag the tag (not `null`)
+ * @return `true` if self closing
*/
- int _charOffset = 0;
+ bool isSelfClosing(Token tag) => false;
/**
- * Initialize a newly created scanner to scan the characters in the given string.
+ * Parse the entire token stream and in the process, advance the current token to the end of the
+ * token stream.
*
- * @param source the source being scanned
- * @param string the string from which characters will be read
+ * @return the list of tag nodes found (not `null`, contains no `null`)
*/
- StringScanner(Source source, this._string) : super(source) {
- this._stringLength = _string.length;
- this._charOffset = -1;
+ List<XmlTagNode> parseTopTagNodes(Token firstToken) {
+ _currentToken = firstToken;
+ List<XmlTagNode> tagNodes = new List<XmlTagNode>();
+ TokenType type = _currentToken.type;
+ while (type != TokenType.EOF) {
+ if (type == TokenType.LT) {
+ tagNodes.add(_parseTagNode());
+ } else if (type == TokenType.DECLARATION || type == TokenType.DIRECTIVE || type == TokenType.COMMENT) {
+ // ignored tokens
+ _currentToken = _currentToken.next;
+ } else {
+ _reportUnexpectedToken();
+ _currentToken = _currentToken.next;
+ }
+ type = _currentToken.type;
+ }
+ return tagNodes;
}
- @override
- int get offset => _charOffset;
+ /**
+ * Answer the current token.
+ *
+ * @return the current token
+ */
+ Token get currentToken => _currentToken;
- void set offset(int offset) {
- _charOffset = offset;
+ /**
+ * Insert a synthetic token of the specified type before the current token
+ *
+ * @param type the type of token to be inserted (not `null`)
+ * @return the synthetic token that was inserted (not `null`)
+ */
+ Token _insertSyntheticToken(TokenType type) {
+ Token token = new Token.con2(type, _currentToken.offset, "");
+ _currentToken.previous.setNext(token);
+ token.setNext(_currentToken);
+ return token;
}
- @override
- int advance() {
- if (++_charOffset < _stringLength) {
- return _string.codeUnitAt(_charOffset);
+ /**
+ * Parse the token stream for an attribute. This method advances the current token over the
+ * attribute, but should not be called if the [currentToken] is not [TokenType#TAG].
+ *
+ * @return the attribute (not `null`)
+ */
+ XmlAttributeNode _parseAttribute() {
+ // Assume the current token is a tag
+ Token name = _currentToken;
+ _currentToken = _currentToken.next;
+ // Equals sign
+ Token equals;
+ if (_currentToken.type == TokenType.EQ) {
+ equals = _currentToken;
+ _currentToken = _currentToken.next;
+ } else {
+ _reportUnexpectedToken();
+ equals = _insertSyntheticToken(TokenType.EQ);
}
- _charOffset = _stringLength;
- return -1;
- }
-
- @override
- String getString(int start, int endDelta) => _string.substring(start, _charOffset + 1 + endDelta).toString();
-
- @override
- int peek() {
- if (_charOffset + 1 < _stringLength) {
- return _string.codeUnitAt(_charOffset + 1);
+ // String value
+ Token value;
+ if (_currentToken.type == TokenType.STRING) {
+ value = _currentToken;
+ _currentToken = _currentToken.next;
+ } else {
+ _reportUnexpectedToken();
+ value = _insertSyntheticToken(TokenType.STRING);
}
- return -1;
+ return createAttributeNode(name, equals, value);
}
-}
-/**
- * Instances of the class `ToSourceVisitor` write a source representation of a visited XML
- * node (and all of it's children) to a writer.
- */
-class ToSourceVisitor implements XmlVisitor<Object> {
/**
- * The writer to which the source is to be written.
+ * Parse the stream for a sequence of attributes. This method advances the current token to the
+ * next [TokenType#GT], [TokenType#SLASH_GT], or [TokenType#EOF].
+ *
+ * @return a collection of zero or more attributes (not `null`, contains no `null`s)
*/
- final PrintWriter _writer;
+ List<XmlAttributeNode> _parseAttributes() {
+ TokenType type = _currentToken.type;
+ if (type == TokenType.GT || type == TokenType.SLASH_GT || type == TokenType.EOF) {
+ return XmlTagNode.NO_ATTRIBUTES;
+ }
+ List<XmlAttributeNode> attributes = new List<XmlAttributeNode>();
+ while (type != TokenType.GT && type != TokenType.SLASH_GT && type != TokenType.EOF) {
+ if (type == TokenType.TAG) {
+ attributes.add(_parseAttribute());
+ } else {
+ _reportUnexpectedToken();
+ _currentToken = _currentToken.next;
+ }
+ type = _currentToken.type;
+ }
+ return attributes;
+ }
/**
- * Initialize a newly created visitor to write source code representing the visited nodes to the
- * given writer.
+ * Parse the stream for a sequence of tag nodes existing within a parent tag node. This method
+ * advances the current token to the next [TokenType#LT_SLASH] or [TokenType#EOF].
*
- * @param writer the writer to which the source is to be written
+ * @return a list of nodes (not `null`, contains no `null`s)
*/
- ToSourceVisitor(this._writer);
-
- @override
- Object visitHtmlScriptTagNode(HtmlScriptTagNode node) => visitXmlTagNode(node);
-
- @override
- Object visitHtmlUnit(HtmlUnit node) {
- for (XmlTagNode child in node.tagNodes) {
- _visit(child);
+ List<XmlTagNode> _parseChildTagNodes() {
+ TokenType type = _currentToken.type;
+ if (type == TokenType.LT_SLASH || type == TokenType.EOF) {
+ return XmlTagNode.NO_TAG_NODES;
}
- return null;
+ List<XmlTagNode> nodes = new List<XmlTagNode>();
+ while (type != TokenType.LT_SLASH && type != TokenType.EOF) {
+ if (type == TokenType.LT) {
+ nodes.add(_parseTagNode());
+ } else if (type == TokenType.COMMENT) {
+ // ignored token
+ _currentToken = _currentToken.next;
+ } else {
+ _reportUnexpectedToken();
+ _currentToken = _currentToken.next;
+ }
+ type = _currentToken.type;
+ }
+ return nodes;
}
- @override
- Object visitXmlAttributeNode(XmlAttributeNode node) {
- String name = node.name;
- Token value = node.valueToken;
- if (name.length == 0) {
- _writer.print("__");
+ /**
+ * Parse the token stream for the next tag node. This method advances current token over the
+ * parsed tag node, but should only be called if the current token is [TokenType#LT]
+ *
+ * @return the tag node or `null` if none found
+ */
+ XmlTagNode _parseTagNode() {
+ // Assume that the current node is a tag node start TokenType#LT
+ Token nodeStart = _currentToken;
+ _currentToken = _currentToken.next;
+ // Get the tag or create a synthetic tag and report an error
+ Token tag;
+ if (_currentToken.type == TokenType.TAG) {
+ tag = _currentToken;
+ _currentToken = _currentToken.next;
} else {
- _writer.print(name);
+ _reportUnexpectedToken();
+ tag = _insertSyntheticToken(TokenType.TAG);
}
- _writer.print("=");
- if (value == null) {
- _writer.print("__");
+ // Parse the attributes
+ List<XmlAttributeNode> attributes = _parseAttributes();
+ // Token ending attribute list
+ Token attributeEnd;
+ if (_currentToken.type == TokenType.GT || _currentToken.type == TokenType.SLASH_GT) {
+ attributeEnd = _currentToken;
+ _currentToken = _currentToken.next;
} else {
- _writer.print(value.lexeme);
+ _reportUnexpectedToken();
+ attributeEnd = _insertSyntheticToken(TokenType.SLASH_GT);
}
- return null;
- }
-
- @override
- Object visitXmlTagNode(XmlTagNode node) {
- _writer.print("<");
- String tagName = node.tag;
- _writer.print(tagName);
- for (XmlAttributeNode attribute in node.attributes) {
- _writer.print(" ");
- _visit(attribute);
+ // If the node has no children, then return the node
+ if (attributeEnd.type == TokenType.SLASH_GT || isSelfClosing(tag)) {
+ return createTagNode(nodeStart, tag, attributes, attributeEnd, XmlTagNode.NO_TAG_NODES, _currentToken, null, attributeEnd);
}
- _writer.print(node.attributeEnd.lexeme);
- if (node.closingTag != null) {
- for (XmlTagNode child in node.tagNodes) {
- _visit(child);
- }
- _writer.print("</");
- _writer.print(tagName);
- _writer.print(">");
+ // Parse the child tag nodes
+ List<XmlTagNode> tagNodes = _parseChildTagNodes();
+ // Token ending child tag nodes
+ Token contentEnd;
+ if (_currentToken.type == TokenType.LT_SLASH) {
+ contentEnd = _currentToken;
+ _currentToken = _currentToken.next;
+ } else {
+ // TODO (danrubel): handle self closing HTML elements by inserting synthetic tokens
+ // but not reporting an error
+ _reportUnexpectedToken();
+ contentEnd = _insertSyntheticToken(TokenType.LT_SLASH);
+ }
+ // Closing tag
+ Token closingTag;
+ if (_currentToken.type == TokenType.TAG) {
+ closingTag = _currentToken;
+ _currentToken = _currentToken.next;
+ } else {
+ _reportUnexpectedToken();
+ closingTag = _insertSyntheticToken(TokenType.TAG);
+ }
+ // Token ending node
+ Token nodeEnd;
+ if (_currentToken.type == TokenType.GT) {
+ nodeEnd = _currentToken;
+ _currentToken = _currentToken.next;
+ } else {
+ _reportUnexpectedToken();
+ nodeEnd = _insertSyntheticToken(TokenType.GT);
}
- return null;
+ return createTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes, contentEnd, closingTag, nodeEnd);
}
/**
- * Safely visit the given node.
- *
- * @param node the node to be visited
+ * Report the current token as unexpected
*/
- void _visit(XmlNode node) {
- if (node != null) {
- node.accept(this);
- }
+ void _reportUnexpectedToken() {
}
}
/**
- * The enumeration `TokenType` defines the types of tokens that can be returned by the
- * scanner.
+ * The abstract class `XmlNode` defines behavior common to all XML/HTML nodes.
*/
-class TokenType extends Enum<TokenType> {
+abstract class XmlNode {
/**
- * The type of the token that marks the end of the input.
+ * The parent of the node, or `null` if the node is the root of an AST structure.
*/
- static const TokenType EOF = const TokenType_EOF('EOF', 0, "");
-
- static const TokenType EQ = const TokenType('EQ', 1, "=");
-
- static const TokenType GT = const TokenType('GT', 2, ">");
-
- static const TokenType LT_SLASH = const TokenType('LT_SLASH', 3, "</");
-
- static const TokenType LT = const TokenType('LT', 4, "<");
-
- static const TokenType SLASH_GT = const TokenType('SLASH_GT', 5, "/>");
-
- static const TokenType COMMENT = const TokenType('COMMENT', 6, null);
-
- static const TokenType DECLARATION = const TokenType('DECLARATION', 7, null);
-
- static const TokenType DIRECTIVE = const TokenType('DIRECTIVE', 8, null);
-
- static const TokenType STRING = const TokenType('STRING', 9, null);
-
- static const TokenType TAG = const TokenType('TAG', 10, null);
-
- static const TokenType TEXT = const TokenType('TEXT', 11, null);
-
- static const List<TokenType> values = const [
- EOF,
- EQ,
- GT,
- LT_SLASH,
- LT,
- SLASH_GT,
- COMMENT,
- DECLARATION,
- DIRECTIVE,
- STRING,
- TAG,
- TEXT];
+ XmlNode _parent;
/**
- * The lexeme that defines this type of token, or `null` if there is more than one possible
- * lexeme for this type of token.
+ * The element associated with this node or `null` if the receiver is not resolved.
*/
- final String lexeme;
-
- const TokenType(String name, int ordinal, this.lexeme) : super(name, ordinal);
-}
-
-class TokenType_EOF extends TokenType {
- const TokenType_EOF(String name, int ordinal, String arg0) : super(name, ordinal, arg0);
-
- @override
- String toString() => "-eof-";
-}
-
-/**
- * Instances of `XmlAttributeNode` represent name/value pairs owned by an [XmlTagNode].
- */
-class XmlAttributeNode extends XmlNode {
- final Token _name;
-
- final Token equals;
-
- final Token _value;
-
- List<XmlExpression> expressions = XmlExpression.EMPTY_ARRAY;
+ Element _element;
/**
- * Construct a new instance representing an XML attribute.
+ * Use the given visitor to visit this node.
*
- * @param name the name token (not `null`). This may be a zero length token if the attribute
- * is badly formed.
- * @param equals the equals sign or `null` if none
- * @param value the value token (not `null`)
+ * @param visitor the visitor that will visit this node
+ * @return the value returned by the visitor as a result of visiting this node
*/
- XmlAttributeNode(this._name, this.equals, this._value);
-
- @override
- accept(XmlVisitor visitor) => visitor.visitXmlAttributeNode(this);
+ accept(XmlVisitor visitor);
- @override
- Token get beginToken => _name;
+ /**
+ * Return the first token included in this node's source range.
+ *
+ * @return the first token or `null` if none
+ */
+ Token get beginToken;
- @override
- Token get endToken => _value;
+ /**
+ * Return the element associated with this node.
+ *
+ * @return the element or `null` if the receiver is not resolved
+ */
+ Element get element => _element;
/**
- * Answer the attribute name. This may be a zero length string if the attribute is badly formed.
+ * Return the offset of the character immediately following the last character of this node's
+ * source range. This is equivalent to `node.getOffset() + node.getLength()`. For an html
+ * unit this will be equal to the length of the unit's source.
*
- * @return the name (not `null`)
+ * @return the offset of the character just past the node's source range
*/
- String get name => _name.lexeme;
+ int get end => offset + length;
/**
- * Answer the attribute name token. This may be a zero length token if the attribute is badly
- * formed.
+ * Return the last token included in this node's source range.
*
- * @return the name token (not `null`)
+ * @return the last token or `null` if none
*/
- Token get nameToken => _name;
+ Token get endToken;
/**
- * Answer the lexeme for the value token without the leading and trailing quotes.
+ * Return the number of characters in the node's source range.
*
- * @return the text or `null` if the value is not specified
+ * @return the number of characters in the node's source range
*/
- String get text {
- if (_value == null) {
- return null;
- }
- //TODO (danrubel): replace HTML character encodings with the actual characters
- String text = _value.lexeme;
- int len = text.length;
- if (len > 0) {
- if (text.codeUnitAt(0) == 0x22) {
- if (len > 1 && text.codeUnitAt(len - 1) == 0x22) {
- return text.substring(1, len - 1);
- } else {
- return text.substring(1);
- }
- } else if (text.codeUnitAt(0) == 0x27) {
- if (len > 1 && text.codeUnitAt(len - 1) == 0x27) {
- return text.substring(1, len - 1);
- } else {
- return text.substring(1);
- }
- }
+ int get length {
+ Token beginToken = this.beginToken;
+ Token endToken = this.endToken;
+ if (beginToken == null || endToken == null) {
+ return -1;
}
- return text;
+ return endToken.offset + endToken.length - beginToken.offset;
}
/**
- * Answer the offset of the value after the leading quote.
+ * Return the offset from the beginning of the file to the first character in the node's source
+ * range.
*
- * @return the offset of the value, or `-1` if the value is not specified
+ * @return the offset from the beginning of the file to the first character in the node's source
+ * range
*/
- int get textOffset {
- if (_value == null) {
+ int get offset {
+ Token beginToken = this.beginToken;
+ if (beginToken == null) {
return -1;
}
- String text = _value.lexeme;
- if (StringUtilities.startsWithChar(text, 0x22) || StringUtilities.startsWithChar(text, 0x27)) {
- return _value.offset + 1;
- }
- return _value.offset;
+ return this.beginToken.offset;
}
/**
- * Answer the attribute value token. A properly formed value will start and end with matching
- * quote characters, but the value returned may not be properly formed.
+ * Return this node's parent node, or `null` if this node is the root of an AST structure.
*
- * @return the value token or `null` if this represents a badly formed attribute
+ * Note that the relationship between an AST node and its parent node may change over the lifetime
+ * of a node.
+ *
+ * @return the parent of this node, or `null` if none
*/
- Token get valueToken => _value;
+ XmlNode get parent => _parent;
- @override
- void visitChildren(XmlVisitor visitor) {
+ /**
+ * Set the element associated with this node.
+ *
+ * @param element the element
+ */
+ void set element(Element element) {
+ this._element = element;
}
-}
-
-/**
- * The interface `XmlVisitor` defines the behavior of objects that can be used to visit an
- * [XmlNode] structure.
- */
-abstract class XmlVisitor<R> {
- R visitHtmlScriptTagNode(HtmlScriptTagNode node);
-
- R visitHtmlUnit(HtmlUnit htmlUnit);
-
- R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode);
- R visitXmlTagNode(XmlTagNode xmlTagNode);
-}
+ @override
+ String toString() {
+ PrintStringWriter writer = new PrintStringWriter();
+ accept(new ToSourceVisitor(writer));
+ return writer.toString();
+ }
-/**
- * Instances of the class `XmlExpression` represent an abstract expression embedded into
- * [XmlNode].
- */
-abstract class XmlExpression {
/**
- * An empty array of expressions.
+ * Use the given visitor to visit all of the children of this node. The children will be visited
+ * in source order.
+ *
+ * @param visitor the visitor that will be used to visit the children of this node
*/
- static List<XmlExpression> EMPTY_ARRAY = new List<XmlExpression>(0);
+ void visitChildren(XmlVisitor visitor);
/**
- * Check if the given offset belongs to the expression's source range.
+ * Make this node the parent of the given child node.
+ *
+ * @param child the node that will become a child of this node
+ * @return the node that was made a child of this node
*/
- bool contains(int offset) => this.offset <= offset && offset < end;
+ XmlNode becomeParentOf(XmlNode child) {
+ if (child != null) {
+ XmlNode node = child;
+ node.parent = this;
+ }
+ return child;
+ }
/**
- * Return the offset of the character immediately following the last character of this
- * expression's source range. This is equivalent to `getOffset() + getLength()`.
+ * Make this node the parent of the given child nodes.
*
- * @return the offset of the character just past the expression's source range
+ * @param children the nodes that will become the children of this node
+ * @param ifEmpty the (empty) nodes to return if "children" is empty
+ * @return the nodes that were made children of this node
*/
- int get end;
+ List becomeParentOfAll(List children, {List ifEmpty}) {
+ if (children == null || children.isEmpty) {
+ if (ifEmpty != null) {
+ return ifEmpty;
+ }
+ }
+ if (children != null) {
+ for (JavaIterator iter = new JavaIterator(children); iter.hasNext;) {
+ XmlNode node = iter.next();
+ node.parent = this;
+ }
+ // This will create ArrayList for exactly given number of elements.
+ return new List.from(children);
+ }
+ return children;
+ }
/**
- * Return the number of characters in the expression's source range.
+ * This method exists for debugging purposes only.
*/
- int get length;
+ void _appendIdentifier(JavaStringBuilder builder, XmlNode node) {
+ if (node is XmlTagNode) {
+ builder.append(node.tag);
+ } else if (node is XmlAttributeNode) {
+ builder.append(node.name);
+ } else {
+ builder.append("htmlUnit");
+ }
+ }
/**
- * Return the offset of the first character in the expression's source range.
+ * This method exists for debugging purposes only.
*/
- int get offset;
+ String _buildRecursiveStructureMessage(XmlNode newParent) {
+ JavaStringBuilder builder = new JavaStringBuilder();
+ builder.append("Attempt to create recursive structure: ");
+ XmlNode current = newParent;
+ while (current != null) {
+ if (!identical(current, newParent)) {
+ builder.append(" -> ");
+ }
+ if (identical(current, this)) {
+ builder.appendChar(0x2A);
+ _appendIdentifier(builder, current);
+ builder.appendChar(0x2A);
+ } else {
+ _appendIdentifier(builder, current);
+ }
+ current = current.parent;
+ }
+ return builder.toString();
+ }
/**
- * Return the [Reference] at the given offset.
+ * Set the parent of this node to the given node.
*
- * @param offset the offset from the beginning of the file
- * @return the [Reference] at the given offset, maybe `null`
+ * @param newParent the node that is to be made the parent of this node
*/
- XmlExpression_Reference getReference(int offset);
+ void set parent(XmlNode newParent) {
+ XmlNode current = newParent;
+ while (current != null) {
+ if (identical(current, this)) {
+ AnalysisEngine.instance.logger.logError2("Circular structure while setting an XML node's parent", new IllegalArgumentException(_buildRecursiveStructureMessage(newParent)));
+ return;
+ }
+ current = current.parent;
+ }
+ _parent = newParent;
+ }
}
/**
- * The reference to the [Element].
+ * Implementation of [XmlExpression] for an [Expression] embedded without any wrapping
+ * characters.
*/
-class XmlExpression_Reference {
- Element element;
+class RawXmlExpression extends XmlExpression {
+ final Expression expression;
- int offset = 0;
+ RawXmlExpression(this.expression);
- int length = 0;
+ @override
+ int get end => expression.end;
- XmlExpression_Reference(Element element, int offset, int length) {
- this.element = element;
- this.offset = offset;
- this.length = length;
+ @override
+ int get length => expression.length;
+
+ @override
+ int get offset => expression.offset;
+
+ @override
+ XmlExpression_Reference getReference(int offset) {
+ AstNode node = new NodeLocator.con1(offset).searchWithin(expression);
+ if (node != null) {
+ Element element = ElementLocator.locate(node);
+ return new XmlExpression_Reference(element, node.offset, node.length);
+ }
+ return null;
}
}
/**
- * Instances of the class `XmlParser` are used to parse tokens into a AST structure comprised
- * of [XmlNode]s.
+ * Utilities locating [Expression]s and [Element]s in [HtmlUnit].
*/
-class XmlParser {
- /**
- * The source being parsed.
- */
- final Source source;
-
- /**
- * The next token to be parsed.
- */
- Token _currentToken;
-
- /**
- * Construct a parser for the specified source.
- *
- * @param source the source being parsed
- */
- XmlParser(this.source);
-
+class HtmlUnitUtils {
/**
- * Create a node representing an attribute.
- *
- * @param name the name of the attribute
- * @param equals the equals sign, or `null` if there is no value
- * @param value the value of the attribute
- * @return the node that was created
+ * Returns the [XmlAttributeNode] that is part of the given [HtmlUnit] and encloses
+ * the given offset.
*/
- XmlAttributeNode createAttributeNode(Token name, Token equals, Token value) => new XmlAttributeNode(name, equals, value);
+ static XmlAttributeNode getAttributeNode(HtmlUnit htmlUnit, int offset) {
+ if (htmlUnit == null) {
+ return null;
+ }
+ List<XmlAttributeNode> result = [null];
+ try {
+ htmlUnit.accept(new RecursiveXmlVisitor_HtmlUnitUtils_getAttributeNode(offset, result));
+ } on HtmlUnitUtils_FoundAttributeNodeError catch (e) {
+ return result[0];
+ }
+ return null;
+ }
/**
- * Create a node representing a tag.
- *
- * @param nodeStart the token marking the beginning of the tag
- * @param tag the name of the tag
- * @param attributes the attributes in the tag
- * @param attributeEnd the token terminating the region where attributes can be
- * @param tagNodes the children of the tag
- * @param contentEnd the token that starts the closing tag
- * @param closingTag the name of the tag that occurs in the closing tag
- * @param nodeEnd the last token in the tag
- * @return the node that was created
+ * Returns the best [Element] of the given [Expression].
*/
- XmlTagNode createTagNode(Token nodeStart, Token tag, List<XmlAttributeNode> attributes, Token attributeEnd, List<XmlTagNode> tagNodes, Token contentEnd, Token closingTag, Token nodeEnd) => new XmlTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes, contentEnd, closingTag, nodeEnd);
+ static Element getElement(Expression expression) {
+ if (expression == null) {
+ return null;
+ }
+ return ElementLocator.locate(expression);
+ }
/**
- * Answer `true` if the specified tag is self closing and thus should never have content or
- * child tag nodes.
- *
- * @param tag the tag (not `null`)
- * @return `true` if self closing
+ * Returns the [Element] of the [Expression] in the given [HtmlUnit], enclosing
+ * the given offset.
*/
- bool isSelfClosing(Token tag) => false;
+ static Element getElementAtOffset(HtmlUnit htmlUnit, int offset) {
+ Expression expression = getExpression(htmlUnit, offset);
+ return getElement(expression);
+ }
/**
- * Parse the entire token stream and in the process, advance the current token to the end of the
- * token stream.
- *
- * @return the list of tag nodes found (not `null`, contains no `null`)
+ * Returns the [Element] to open when requested at the given [Expression].
*/
- List<XmlTagNode> parseTopTagNodes(Token firstToken) {
- _currentToken = firstToken;
- List<XmlTagNode> tagNodes = new List<XmlTagNode>();
- TokenType type = _currentToken.type;
- while (type != TokenType.EOF) {
- if (type == TokenType.LT) {
- tagNodes.add(_parseTagNode());
- } else if (type == TokenType.DECLARATION || type == TokenType.DIRECTIVE || type == TokenType.COMMENT) {
- // ignored tokens
- _currentToken = _currentToken.next;
- } else {
- _reportUnexpectedToken();
- _currentToken = _currentToken.next;
+ static Element getElementToOpen(HtmlUnit htmlUnit, Expression expression) {
+ Element element = getElement(expression);
+ {
+ AngularElement angularElement = AngularHtmlUnitResolver.getAngularElement(element);
+ if (angularElement != null) {
+ return angularElement;
}
- type = _currentToken.type;
}
- return tagNodes;
+ return element;
}
/**
- * Answer the current token.
- *
- * @return the current token
- */
- Token get currentToken => _currentToken;
-
- /**
- * Insert a synthetic token of the specified type before the current token
- *
- * @param type the type of token to be inserted (not `null`)
- * @return the synthetic token that was inserted (not `null`)
+ * Returns the [XmlTagNode] that is part of the given [HtmlUnit] and encloses the
+ * given offset.
*/
- Token _insertSyntheticToken(TokenType type) {
- Token token = new Token.con2(type, _currentToken.offset, "");
- _currentToken.previous.setNext(token);
- token.setNext(_currentToken);
- return token;
+ static XmlTagNode getEnclosingTagNode(HtmlUnit htmlUnit, int offset) {
+ if (htmlUnit == null) {
+ return null;
+ }
+ List<XmlTagNode> result = [null];
+ try {
+ htmlUnit.accept(new RecursiveXmlVisitor_HtmlUnitUtils_getEnclosingTagNode(offset, result));
+ } on HtmlUnitUtils_FoundTagNodeError catch (e) {
+ return result[0];
+ }
+ return null;
}
/**
- * Parse the token stream for an attribute. This method advances the current token over the
- * attribute, but should not be called if the [currentToken] is not [TokenType#TAG].
- *
- * @return the attribute (not `null`)
+ * Returns the [Expression] that is part of the given [HtmlUnit] and encloses the
+ * given offset.
*/
- XmlAttributeNode _parseAttribute() {
- // Assume the current token is a tag
- Token name = _currentToken;
- _currentToken = _currentToken.next;
- // Equals sign
- Token equals;
- if (_currentToken.type == TokenType.EQ) {
- equals = _currentToken;
- _currentToken = _currentToken.next;
- } else {
- _reportUnexpectedToken();
- equals = _insertSyntheticToken(TokenType.EQ);
- }
- // String value
- Token value;
- if (_currentToken.type == TokenType.STRING) {
- value = _currentToken;
- _currentToken = _currentToken.next;
- } else {
- _reportUnexpectedToken();
- value = _insertSyntheticToken(TokenType.STRING);
+ static Expression getExpression(HtmlUnit htmlUnit, int offset) {
+ if (htmlUnit == null) {
+ return null;
}
- return createAttributeNode(name, equals, value);
+ List<Expression> result = [null];
+ try {
+ // TODO(scheglov) this code is very Angular specific
+ htmlUnit.accept(new ExpressionVisitor_HtmlUnitUtils_getExpression(offset, result));
+ } on HtmlUnitUtils_FoundExpressionError catch (e) {
+ return result[0];
+ }
+ return null;
}
/**
- * Parse the stream for a sequence of attributes. This method advances the current token to the
- * next [TokenType#GT], [TokenType#SLASH_GT], or [TokenType#EOF].
- *
- * @return a collection of zero or more attributes (not `null`, contains no `null`s)
+ * Returns the [XmlTagNode] that is part of the given [HtmlUnit] and its open or
+ * closing tag name encloses the given offset.
*/
- List<XmlAttributeNode> _parseAttributes() {
- TokenType type = _currentToken.type;
- if (type == TokenType.GT || type == TokenType.SLASH_GT || type == TokenType.EOF) {
- return XmlTagNode.NO_ATTRIBUTES;
+ static XmlTagNode getTagNode(HtmlUnit htmlUnit, int offset) {
+ XmlTagNode node = getEnclosingTagNode(htmlUnit, offset);
+ // do we have an enclosing tag at all?
+ if (node == null) {
+ return null;
}
- List<XmlAttributeNode> attributes = new List<XmlAttributeNode>();
- while (type != TokenType.GT && type != TokenType.SLASH_GT && type != TokenType.EOF) {
- if (type == TokenType.TAG) {
- attributes.add(_parseAttribute());
- } else {
- _reportUnexpectedToken();
- _currentToken = _currentToken.next;
- }
- type = _currentToken.type;
+ // is "offset" in the open tag?
+ Token openTag = node.tagToken;
+ if (openTag.offset <= offset && offset <= openTag.end) {
+ return node;
}
- return attributes;
+ // is "offset" in the open tag?
+ Token closeTag = node.closingTag;
+ if (closeTag != null && closeTag.offset <= offset && offset <= closeTag.end) {
+ return node;
+ }
+ // not on a tag name
+ return null;
}
/**
- * Parse the stream for a sequence of tag nodes existing within a parent tag node. This method
- * advances the current token to the next [TokenType#LT_SLASH] or [TokenType#EOF].
- *
- * @return a list of nodes (not `null`, contains no `null`s)
+ * Returns the [Expression] that is part of the given root [AstNode] and encloses the
+ * given offset.
*/
- List<XmlTagNode> _parseChildTagNodes() {
- TokenType type = _currentToken.type;
- if (type == TokenType.LT_SLASH || type == TokenType.EOF) {
- return XmlTagNode.NO_TAG_NODES;
- }
- List<XmlTagNode> nodes = new List<XmlTagNode>();
- while (type != TokenType.LT_SLASH && type != TokenType.EOF) {
- if (type == TokenType.LT) {
- nodes.add(_parseTagNode());
- } else if (type == TokenType.COMMENT) {
- // ignored token
- _currentToken = _currentToken.next;
- } else {
- _reportUnexpectedToken();
- _currentToken = _currentToken.next;
+ static Expression _getExpressionAt(AstNode root, int offset) {
+ if (root.offset <= offset && offset <= root.end) {
+ AstNode dartNode = new NodeLocator.con1(offset).searchWithin(root);
+ if (dartNode is Expression) {
+ return dartNode;
}
- type = _currentToken.type;
}
- return nodes;
+ return null;
+ }
+}
+
+class HtmlUnitUtils_FoundAttributeNodeError extends Error {
+}
+
+class HtmlUnitUtils_FoundExpressionError extends Error {
+}
+
+class HtmlUnitUtils_FoundTagNodeError extends Error {
+}
+
+class RecursiveXmlVisitor_HtmlUnitUtils_getAttributeNode extends RecursiveXmlVisitor<Object> {
+ int offset = 0;
+
+ List<XmlAttributeNode> result;
+
+ RecursiveXmlVisitor_HtmlUnitUtils_getAttributeNode(this.offset, this.result) : super();
+
+ @override
+ Object visitXmlAttributeNode(XmlAttributeNode node) {
+ Token nameToken = node.nameToken;
+ if (nameToken.offset <= offset && offset <= nameToken.end) {
+ result[0] = node;
+ throw new HtmlUnitUtils_FoundAttributeNodeError();
+ }
+ return super.visitXmlAttributeNode(node);
+ }
+}
+
+class RecursiveXmlVisitor_HtmlUnitUtils_getEnclosingTagNode extends RecursiveXmlVisitor<Object> {
+ int offset = 0;
+
+ List<XmlTagNode> result;
+
+ RecursiveXmlVisitor_HtmlUnitUtils_getEnclosingTagNode(this.offset, this.result) : super();
+
+ @override
+ Object visitXmlTagNode(XmlTagNode node) {
+ if (node.offset <= offset && offset < node.end) {
+ result[0] = node;
+ super.visitXmlTagNode(node);
+ throw new HtmlUnitUtils_FoundTagNodeError();
+ }
+ return null;
+ }
+}
+
+class ExpressionVisitor_HtmlUnitUtils_getExpression extends ExpressionVisitor {
+ int offset = 0;
+
+ List<Expression> result;
+
+ ExpressionVisitor_HtmlUnitUtils_getExpression(this.offset, this.result) : super();
+
+ @override
+ void visitExpression(Expression expression) {
+ Expression at = HtmlUnitUtils._getExpressionAt(expression, offset);
+ if (at != null) {
+ result[0] = at;
+ throw new HtmlUnitUtils_FoundExpressionError();
+ }
}
+}
+
+/**
+ * The interface `XmlVisitor` defines the behavior of objects that can be used to visit an
+ * [XmlNode] structure.
+ */
+abstract class XmlVisitor<R> {
+ R visitHtmlScriptTagNode(HtmlScriptTagNode node);
+
+ R visitHtmlUnit(HtmlUnit htmlUnit);
+
+ R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode);
+
+ R visitXmlTagNode(XmlTagNode xmlTagNode);
+}
+/**
+ * Instances of the class `ToSourceVisitor` write a source representation of a visited XML
+ * node (and all of it's children) to a writer.
+ */
+class ToSourceVisitor implements XmlVisitor<Object> {
/**
- * Parse the token stream for the next tag node. This method advances current token over the
- * parsed tag node, but should only be called if the current token is [TokenType#LT]
+ * The writer to which the source is to be written.
+ */
+ final PrintWriter _writer;
+
+ /**
+ * Initialize a newly created visitor to write source code representing the visited nodes to the
+ * given writer.
*
- * @return the tag node or `null` if none found
+ * @param writer the writer to which the source is to be written
*/
- XmlTagNode _parseTagNode() {
- // Assume that the current node is a tag node start TokenType#LT
- Token nodeStart = _currentToken;
- _currentToken = _currentToken.next;
- // Get the tag or create a synthetic tag and report an error
- Token tag;
- if (_currentToken.type == TokenType.TAG) {
- tag = _currentToken;
- _currentToken = _currentToken.next;
- } else {
- _reportUnexpectedToken();
- tag = _insertSyntheticToken(TokenType.TAG);
+ ToSourceVisitor(this._writer);
+
+ @override
+ Object visitHtmlScriptTagNode(HtmlScriptTagNode node) => visitXmlTagNode(node);
+
+ @override
+ Object visitHtmlUnit(HtmlUnit node) {
+ for (XmlTagNode child in node.tagNodes) {
+ _visit(child);
}
- // Parse the attributes
- List<XmlAttributeNode> attributes = _parseAttributes();
- // Token ending attribute list
- Token attributeEnd;
- if (_currentToken.type == TokenType.GT || _currentToken.type == TokenType.SLASH_GT) {
- attributeEnd = _currentToken;
- _currentToken = _currentToken.next;
+ return null;
+ }
+
+ @override
+ Object visitXmlAttributeNode(XmlAttributeNode node) {
+ String name = node.name;
+ Token value = node.valueToken;
+ if (name.length == 0) {
+ _writer.print("__");
} else {
- _reportUnexpectedToken();
- attributeEnd = _insertSyntheticToken(TokenType.SLASH_GT);
- }
- // If the node has no children, then return the node
- if (attributeEnd.type == TokenType.SLASH_GT || isSelfClosing(tag)) {
- return createTagNode(nodeStart, tag, attributes, attributeEnd, XmlTagNode.NO_TAG_NODES, _currentToken, null, attributeEnd);
+ _writer.print(name);
}
- // Parse the child tag nodes
- List<XmlTagNode> tagNodes = _parseChildTagNodes();
- // Token ending child tag nodes
- Token contentEnd;
- if (_currentToken.type == TokenType.LT_SLASH) {
- contentEnd = _currentToken;
- _currentToken = _currentToken.next;
+ _writer.print("=");
+ if (value == null) {
+ _writer.print("__");
} else {
- // TODO (danrubel): handle self closing HTML elements by inserting synthetic tokens
- // but not reporting an error
- _reportUnexpectedToken();
- contentEnd = _insertSyntheticToken(TokenType.LT_SLASH);
+ _writer.print(value.lexeme);
}
- // Closing tag
- Token closingTag;
- if (_currentToken.type == TokenType.TAG) {
- closingTag = _currentToken;
- _currentToken = _currentToken.next;
- } else {
- _reportUnexpectedToken();
- closingTag = _insertSyntheticToken(TokenType.TAG);
+ return null;
+ }
+
+ @override
+ Object visitXmlTagNode(XmlTagNode node) {
+ _writer.print("<");
+ String tagName = node.tag;
+ _writer.print(tagName);
+ for (XmlAttributeNode attribute in node.attributes) {
+ _writer.print(" ");
+ _visit(attribute);
}
- // Token ending node
- Token nodeEnd;
- if (_currentToken.type == TokenType.GT) {
- nodeEnd = _currentToken;
- _currentToken = _currentToken.next;
- } else {
- _reportUnexpectedToken();
- nodeEnd = _insertSyntheticToken(TokenType.GT);
+ _writer.print(node.attributeEnd.lexeme);
+ if (node.closingTag != null) {
+ for (XmlTagNode child in node.tagNodes) {
+ _visit(child);
+ }
+ _writer.print("</");
+ _writer.print(tagName);
+ _writer.print(">");
}
- return createTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes, contentEnd, closingTag, nodeEnd);
+ return null;
}
/**
- * Report the current token as unexpected
+ * Safely visit the given node.
+ *
+ * @param node the node to be visited
*/
- void _reportUnexpectedToken() {
+ void _visit(XmlNode node) {
+ if (node != null) {
+ node.accept(this);
+ }
}
}
/**
- * Instances of `XmlTagNode` represent XML or HTML elements such as `` and
- * `<body foo="bar"> ... </body>`.
+ * Instances of the class `HtmlUnit` represent the contents of an HTML file.
*/
-class XmlTagNode extends XmlNode {
- /**
- * Constant representing empty list of attributes.
- */
- static List<XmlAttributeNode> NO_ATTRIBUTES = new UnmodifiableListView(new List<XmlAttributeNode>());
-
- /**
- * Constant representing empty list of tag nodes.
- */
- static List<XmlTagNode> NO_TAG_NODES = new UnmodifiableListView(new List<XmlTagNode>());
-
- /**
- * The starting [TokenType#LT] token (not `null`).
- */
- final Token nodeStart;
-
- /**
- * The [TokenType#TAG] token after the starting '&lt;' (not `null`).
- */
- final Token _tag;
-
+class HtmlUnit extends XmlNode {
/**
- * The attributes contained by the receiver (not `null`, contains no `null`s).
+ * The first token in the token stream that was parsed to form this HTML unit.
*/
- List<XmlAttributeNode> _attributes;
+ final Token beginToken;
/**
- * The [TokenType#GT] or [TokenType#SLASH_GT] token after the attributes (not
- * `null`). The token may be the same token as [nodeEnd] if there are no child
- * [tagNodes].
+ * The last token in the token stream that was parsed to form this compilation unit. This token
+ * should always have a type of [TokenType.EOF].
*/
- final Token attributeEnd;
+ final Token endToken;
/**
* The tag nodes contained in the receiver (not `null`, contains no `null`s).
@@ -1655,182 +1542,108 @@ class XmlTagNode extends XmlNode {
List<XmlTagNode> _tagNodes;
/**
- * The token (not `null`) after the content, which may be
- * * (1) [TokenType#LT_SLASH] for nodes with open and close tags, or
- * * (2) the [TokenType#LT] nodeStart of the next sibling node if this node is self
- * closing or the attributeEnd is [TokenType#SLASH_GT], or
- * * (3) [TokenType#EOF] if the node does not have a closing tag and is the last node in
- * the stream [TokenType#LT_SLASH] token after the content, or `null` if there is no
- * content and the attributes ended with [TokenType#SLASH_GT].
- */
- final Token contentEnd;
-
- /**
- * The closing [TokenType#TAG] after the child elements or `null` if there is no
- * content and the attributes ended with [TokenType#SLASH_GT]
- */
- final Token closingTag;
-
- /**
- * The ending [TokenType#GT] or [TokenType#SLASH_GT] token (not `null`).
- */
- final Token nodeEnd;
-
- /**
- * The expressions that are embedded in the tag's content.
- */
- List<XmlExpression> expressions = XmlExpression.EMPTY_ARRAY;
-
- /**
- * Construct a new instance representing an XML or HTML element
+ * Construct a new instance representing the content of an HTML file.
*
- * @param nodeStart the starting [TokenType#LT] token (not `null`)
- * @param tag the [TokenType#TAG] token after the starting '&lt;' (not `null`).
- * @param attributes the attributes associated with this element or [NO_ATTRIBUTES] (not
- * `null`, contains no `null`s)
- * @param attributeEnd The [TokenType#GT] or [TokenType#SLASH_GT] token after the
- * attributes (not `null`). The token may be the same token as [nodeEnd] if
- * there are no child [tagNodes].
- * @param tagNodes child tag nodes of the receiver or [NO_TAG_NODES] (not `null`,
- * contains no `null`s)
- * @param contentEnd the token (not `null`) after the content, which may be
- * * (1) [TokenType#LT_SLASH] for nodes with open and close tags, or
- * * (2) the [TokenType#LT] nodeStart of the next sibling node if this node is
- * self closing or the attributeEnd is [TokenType#SLASH_GT], or
- * * (3) [TokenType#EOF] if the node does not have a closing tag and is the last
- * node in the stream [TokenType#LT_SLASH] token after the content, or `null`
- * if there is no content and the attributes ended with [TokenType#SLASH_GT].
- * @param closingTag the closing [TokenType#TAG] after the child elements or `null` if
- * there is no content and the attributes ended with [TokenType#SLASH_GT]
- * @param nodeEnd the ending [TokenType#GT] or [TokenType#SLASH_GT] token (not
- * `null`)
+ * @param beginToken the first token in the file (not `null`)
+ * @param tagNodes child tag nodes of the receiver (not `null`, contains no `null`s)
+ * @param endToken the last token in the token stream which should be of type
+ * [TokenType.EOF]
*/
- XmlTagNode(this.nodeStart, this._tag, List<XmlAttributeNode> attributes, this.attributeEnd, List<XmlTagNode> tagNodes, this.contentEnd, this.closingTag, this.nodeEnd) {
- this._attributes = becomeParentOfAll(attributes, ifEmpty: NO_ATTRIBUTES);
- this._tagNodes = becomeParentOfAll(tagNodes, ifEmpty: NO_TAG_NODES);
+ HtmlUnit(this.beginToken, List<XmlTagNode> tagNodes, this.endToken) {
+ this._tagNodes = becomeParentOfAll(tagNodes);
}
@override
- accept(XmlVisitor visitor) => visitor.visitXmlTagNode(this);
-
- /**
- * Answer the attribute with the specified name.
- *
- * @param name the attribute name
- * @return the attribute or `null` if no matching attribute is found
- */
- XmlAttributeNode getAttribute(String name) {
- for (XmlAttributeNode attribute in _attributes) {
- if (attribute.name == name) {
- return attribute;
- }
- }
- return null;
- }
+ accept(XmlVisitor visitor) => visitor.visitHtmlUnit(this);
/**
- * Answer the receiver's attributes. Callers should not manipulate the returned list to edit the
- * AST structure.
+ * Return the element associated with this HTML unit.
*
- * @return the attributes (not `null`, contains no `null`s)
+ * @return the element or `null` if the receiver is not resolved
*/
- List<XmlAttributeNode> get attributes => _attributes;
+ @override
+ HtmlElement get element => super.element as HtmlElement;
/**
- * Find the attribute with the given name (see [getAttribute] and answer the lexeme
- * for the attribute's value token without the leading and trailing quotes (see
- * [XmlAttributeNode#getText]).
+ * Answer the tag nodes contained in the receiver. Callers should not manipulate the returned list
+ * to edit the AST structure.
*
- * @param name the attribute name
- * @return the attribute text or `null` if no matching attribute is found
+ * @return the children (not `null`, contains no `null`s)
*/
- String getAttributeText(String name) {
- XmlAttributeNode attribute = getAttribute(name);
- return attribute != null ? attribute.text : null;
- }
+ List<XmlTagNode> get tagNodes => _tagNodes;
@override
- Token get beginToken => nodeStart;
-
- /**
- * Answer a string representing the content contained in the receiver. This includes the textual
- * representation of any child tag nodes ([getTagNodes]). Whitespace between '&lt;',
- * '&lt;/', and '>', '/>' is discarded, but all other whitespace is preserved.
- *
- * @return the content (not `null`)
- */
- String get content {
- Token token = attributeEnd.next;
- if (identical(token, contentEnd)) {
- return "";
- }
- //TODO (danrubel): handle CDATA and replace HTML character encodings with the actual characters
- String content = token.lexeme;
- token = token.next;
- if (identical(token, contentEnd)) {
- return content;
- }
- JavaStringBuilder buffer = new JavaStringBuilder();
- while (!identical(token, contentEnd)) {
- buffer.append(token.lexeme);
- token = token.next;
+ void set element(Element element) {
+ if (element != null && element is! HtmlElement) {
+ throw new IllegalArgumentException("HtmlElement expected, but ${element.runtimeType} given");
}
- return buffer.toString();
+ super.element = element;
}
@override
- Token get endToken {
- if (nodeEnd != null) {
- return nodeEnd;
- }
- if (closingTag != null) {
- return closingTag;
- }
- if (contentEnd != null) {
- return contentEnd;
- }
- if (!_tagNodes.isEmpty) {
- return _tagNodes[_tagNodes.length - 1].endToken;
- }
- if (attributeEnd != null) {
- return attributeEnd;
- }
- if (!_attributes.isEmpty) {
- return _attributes[_attributes.length - 1].endToken;
+ void visitChildren(XmlVisitor visitor) {
+ for (XmlTagNode node in _tagNodes) {
+ node.accept(visitor);
}
- return _tag;
}
+}
+/**
+ * Instances of the class `StringScanner` implement a scanner that reads from a string. The
+ * scanning logic is in the superclass.
+ */
+class StringScanner extends AbstractScanner {
/**
- * Answer the tag name after the starting '&lt;'.
- *
- * @return the tag name (not `null`)
+ * The string from which characters will be read.
*/
- String get tag => _tag.lexeme;
+ final String _string;
/**
- * Answer the tag nodes contained in the receiver. Callers should not manipulate the returned list
- * to edit the AST structure.
- *
- * @return the children (not `null`, contains no `null`s)
+ * The number of characters in the string.
*/
- List<XmlTagNode> get tagNodes => _tagNodes;
+ int _stringLength = 0;
/**
- * Answer the [TokenType#TAG] token after the starting '&lt;'.
+ * The index, relative to the string, of the last character that was read.
+ */
+ int _charOffset = 0;
+
+ /**
+ * Initialize a newly created scanner to scan the characters in the given string.
*
- * @return the token (not `null`)
+ * @param source the source being scanned
+ * @param string the string from which characters will be read
*/
- Token get tagToken => _tag;
+ StringScanner(Source source, this._string) : super(source) {
+ this._stringLength = _string.length;
+ this._charOffset = -1;
+ }
@override
- void visitChildren(XmlVisitor visitor) {
- for (XmlAttributeNode node in _attributes) {
- node.accept(visitor);
+ int get offset => _charOffset;
+
+ void set offset(int offset) {
+ _charOffset = offset;
+ }
+
+ @override
+ int advance() {
+ if (++_charOffset < _stringLength) {
+ return _string.codeUnitAt(_charOffset);
}
- for (XmlTagNode node in _tagNodes) {
- node.accept(visitor);
+ _charOffset = _stringLength;
+ return -1;
+ }
+
+ @override
+ String getString(int start, int endDelta) => _string.substring(start, _charOffset + 1 + endDelta).toString();
+
+ @override
+ int peek() {
+ if (_charOffset + 1 < _stringLength) {
+ return _string.codeUnitAt(_charOffset + 1);
}
+ return -1;
}
}
@@ -1976,68 +1789,255 @@ class HtmlParser extends XmlParser {
}
/**
- * Instances of the class `HtmlUnit` represent the contents of an HTML file.
+ * Instances of the class `RecursiveXmlVisitor` implement an XML visitor that will recursively
+ * visit all of the nodes in an XML structure. For example, using an instance of this class to visit
+ * a [XmlTagNode] will also cause all of the contained [XmlAttributeNode]s and
+ * [XmlTagNode]s to be visited.
+ *
+ * Subclasses that override a visit method must either invoke the overridden visit method or must
+ * explicitly ask the visited node to visit its children. Failure to do so will cause the children
+ * of the visited node to not be visited.
*/
-class HtmlUnit extends XmlNode {
+class RecursiveXmlVisitor<R> implements XmlVisitor<R> {
+ @override
+ R visitHtmlScriptTagNode(HtmlScriptTagNode node) {
+ node.visitChildren(this);
+ return null;
+ }
+
+ @override
+ R visitHtmlUnit(HtmlUnit node) {
+ node.visitChildren(this);
+ return null;
+ }
+
+ @override
+ R visitXmlAttributeNode(XmlAttributeNode node) {
+ node.visitChildren(this);
+ return null;
+ }
+
+ @override
+ R visitXmlTagNode(XmlTagNode node) {
+ node.visitChildren(this);
+ return null;
+ }
+}
+
+/**
+ * Instances of the class `HtmlScriptTagNode` represent a script tag within an HTML file that
+ * references a Dart script.
+ */
+class HtmlScriptTagNode extends XmlTagNode {
/**
- * The first token in the token stream that was parsed to form this HTML unit.
+ * The AST structure representing the Dart code within this tag.
*/
- final Token beginToken;
+ CompilationUnit _script;
/**
- * The last token in the token stream that was parsed to form this compilation unit. This token
- * should always have a type of [TokenType.EOF].
+ * The element representing this script.
*/
- final Token endToken;
+ HtmlScriptElement scriptElement;
/**
- * The tag nodes contained in the receiver (not `null`, contains no `null`s).
+ * Initialize a newly created node to represent a script tag within an HTML file that references a
+ * Dart script.
+ *
+ * @param nodeStart the token marking the beginning of the tag
+ * @param tag the name of the tag
+ * @param attributes the attributes in the tag
+ * @param attributeEnd the token terminating the region where attributes can be
+ * @param tagNodes the children of the tag
+ * @param contentEnd the token that starts the closing tag
+ * @param closingTag the name of the tag that occurs in the closing tag
+ * @param nodeEnd the last token in the tag
*/
- List<XmlTagNode> _tagNodes;
+ HtmlScriptTagNode(Token nodeStart, Token tag, List<XmlAttributeNode> attributes, Token attributeEnd, List<XmlTagNode> tagNodes, Token contentEnd, Token closingTag, Token nodeEnd) : super(nodeStart, tag, attributes, attributeEnd, tagNodes, contentEnd, closingTag, nodeEnd);
+
+ @override
+ accept(XmlVisitor visitor) => visitor.visitHtmlScriptTagNode(this);
/**
- * Construct a new instance representing the content of an HTML file.
+ * Return the AST structure representing the Dart code within this tag, or `null` if this
+ * tag references an external script.
*
- * @param beginToken the first token in the file (not `null`)
- * @param tagNodes child tag nodes of the receiver (not `null`, contains no `null`s)
- * @param endToken the last token in the token stream which should be of type
- * [TokenType.EOF]
+ * @return the AST structure representing the Dart code within this tag
*/
- HtmlUnit(this.beginToken, List<XmlTagNode> tagNodes, this.endToken) {
- this._tagNodes = becomeParentOfAll(tagNodes);
+ CompilationUnit get script => _script;
+
+ /**
+ * Set the AST structure representing the Dart code within this tag to the given compilation unit.
+ *
+ * @param unit the AST structure representing the Dart code within this tag
+ */
+ void set script(CompilationUnit unit) {
+ _script = unit;
}
+}
+
+/**
+ * The enumeration `TokenType` defines the types of tokens that can be returned by the
+ * scanner.
+ */
+class TokenType extends Enum<TokenType> {
+ /**
+ * The type of the token that marks the end of the input.
+ */
+ static const TokenType EOF = const TokenType_EOF('EOF', 0, "");
+
+ static const TokenType EQ = const TokenType('EQ', 1, "=");
+
+ static const TokenType GT = const TokenType('GT', 2, ">");
+
+ static const TokenType LT_SLASH = const TokenType('LT_SLASH', 3, "</");
+
+ static const TokenType LT = const TokenType('LT', 4, "<");
+
+ static const TokenType SLASH_GT = const TokenType('SLASH_GT', 5, "/>");
+
+ static const TokenType COMMENT = const TokenType('COMMENT', 6, null);
+
+ static const TokenType DECLARATION = const TokenType('DECLARATION', 7, null);
+
+ static const TokenType DIRECTIVE = const TokenType('DIRECTIVE', 8, null);
+
+ static const TokenType STRING = const TokenType('STRING', 9, null);
+
+ static const TokenType TAG = const TokenType('TAG', 10, null);
+
+ static const TokenType TEXT = const TokenType('TEXT', 11, null);
+
+ static const List<TokenType> values = const [
+ EOF,
+ EQ,
+ GT,
+ LT_SLASH,
+ LT,
+ SLASH_GT,
+ COMMENT,
+ DECLARATION,
+ DIRECTIVE,
+ STRING,
+ TAG,
+ TEXT];
+
+ /**
+ * The lexeme that defines this type of token, or `null` if there is more than one possible
+ * lexeme for this type of token.
+ */
+ final String lexeme;
+
+ const TokenType(String name, int ordinal, this.lexeme) : super(name, ordinal);
+}
+
+class TokenType_EOF extends TokenType {
+ const TokenType_EOF(String name, int ordinal, String arg0) : super(name, ordinal, arg0);
@override
- accept(XmlVisitor visitor) => visitor.visitHtmlUnit(this);
+ String toString() => "-eof-";
+}
+
+/**
+ * Instances of `XmlAttributeNode` represent name/value pairs owned by an [XmlTagNode].
+ */
+class XmlAttributeNode extends XmlNode {
+ final Token _name;
+
+ final Token equals;
+
+ final Token _value;
+
+ List<XmlExpression> expressions = XmlExpression.EMPTY_ARRAY;
/**
- * Return the element associated with this HTML unit.
+ * Construct a new instance representing an XML attribute.
*
- * @return the element or `null` if the receiver is not resolved
+ * @param name the name token (not `null`). This may be a zero length token if the attribute
+ * is badly formed.
+ * @param equals the equals sign or `null` if none
+ * @param value the value token (not `null`)
*/
+ XmlAttributeNode(this._name, this.equals, this._value);
+
@override
- HtmlElement get element => super.element as HtmlElement;
+ accept(XmlVisitor visitor) => visitor.visitXmlAttributeNode(this);
+
+ @override
+ Token get beginToken => _name;
+
+ @override
+ Token get endToken => _value;
/**
- * Answer the tag nodes contained in the receiver. Callers should not manipulate the returned list
- * to edit the AST structure.
+ * Answer the attribute name. This may be a zero length string if the attribute is badly formed.
*
- * @return the children (not `null`, contains no `null`s)
+ * @return the name (not `null`)
*/
- List<XmlTagNode> get tagNodes => _tagNodes;
+ String get name => _name.lexeme;
- @override
- void set element(Element element) {
- if (element != null && element is! HtmlElement) {
- throw new IllegalArgumentException("HtmlElement expected, but ${element.runtimeType} given");
+ /**
+ * Answer the attribute name token. This may be a zero length token if the attribute is badly
+ * formed.
+ *
+ * @return the name token (not `null`)
+ */
+ Token get nameToken => _name;
+
+ /**
+ * Answer the lexeme for the value token without the leading and trailing quotes.
+ *
+ * @return the text or `null` if the value is not specified
+ */
+ String get text {
+ if (_value == null) {
+ return null;
}
- super.element = element;
+ //TODO (danrubel): replace HTML character encodings with the actual characters
+ String text = _value.lexeme;
+ int len = text.length;
+ if (len > 0) {
+ if (text.codeUnitAt(0) == 0x22) {
+ if (len > 1 && text.codeUnitAt(len - 1) == 0x22) {
+ return text.substring(1, len - 1);
+ } else {
+ return text.substring(1);
+ }
+ } else if (text.codeUnitAt(0) == 0x27) {
+ if (len > 1 && text.codeUnitAt(len - 1) == 0x27) {
+ return text.substring(1, len - 1);
+ } else {
+ return text.substring(1);
+ }
+ }
+ }
+ return text;
+ }
+
+ /**
+ * Answer the offset of the value after the leading quote.
+ *
+ * @return the offset of the value, or `-1` if the value is not specified
+ */
+ int get textOffset {
+ if (_value == null) {
+ return -1;
+ }
+ String text = _value.lexeme;
+ if (StringUtilities.startsWithChar(text, 0x22) || StringUtilities.startsWithChar(text, 0x27)) {
+ return _value.offset + 1;
+ }
+ return _value.offset;
}
+ /**
+ * Answer the attribute value token. A properly formed value will start and end with matching
+ * quote characters, but the value returned may not be properly formed.
+ *
+ * @return the value token or `null` if this represents a badly formed attribute
+ */
+ Token get valueToken => _value;
+
@override
void visitChildren(XmlVisitor visitor) {
- for (XmlTagNode node in _tagNodes) {
- node.accept(visitor);
- }
}
}

Powered by Google App Engine
This is Rietveld 408576698