blob: 206f55239e358d4b55aa05700f63fa7f80f8bd4c [file] [log] [blame]
// This code was auto-generated, is not intended to be edited, and is subject to
// significant change. Please see the README file for more information.
library engine.html;
import 'dart:collection';
import 'java_core.dart';
import 'java_engine.dart';
import 'source.dart';
import 'error.dart';
import 'instrumentation.dart';
import 'element.dart' show HtmlElementImpl;
/**
* Instances of the class {@code 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.
* @coverage dart.engine.html
*/
class Token {
/**
* The offset from the beginning of the file to the first character in the token.
*/
int _offset = 0;
/**
* The previous token in the token stream.
*/
Token _previous;
/**
* The next token in the token stream.
*/
Token _next;
/**
* The type of the token.
*/
TokenType _type;
/**
* The lexeme represented by this token.
*/
String _value;
/**
* Initialize a newly created token.
* @param type the token type (not {@code null})
* @param offset the offset from the beginning of the file to the first character in the token
*/
Token.con1(TokenType type, int offset) {
_jtd_constructor_155_impl(type, offset);
}
_jtd_constructor_155_impl(TokenType type, int offset) {
_jtd_constructor_156_impl(type, offset, type.lexeme);
}
/**
* Initialize a newly created token.
* @param type the token type (not {@code 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 {@code null})
*/
Token.con2(TokenType type2, int offset2, String value2) {
_jtd_constructor_156_impl(type2, offset2, value2);
}
_jtd_constructor_156_impl(TokenType type2, int offset2, String value2) {
this._type = type2;
this._value = StringUtilities.intern(value2);
this._offset = offset2;
}
/**
* 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
*/
int get end => _offset + length;
/**
* 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 => lexeme.length;
/**
* Return the lexeme that represents this token.
* @return the lexeme (not {@code null})
*/
String get lexeme => _value;
/**
* Return the next token in the token stream.
* @return the next token in the token stream
*/
Token get next => _next;
/**
* Return the offset from the beginning of the file to the first character in the token.
* @return the offset from the beginning of the file to the first character in the token
*/
int get offset => _offset;
/**
* Return the previous token in the token stream.
* @return the previous token in the token stream
*/
Token get previous => _previous;
/**
* Answer the token type for the receiver.
* @return the token type (not {@code null})
*/
TokenType get type => _type;
/**
* Return {@code 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 ({@code 0}).
* @return {@code true} if this token is a synthetic token
*/
bool 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;
}
String toString() => lexeme;
/**
* Set the previous token in the token stream to the given token.
* @param previous the previous token in the token stream
*/
void set previous(Token previous2) {
this._previous = previous2;
}
}
/**
* Instances of {@code HtmlParseResult} hold the result of parsing an HTML file.
* @coverage dart.engine.html
*/
class HtmlParseResult extends HtmlScanResult {
/**
* The unit containing the parsed information (not {@code null}).
*/
HtmlUnit _unit;
HtmlParseResult(int modificationTime, Token token, List<int> lineStarts, HtmlUnit unit) : super(modificationTime, token, lineStarts) {
this._unit = unit;
}
/**
* Answer the unit generated by parsing the source
* @return the unit (not {@code null})
*/
HtmlUnit get htmlUnit => _unit;
}
/**
* Instances of the class {@code 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 {@link XmlTagNode} will also cause all of the contained {@link XmlAttributeNode}s and{@link XmlTagNode}s to be visited.
* <p>
* 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.
* @coverage dart.engine.html
*/
class RecursiveXmlVisitor<R> implements XmlVisitor<R> {
R visitHtmlUnit(HtmlUnit node) {
node.visitChildren(this);
return null;
}
R visitXmlAttributeNode(XmlAttributeNode node) {
node.visitChildren(this);
return null;
}
R visitXmlTagNode(XmlTagNode node) {
node.visitChildren(this);
return null;
}
}
/**
* The abstract class {@code XmlNode} defines behavior common to all XML/HTML nodes.
* @coverage dart.engine.html
*/
abstract class XmlNode {
/**
* The parent of the node, or {@code null} if the node is the root of an AST structure.
*/
XmlNode _parent;
/**
* Use the given visitor to visit this node.
* @param visitor the visitor that will visit this node
* @return the value returned by the visitor as a result of visiting this node
*/
accept(XmlVisitor visitor);
/**
* Return the first token included in this node's source range.
* @return the first token or {@code null} if none
*/
Token get beginToken;
/**
* Return the offset of the character immediately following the last character of this node's
* source range. This is equivalent to {@code node.getOffset() + node.getLength()}. For an html
* unit this will be equal to the length of the unit's source.
* @return the offset of the character just past the node's source range
*/
int get end => offset + length;
/**
* Return the last token included in this node's source range.
* @return the last token or {@code null} if none
*/
Token get endToken;
/**
* 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 beginToken2 = beginToken;
Token endToken2 = endToken;
if (beginToken2 == null || endToken2 == null) {
return -1;
}
return endToken2.offset + endToken2.length - beginToken2.offset;
}
/**
* 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 beginToken2 = beginToken;
if (beginToken2 == null) {
return -1;
}
return beginToken.offset;
}
/**
* Return this node's parent node, or {@code null} if this node is the root of an AST structure.
* <p>
* 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 {@code null} if none
*/
XmlNode get parent => _parent;
String toString() {
PrintStringWriter writer = new PrintStringWriter();
accept(new ToSourceVisitor(writer));
return writer.toString();
}
/**
* 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
*/
void visitChildren(XmlVisitor<Object> visitor);
/**
* Make this node the parent of the given child nodes.
* @param children the nodes that will become the children of this node
* @return the nodes that were made children of this node
*/
List becomeParentOf(List children) {
if (children != null) {
for (JavaIterator iter = new JavaIterator(children); iter.hasNext;) {
XmlNode node = iter.next();
node.parent = this;
}
return new List.from(children);
}
return children;
}
/**
* 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
*/
XmlNode becomeParentOf2(XmlNode child) {
if (child != null) {
XmlNode node = child;
node.parent = this;
}
return child;
}
/**
* Set the parent of this node to the given node.
* @param newParent the node that is to be made the parent of this node
*/
void set parent(XmlNode newParent) {
_parent = newParent;
}
}
/**
* Instances of the class {@code 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 SimpleXmlVisitor<R> implements XmlVisitor<R> {
R visitHtmlUnit(HtmlUnit htmlUnit) => null;
R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode) => null;
R visitXmlTagNode(XmlTagNode xmlTagNode) => null;
}
/**
* The abstract class {@code AbstractScanner} implements a scanner for HTML code. Subclasses are
* required to implement the interface used to access the characters being scanned.
* @coverage dart.engine.html
*/
abstract class AbstractScanner {
static List<String> _NO_PASS_THROUGH_ELEMENTS = <String> [];
/**
* The source being scanned.
*/
Source _source;
/**
* The token pointing to the head of the linked list of tokens.
*/
Token _tokens;
/**
* The last token that was scanned.
*/
Token _tail;
/**
* A list containing the offsets of the first character of each line in the source code.
*/
List<int> _lineStarts = new List<int>();
/**
* An array of element tags for which the content between tags should be consider a single token.
*/
List<String> _passThroughElements = _NO_PASS_THROUGH_ELEMENTS;
/**
* Initialize a newly created scanner.
* @param source the source being scanned
*/
AbstractScanner(Source source) {
this._source = source;
_tokens = new Token.con1(TokenType.EOF, -1);
_tokens.setNext(_tokens);
_tail = _tokens;
recordStartOfLine();
}
/**
* Return an array containing the offsets of the first character of each line in the source code.
* @return an array containing the offsets of the first character of each line in the source code
*/
List<int> get lineStarts => _lineStarts;
/**
* 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 current offset of the scanner in the source
*/
int get offset;
/**
* Answer the source being scanned.
* @return the source or {@code null} if undefined
*/
Source get source => _source;
/**
* Set array of element tags for which the content between tags should be consider a single token.
*/
void set passThroughElements(List<String> passThroughElements2) {
this._passThroughElements = passThroughElements2 != null ? passThroughElements2 : _NO_PASS_THROUGH_ELEMENTS;
}
/**
* Scan the source code to produce a list of tokens representing the source.
* @return the first token in the list of tokens that were produced
*/
Token tokenize() {
scan();
appendEofToken();
return firstToken();
}
/**
* Advance the current position and return the character at the new current position.
* @return the character at the new current position
*/
int advance();
/**
* 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.
* @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
*/
String getString(int start, int endDelta);
/**
* Return the character at the current position without changing the current position.
* @return the character at the current position
*/
int peek();
/**
* Record the fact that we are at the beginning of a new line in the source.
*/
void recordStartOfLine() {
_lineStarts.add(offset);
}
void appendEofToken() {
Token eofToken = new Token.con1(TokenType.EOF, offset);
eofToken.setNext(eofToken);
_tail = _tail.setNext(eofToken);
}
Token emit(Token token) {
_tail.setNext(token);
_tail = token;
return token;
}
Token emit2(TokenType type, int start) => emit(new Token.con1(type, start));
Token emit3(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;
}
void scan() {
bool inBrackets = false;
bool passThrough = false;
int c = advance();
while (c >= 0) {
int start = offset;
if (c == 0x3C) {
c = advance();
if (c == 0x21) {
c = advance();
if (c == 0x2D && peek() == 0x2D) {
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);
}
emit3(TokenType.COMMENT, start, -1);
if (_tail.length < 7) {
}
} else {
while (c >= 0) {
if (c == 0x3E) {
c = advance();
break;
}
c = recordStartOfLineAndAdvance(c);
}
emit3(TokenType.DECLARATION, start, -1);
if (!_tail.lexeme.endsWith(">")) {
}
}
} else if (c == 0x3F) {
while (c >= 0) {
if (c == 0x3F) {
c = advance();
if (c == 0x3E) {
c = advance();
break;
}
} else {
c = recordStartOfLineAndAdvance(c);
}
}
emit3(TokenType.DIRECTIVE, start, -1);
if (_tail.length < 4) {
}
} else if (c == 0x2F) {
emit2(TokenType.LT_SLASH, start);
inBrackets = true;
c = advance();
} else {
inBrackets = true;
emit2(TokenType.LT, start);
while (Character.isWhitespace(c)) {
c = recordStartOfLineAndAdvance(c);
}
if (Character.isLetterOrDigit(c)) {
int tagStart = offset;
c = advance();
while (Character.isLetterOrDigit(c) || c == 0x2D || c == 0x5F) {
c = advance();
}
emit3(TokenType.TAG, tagStart, -1);
String tag = _tail.lexeme;
for (String str in _passThroughElements) {
if (str == tag) {
passThrough = true;
break;
}
}
}
}
} else if (c == 0x3E) {
emit2(TokenType.GT, start);
inBrackets = false;
c = advance();
if (passThrough) {
while (c >= 0 && (c != 0x3C || peek() != 0x2F)) {
c = recordStartOfLineAndAdvance(c);
}
if (start + 1 < offset) {
emit3(TokenType.TEXT, start + 1, -1);
}
passThrough = false;
}
} else if (c == 0x2F && peek() == 0x3E) {
advance();
emit2(TokenType.SLASH_GT, start);
inBrackets = false;
c = advance();
} else if (!inBrackets) {
c = recordStartOfLineAndAdvance(c);
while (c != 0x3C && c >= 0) {
c = recordStartOfLineAndAdvance(c);
}
emit3(TokenType.TEXT, start, -1);
} else if (c == 0x22 || c == 0x27) {
int endQuote = c;
c = advance();
while (c >= 0) {
if (c == endQuote) {
c = advance();
break;
}
c = recordStartOfLineAndAdvance(c);
}
emit3(TokenType.STRING, start, -1);
} else if (c == 0x3D) {
emit2(TokenType.EQ, start);
c = advance();
} else if (Character.isWhitespace(c)) {
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();
}
emit3(TokenType.TAG, start, -1);
} else {
emit3(TokenType.TEXT, start, 0);
c = advance();
}
}
}
}
/**
* Instances of {@code HtmlScanResult} hold the result of scanning an HTML file.
* @coverage dart.engine.html
*/
class HtmlScanResult {
/**
* The time at which the contents of the source were last set.
*/
int _modificationTime = 0;
/**
* The first token in the token stream (not {@code null}).
*/
Token _token;
/**
* The line start information that was produced.
*/
List<int> _lineStarts;
HtmlScanResult(int modificationTime, Token token, List<int> lineStarts) {
this._modificationTime = modificationTime;
this._token = token;
this._lineStarts = lineStarts;
}
/**
* Answer the line start information that was produced.
* @return an array of line starts (not {@code null})
*/
List<int> get lineStarts => _lineStarts;
/**
* Return the time at which the contents of the source were last set.
* @return the time at which the contents of the source were last set
*/
int get modificationTime => _modificationTime;
/**
* Answer the first token in the token stream.
* @return the token (not {@code null})
*/
Token get token => _token;
}
/**
* Instances of the class {@code StringScanner} implement a scanner that reads from a string. The
* scanning logic is in the superclass.
* @coverage dart.engine.html
*/
class StringScanner extends AbstractScanner {
/**
* The string from which characters will be read.
*/
String _string;
/**
* The number of characters in the string.
*/
int _stringLength = 0;
/**
* 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.
* @param source the source being scanned
* @param string the string from which characters will be read
*/
StringScanner(Source source, String string) : super(source) {
this._string = string;
this._stringLength = string.length;
this._charOffset = -1;
}
int get offset => _charOffset;
void set offset(int offset2) {
_charOffset = offset2;
}
int advance() {
if (++_charOffset < _stringLength) {
return _string.codeUnitAt(_charOffset);
}
_charOffset = _stringLength;
return -1;
}
String getString(int start, int endDelta) => _string.substring(start, _charOffset + 1 + endDelta);
int peek() {
if (_charOffset + 1 < _stringLength) {
return _string.codeUnitAt(_charOffset + 1);
}
return -1;
}
}
/**
* Instances of the class {@code CharBufferScanner} implement a scanner that reads from a character
* buffer. The scanning logic is in the superclass.
* @coverage dart.engine.html
*/
class CharBufferScanner extends AbstractScanner {
/**
* The buffer from which characters will be read.
*/
CharSequence _buffer;
/**
* The number of characters in the buffer.
*/
int _bufferLength = 0;
/**
* The index of the last character that was read.
*/
int _charOffset = 0;
/**
* Initialize a newly created scanner to scan the characters in the given character buffer.
* @param source the source being scanned
* @param buffer the buffer from which characters will be read
*/
CharBufferScanner(Source source, CharSequence buffer) : super(source) {
this._buffer = buffer;
this._bufferLength = buffer.length();
this._charOffset = -1;
}
int get offset => _charOffset;
int advance() {
if (++_charOffset < _bufferLength) {
return _buffer.charAt(_charOffset);
}
_charOffset = _bufferLength;
return -1;
}
String getString(int start, int endDelta) => _buffer.subSequence(start, _charOffset + 1 + endDelta).toString();
int peek() {
if (_charOffset + 1 < _bufferLength) {
return _buffer.charAt(_charOffset + 1);
}
return -1;
}
}
/**
* Instances of the class {@code ToSourceVisitor} write a source representation of a visited XML
* node (and all of it's children) to a writer.
* @coverage dart.engine.html
*/
class ToSourceVisitor implements XmlVisitor<Object> {
/**
* The writer to which the source is to be written.
*/
PrintWriter _writer;
/**
* Initialize a newly created visitor to write source code representing the visited nodes to the
* given writer.
* @param writer the writer to which the source is to be written
*/
ToSourceVisitor(PrintWriter writer) {
this._writer = writer;
}
Object visitHtmlUnit(HtmlUnit node) {
for (XmlTagNode child in node.tagNodes) {
visit(child);
}
return null;
}
Object visitXmlAttributeNode(XmlAttributeNode node) {
String name2 = node.name.lexeme;
Token value2 = node.value;
if (name2.length == 0) {
_writer.print("__");
} else {
_writer.print(name2);
}
_writer.print("=");
if (value2 == null) {
_writer.print("__");
} else {
_writer.print(value2.lexeme);
}
return null;
}
Object visitXmlTagNode(XmlTagNode node) {
_writer.print("<");
String tagName = node.tag.lexeme;
_writer.print(tagName);
for (XmlAttributeNode attribute in node.attributes) {
_writer.print(" ");
visit(attribute);
}
_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 null;
}
/**
* Safely visit the given node.
* @param node the node to be visited
*/
void visit(XmlNode node) {
if (node != null) {
node.accept(this);
}
}
}
/**
* The enumeration {@code TokenType} defines the types of tokens that can be returned by the
* scanner.
* @coverage dart.engine.html
*/
class TokenType implements Comparable<TokenType> {
/**
* The type of the token that marks the end of the input.
*/
static final TokenType EOF = new TokenType_EOF('EOF', 0, "");
static final TokenType EQ = new TokenType('EQ', 1, "=");
static final TokenType GT = new TokenType('GT', 2, ">");
static final TokenType LT_SLASH = new TokenType('LT_SLASH', 3, "</");
static final TokenType LT = new TokenType('LT', 4, "<");
static final TokenType SLASH_GT = new TokenType('SLASH_GT', 5, "/>");
static final TokenType COMMENT = new TokenType('COMMENT', 6, null);
static final TokenType DECLARATION = new TokenType('DECLARATION', 7, null);
static final TokenType DIRECTIVE = new TokenType('DIRECTIVE', 8, null);
static final TokenType STRING = new TokenType('STRING', 9, null);
static final TokenType TAG = new TokenType('TAG', 10, null);
static final TokenType TEXT = new TokenType('TEXT', 11, null);
static final List<TokenType> values = [EOF, EQ, GT, LT_SLASH, LT, SLASH_GT, COMMENT, DECLARATION, DIRECTIVE, STRING, TAG, TEXT];
/// The name of this enum constant, as declared in the enum declaration.
final String name;
/// The position in the enum declaration.
final int ordinal;
/**
* The lexeme that defines this type of token, or {@code null} if there is more than one possible
* lexeme for this type of token.
*/
String _lexeme;
TokenType(this.name, this.ordinal, String lexeme) {
this._lexeme = lexeme;
}
/**
* Return the lexeme that defines this type of token, or {@code null} if there is more than one
* possible lexeme for this type of token.
* @return the lexeme that defines this type of token
*/
String get lexeme => _lexeme;
int compareTo(TokenType other) => ordinal - other.ordinal;
String toString() => name;
}
class TokenType_EOF extends TokenType {
TokenType_EOF(String name, int ordinal, String arg0) : super(name, ordinal, arg0);
String toString() => "-eof-";
}
/**
* Instances of {@code XmlAttributeNode} represent name/value pairs owned by an {@link XmlTagNode}.
* @coverage dart.engine.html
*/
class XmlAttributeNode extends XmlNode {
Token _name;
Token _equals;
Token _value;
/**
* Construct a new instance representing an XML attribute.
* @param name the name token (not {@code null}). This may be a zero length token if the attribute
* is badly formed.
* @param equals the equals sign or {@code null} if none
* @param value the value token (not {@code null})
*/
XmlAttributeNode(Token name, Token equals, Token value) {
this._name = name;
this._equals = equals;
this._value = value;
}
accept(XmlVisitor visitor) => visitor.visitXmlAttributeNode(this);
Token get beginToken => _name;
Token get endToken => _value;
/**
* Answer the equals sign token that appears between the name and value tokens. This may be{@code null} if the attribute is badly formed.
* @return the token or {@code null} if there is no equals sign between the name and value
*/
Token get equals => _equals;
/**
* Answer the attribute name. This may be a zero length token if the attribute is badly formed.
* @return the name (not {@code null})
*/
Token get name => _name;
/**
* Answer the lexeme for the value token without the leading and trailing quotes.
* @return the text or {@code null} if the value is not specified
*/
String get text {
if (_value == null) {
return null;
}
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 attribute value. A properly formed value will start and end with matching quote
* characters, but the value returned may not be properly formed.
* @return the value or {@code null} if this represents a badly formed attribute
*/
Token get value => _value;
void visitChildren(XmlVisitor<Object> visitor) {
}
}
/**
* The interface {@code XmlVisitor} defines the behavior of objects that can be used to visit an{@link XmlNode} structure.
* @coverage dart.engine.html
*/
abstract class XmlVisitor<R> {
R visitHtmlUnit(HtmlUnit htmlUnit);
R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode);
R visitXmlTagNode(XmlTagNode xmlTagNode);
}
/**
* Instances of {@code HtmlScanner} receive and scan HTML content from a {@link Source}.<br/>
* For example, the following code scans HTML source and returns the result:
* <pre>
* HtmlScanner scanner = new HtmlScanner(source);
* source.getContents(scanner);
* return scanner.getResult();
* </pre>
* @coverage dart.engine.html
*/
class HtmlScanner implements Source_ContentReceiver {
List<String> _SCRIPT_TAG = <String> ["script"];
/**
* The source being scanned (not {@code null})
*/
Source _source;
/**
* The time at which the contents of the source were last set.
*/
int _modificationTime = 0;
/**
* The scanner used to scan the source
*/
AbstractScanner _scanner;
/**
* The first token in the token stream.
*/
Token _token;
/**
* Construct a new instance to scan the specified source.
* @param source the source to be scanned (not {@code null})
*/
HtmlScanner(Source source) {
this._source = source;
}
void accept(CharBuffer contents, int modificationTime2) {
this._modificationTime = modificationTime2;
_scanner = new CharBufferScanner(_source, contents);
_scanner.passThroughElements = _SCRIPT_TAG;
_token = _scanner.tokenize();
}
void accept2(String contents, int modificationTime2) {
this._modificationTime = modificationTime2;
_scanner = new StringScanner(_source, contents);
_scanner.passThroughElements = _SCRIPT_TAG;
_token = _scanner.tokenize();
}
/**
* Answer the result of scanning the source
* @return the result (not {@code null})
*/
HtmlScanResult get result => new HtmlScanResult(_modificationTime, _token, _scanner.lineStarts);
}
/**
* Instances of the class {@code XmlParser} are used to parse tokens into a AST structure comprised
* of {@link XmlNode}s.
* @coverage dart.engine.html
*/
class XmlParser {
/**
* The source being parsed.
*/
Source _source;
/**
* The next token to be parsed.
*/
Token _currentToken;
/**
* Construct a parser for the specified source.
* @param source the source being parsed
*/
XmlParser(Source source) {
this._source = source;
}
/**
* Answer the source being parsed.
* @return the source
*/
Source get source => _source;
/**
* Answer {@code true} if the specified tag is self closing and thus should never have content or
* child tag nodes.
* @param tag the tag (not {@code null})
* @return {@code true} if self closing
*/
bool isSelfClosing(Token tag) => false;
/**
* 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 {@code null}, contains no {@code null})
*/
List<XmlTagNode> parseTopTagNodes(Token firstToken) {
_currentToken = firstToken;
List<XmlTagNode> tagNodes = new List<XmlTagNode>();
while (true) {
while (true) {
if (_currentToken.type == TokenType.LT) {
tagNodes.add(parseTagNode());
} else if (_currentToken.type == TokenType.DECLARATION || _currentToken.type == TokenType.DIRECTIVE || _currentToken.type == TokenType.COMMENT) {
_currentToken = _currentToken.next;
} else if (_currentToken.type == TokenType.EOF) {
return tagNodes;
} else {
reportUnexpectedToken();
_currentToken = _currentToken.next;
}
break;
}
}
}
/**
* 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 {@code null})
* @return the synthetic token that was inserted (not {@code null})
*/
Token insertSyntheticToken(TokenType type) {
Token token = new Token.con2(type, _currentToken.offset, "");
_currentToken.previous.setNext(token);
token.setNext(_currentToken);
return token;
}
/**
* Parse the token stream for an attribute. This method advances the current token over the
* attribute, but should not be called if the {@link #currentToken} is not {@link TokenType#TAG}.
* @return the attribute (not {@code null})
*/
XmlAttributeNode parseAttribute() {
Token name = _currentToken;
_currentToken = _currentToken.next;
Token equals;
if (identical(_currentToken.type, TokenType.EQ)) {
equals = _currentToken;
_currentToken = _currentToken.next;
} else {
reportUnexpectedToken();
equals = insertSyntheticToken(TokenType.EQ);
}
Token value;
if (identical(_currentToken.type, TokenType.STRING)) {
value = _currentToken;
_currentToken = _currentToken.next;
} else {
reportUnexpectedToken();
value = insertSyntheticToken(TokenType.STRING);
}
return new XmlAttributeNode(name, equals, value);
}
/**
* Parse the stream for a sequence of attributes. This method advances the current token to the
* next {@link TokenType#GT}, {@link TokenType#SLASH_GT}, or {@link TokenType#EOF}.
* @return a collection of zero or more attributes (not {@code null}, contains no {@code null}s)
*/
List<XmlAttributeNode> parseAttributes() {
TokenType type2 = _currentToken.type;
if (identical(type2, TokenType.GT) || identical(type2, TokenType.SLASH_GT) || identical(type2, TokenType.EOF)) {
return XmlTagNode.NO_ATTRIBUTES;
}
List<XmlAttributeNode> attributes = new List<XmlAttributeNode>();
while (true) {
while (true) {
if (_currentToken.type == TokenType.GT || _currentToken.type == TokenType.SLASH_GT || _currentToken.type == TokenType.EOF) {
return attributes;
} else if (_currentToken.type == TokenType.TAG) {
attributes.add(parseAttribute());
} else {
reportUnexpectedToken();
_currentToken = _currentToken.next;
}
break;
}
}
}
/**
* Parse the stream for a sequence of tag nodes existing within a parent tag node. This method
* advances the current token to the next {@link TokenType#LT_SLASH} or {@link TokenType#EOF}.
* @return a list of nodes (not {@code null}, contains no {@code null}s)
*/
List<XmlTagNode> parseChildTagNodes() {
TokenType type2 = _currentToken.type;
if (identical(type2, TokenType.LT_SLASH) || identical(type2, TokenType.EOF)) {
return XmlTagNode.NO_TAG_NODES;
}
List<XmlTagNode> nodes = new List<XmlTagNode>();
while (true) {
while (true) {
if (_currentToken.type == TokenType.LT) {
nodes.add(parseTagNode());
} else if (_currentToken.type == TokenType.LT_SLASH || _currentToken.type == TokenType.EOF) {
return nodes;
} else if (_currentToken.type == TokenType.COMMENT) {
_currentToken = _currentToken.next;
} else {
reportUnexpectedToken();
_currentToken = _currentToken.next;
}
break;
}
}
}
/**
* 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 {@link TokenType#LT}
* @return the tag node or {@code null} if none found
*/
XmlTagNode parseTagNode() {
Token nodeStart = _currentToken;
_currentToken = _currentToken.next;
Token tag;
if (identical(_currentToken.type, TokenType.TAG)) {
tag = _currentToken;
_currentToken = _currentToken.next;
} else {
reportUnexpectedToken();
tag = insertSyntheticToken(TokenType.TAG);
}
List<XmlAttributeNode> attributes = parseAttributes();
Token attributeEnd;
if (identical(_currentToken.type, TokenType.GT) || identical(_currentToken.type, TokenType.SLASH_GT)) {
attributeEnd = _currentToken;
_currentToken = _currentToken.next;
} else {
reportUnexpectedToken();
attributeEnd = insertSyntheticToken(TokenType.SLASH_GT);
}
if (identical(attributeEnd.type, TokenType.SLASH_GT) || isSelfClosing(tag)) {
return new XmlTagNode(nodeStart, tag, attributes, attributeEnd, XmlTagNode.NO_TAG_NODES, _currentToken, null, attributeEnd);
}
List<XmlTagNode> tagNodes = parseChildTagNodes();
Token contentEnd;
if (identical(_currentToken.type, TokenType.LT_SLASH)) {
contentEnd = _currentToken;
_currentToken = _currentToken.next;
} else {
reportUnexpectedToken();
contentEnd = insertSyntheticToken(TokenType.LT_SLASH);
}
Token closingTag;
if (identical(_currentToken.type, TokenType.TAG)) {
closingTag = _currentToken;
_currentToken = _currentToken.next;
} else {
reportUnexpectedToken();
closingTag = insertSyntheticToken(TokenType.TAG);
}
Token nodeEnd;
if (identical(_currentToken.type, TokenType.GT)) {
nodeEnd = _currentToken;
_currentToken = _currentToken.next;
} else {
reportUnexpectedToken();
nodeEnd = insertSyntheticToken(TokenType.GT);
}
return new XmlTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes, contentEnd, closingTag, nodeEnd);
}
/**
* Report the current token as unexpected
*/
void reportUnexpectedToken() {
}
}
/**
* Instances of {@code XmlTagNode} represent XML or HTML elements such as {@code <p>} and{@code <body foo="bar"> ... </body>}.
* @coverage dart.engine.html
*/
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 {@link TokenType#LT} token (not {@code null}).
*/
Token _nodeStart;
/**
* The {@link TokenType#TAG} token after the starting '&lt;' (not {@code null}).
*/
Token _tag;
/**
* The attributes contained by the receiver (not {@code null}, contains no {@code null}s).
*/
List<XmlAttributeNode> _attributes;
/**
* The {@link TokenType#GT} or {@link TokenType#SLASH_GT} token after the attributes (not{@code null}). The token may be the same token as {@link #nodeEnd} if there are no child{@link #tagNodes}.
*/
Token _attributeEnd;
/**
* The tag nodes contained in the receiver (not {@code null}, contains no {@code null}s).
*/
List<XmlTagNode> _tagNodes;
/**
* The token (not {@code null}) after the content, which may be
* <ul>
* <li>(1) {@link TokenType#LT_SLASH} for nodes with open and close tags, or</li>
* <li>(2) the {@link TokenType#LT} nodeStart of the next sibling node if this node is self
* closing or the attributeEnd is {@link TokenType#SLASH_GT}, or</li>
* <li>(3) {@link TokenType#EOF} if the node does not have a closing tag and is the last node in
* the stream {@link TokenType#LT_SLASH} token after the content, or {@code null} if there is no
* content and the attributes ended with {@link TokenType#SLASH_GT}.</li>
* </ul>
*/
Token _contentEnd;
/**
* The closing {@link TokenType#TAG} after the child elements or {@code null} if there is no
* content and the attributes ended with {@link TokenType#SLASH_GT}
*/
Token _closingTag;
/**
* The ending {@link TokenType#GT} or {@link TokenType#SLASH_GT} token (not {@code null}).
*/
Token _nodeEnd;
/**
* Construct a new instance representing an XML or HTML element
* @param nodeStart the starting {@link TokenType#LT} token (not {@code null})
* @param tag the {@link TokenType#TAG} token after the starting '&lt;' (not {@code null}).
* @param attributes the attributes associated with this element or {@link #NO_ATTRIBUTES} (not{@code null}, contains no {@code null}s)
* @param attributeEnd The {@link TokenType#GT} or {@link TokenType#SLASH_GT} token after the
* attributes (not {@code null}). The token may be the same token as {@link #nodeEnd} if
* there are no child {@link #tagNodes}.
* @param tagNodes child tag nodes of the receiver or {@link #NO_TAG_NODES} (not {@code null},
* contains no {@code null}s)
* @param contentEnd the token (not {@code null}) after the content, which may be
* <ul>
* <li>(1) {@link TokenType#LT_SLASH} for nodes with open and close tags, or</li>
* <li>(2) the {@link TokenType#LT} nodeStart of the next sibling node if this node is
* self closing or the attributeEnd is {@link TokenType#SLASH_GT}, or</li>
* <li>(3) {@link TokenType#EOF} if the node does not have a closing tag and is the last
* node in the stream {@link TokenType#LT_SLASH} token after the content, or {@code null}if there is no content and the attributes ended with {@link TokenType#SLASH_GT}.</li>
* </ul>
* @param closingTag the closing {@link TokenType#TAG} after the child elements or {@code null} if
* there is no content and the attributes ended with {@link TokenType#SLASH_GT}
* @param nodeEnd the ending {@link TokenType#GT} or {@link TokenType#SLASH_GT} token (not{@code null})
*/
XmlTagNode(Token nodeStart, Token tag, List<XmlAttributeNode> attributes, Token attributeEnd, List<XmlTagNode> tagNodes, Token contentEnd, Token closingTag, Token nodeEnd) {
this._nodeStart = nodeStart;
this._tag = tag;
this._attributes = becomeParentOfEmpty(attributes, NO_ATTRIBUTES);
this._attributeEnd = attributeEnd;
this._tagNodes = becomeParentOfEmpty(tagNodes, NO_TAG_NODES);
this._contentEnd = contentEnd;
this._closingTag = closingTag;
this._nodeEnd = nodeEnd;
}
accept(XmlVisitor visitor) => visitor.visitXmlTagNode(this);
/**
* Answer the attribute with the specified name.
* @param name the attribute name
* @return the attribute or {@code null} if no matching attribute is found
*/
XmlAttributeNode getAttribute(String name2) {
for (XmlAttributeNode attribute in _attributes) {
if (attribute.name.lexeme == name2) {
return attribute;
}
}
return null;
}
/**
* The {@link TokenType#GT} or {@link TokenType#SLASH_GT} token after the attributes (not{@code null}). The token may be the same token as {@link #nodeEnd} if there are no child{@link #tagNodes}.
* @return the token (not {@code null})
*/
Token get attributeEnd => _attributeEnd;
/**
* Answer the receiver's attributes. Callers should not manipulate the returned list to edit the
* AST structure.
* @return the attributes (not {@code null}, contains no {@code null}s)
*/
List<XmlAttributeNode> get attributes => _attributes;
/**
* Find the attribute with the given name (see {@link #getAttribute(String)} and answer the lexeme
* for the attribute's value token without the leading and trailing quotes (see{@link XmlAttributeNode#getText()}).
* @param name the attribute name
* @return the attribute text or {@code null} if no matching attribute is found
*/
String getAttributeText(String name) {
XmlAttributeNode attribute = getAttribute(name);
return attribute != null ? attribute.text : null;
}
Token get beginToken => _nodeStart;
/**
* The the closing {@link TokenType#TAG} after the child elements or {@code null} if there is no
* content and the attributes ended with {@link TokenType#SLASH_GT}
* @return the closing tag or {@code null}
*/
Token get closingTag => _closingTag;
/**
* Answer a string representing the content contained in the receiver. This includes the textual
* representation of any child tag nodes ({@link #getTagNodes()}). Whitespace between '&lt;',
* '&lt;/', and '>', '/>' is discarded, but all other whitespace is preserved.
* @return the content (not {@code null})
*/
String get content {
Token token = _attributeEnd.next;
if (identical(token, _contentEnd)) {
return "";
}
String content = token.lexeme;
token = token.next;
if (identical(token, _contentEnd)) {
return content;
}
JavaStringBuilder buffer = new JavaStringBuilder();
while (token != _contentEnd) {
buffer.append(token.lexeme);
token = token.next;
}
return buffer.toString();
}
/**
* Answer the token (not {@code null}) after the content, which may be
* <ul>
* <li>(1) {@link TokenType#LT_SLASH} for nodes with open and close tags, or</li>
* <li>(2) the {@link TokenType#LT} nodeStart of the next sibling node if this node is self
* closing or the attributeEnd is {@link TokenType#SLASH_GT}, or</li>
* <li>(3) {@link TokenType#EOF} if the node does not have a closing tag and is the last node in
* the stream {@link TokenType#LT_SLASH} token after the content, or {@code null} if there is no
* content and the attributes ended with {@link TokenType#SLASH_GT}.</li>
* </ul>
* @return the token (not {@code null})
*/
Token get contentEnd => _contentEnd;
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;
}
/**
* Answer the ending {@link TokenType#GT} or {@link TokenType#SLASH_GT} token.
* @return the token (not {@code null})
*/
Token get nodeEnd => _nodeEnd;
/**
* Answer the starting {@link TokenType#LT} token.
* @return the token (not {@code null})
*/
Token get nodeStart => _nodeStart;
/**
* Answer the {@link TokenType#TAG} token after the starting '&lt;'.
* @return the token (not {@code null})
*/
Token get tag => _tag;
/**
* Answer the tag nodes contained in the receiver. Callers should not manipulate the returned list
* to edit the AST structure.
* @return the children (not {@code null}, contains no {@code null}s)
*/
List<XmlTagNode> get tagNodes => _tagNodes;
void visitChildren(XmlVisitor<Object> visitor) {
for (XmlAttributeNode node in _attributes) {
node.accept(visitor);
}
for (XmlTagNode node in _tagNodes) {
node.accept(visitor);
}
}
/**
* Same as {@link #becomeParentOf(List)}, but returns given "ifEmpty" if "children" is empty
*/
List becomeParentOfEmpty(List children, List ifEmpty) {
if (children != null && children.isEmpty) {
return ifEmpty;
}
return becomeParentOf(children);
}
}
/**
* Instances of the class {@code HtmlParser} are used to parse tokens into a AST structure comprised
* of {@link XmlNode}s.
* @coverage dart.engine.html
*/
class HtmlParser extends XmlParser {
static Set<String> SELF_CLOSING = new Set<String>();
/**
* Construct a parser for the specified source.
* @param source the source being parsed
*/
HtmlParser(Source source) : super(source) {
}
/**
* Parse the tokens specified by the given scan result.
* @param scanResult the result of scanning an HTML source (not {@code null})
* @return the parse result (not {@code null})
*/
HtmlParseResult parse(HtmlScanResult scanResult) {
Token firstToken = scanResult.token;
List<XmlTagNode> tagNodes = parseTopTagNodes(firstToken);
HtmlUnit unit = new HtmlUnit(firstToken, tagNodes, currentToken);
return new HtmlParseResult(scanResult.modificationTime, firstToken, scanResult.lineStarts, unit);
}
/**
* Scan then parse the specified source.
* @param source the source to be scanned and parsed (not {@code null})
* @return the parse result (not {@code null})
*/
HtmlParseResult parse2(Source source) {
HtmlScanner scanner = new HtmlScanner(source);
source.getContents(scanner);
return parse(scanner.result);
}
bool isSelfClosing(Token tag) => SELF_CLOSING.contains(tag.lexeme);
}
/**
* Instances of the class {@code HtmlUnit} represent the contents of an HTML file.
* @coverage dart.engine.html
*/
class HtmlUnit extends XmlNode {
/**
* The first token in the token stream that was parsed to form this HTML unit.
*/
Token _beginToken;
/**
* The last token in the token stream that was parsed to form this compilation unit. This token
* should always have a type of {@link TokenType.EOF}.
*/
Token _endToken;
/**
* The tag nodes contained in the receiver (not {@code null}, contains no {@code null}s).
*/
List<XmlTagNode> _tagNodes;
/**
* The element associated with this HTML unit or {@code null} if the receiver is not resolved.
*/
HtmlElementImpl _element;
/**
* Construct a new instance representing the content of an HTML file.
* @param beginToken the first token in the file (not {@code null})
* @param tagNodes child tag nodes of the receiver (not {@code null}, contains no {@code null}s)
* @param endToken the last token in the token stream which should be of type{@link TokenType.EOF}
*/
HtmlUnit(Token beginToken, List<XmlTagNode> tagNodes, Token endToken) {
this._beginToken = beginToken;
this._tagNodes = becomeParentOf(tagNodes);
this._endToken = endToken;
}
accept(XmlVisitor visitor) => visitor.visitHtmlUnit(this);
Token get beginToken => _beginToken;
/**
* Return the element associated with this HTML unit.
* @return the element or {@code null} if the receiver is not resolved
*/
HtmlElementImpl get element => _element;
Token get endToken => _endToken;
/**
* Answer the tag nodes contained in the receiver. Callers should not manipulate the returned list
* to edit the AST structure.
* @return the children (not {@code null}, contains no {@code null}s)
*/
List<XmlTagNode> get tagNodes => _tagNodes;
/**
* Set the element associated with this HTML unit.
* @param element the element
*/
void set element(HtmlElementImpl element2) {
this._element = element2;
}
void visitChildren(XmlVisitor<Object> visitor) {
for (XmlTagNode node in _tagNodes) {
node.accept(visitor);
}
}
}