| 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 '<' (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 '<' (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 '<',
|
| + * '</', 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 '<'.
|
| *
|
| - * @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 '<'.
|
| + *
|
| + * @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 '<' (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 '<' (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 '<',
|
| - * '</', 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 '<'.
|
| - *
|
| - * @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 '<'.
|
| + * 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);
|
| - }
|
| }
|
| }
|
|
|