//add whitespace separator
ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol);
//combinator is not required
combinator = this._combinator();
//selector is required if there's a combinator
nextSelector = this._simple_selector_sequence();
if (nextSelector === null) {
if (combinator !== null) {
this._unexpectedToken(tokenStream.LT(1));
if (combinator !== null) {
selector.push(combinator);
selector.push(nextSelector);
return new Selector(selector, selector[0].line, selector[0].col);
_simple_selector_sequence: function() {
* simple_selector_sequence
* : [ type_selector | universal ]
* [ HASH | class | attrib | pseudo | negation ]*
* | [ HASH | class | attrib | pseudo | negation ]+
var tokenStream = this._tokenStream,
//parts of a simple selector
//the different parts after the element name to search for
return tokenStream.match(Tokens.HASH) ?
new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) :
//get starting line and column for the selector
line = tokenStream.LT(1).startLine;
col = tokenStream.LT(1).startCol;
elementName = this._type_selector();
elementName = this._universal();
if (elementName !== null) {
selectorText += elementName;
//whitespace means we're done
if (tokenStream.peek() === Tokens.S) {
//check for each component
while (i < len && component === null) {
component = components[i++].call(this);
if (component === null) {
//we don't have a selector
if (selectorText === "") {
modifiers.push(component);
selectorText += component.toString();
return selectorText !== "" ?
new SelectorPart(elementName, modifiers, selectorText, line, col) :
_type_selector: function() {
* : [ namespace_prefix ]? element_name
var tokenStream = this._tokenStream,
ns = this._namespace_prefix(),
elementName = this._element_name();
* Need to back out the namespace that was read due to both
* type_selector and universal reading namespace_prefix
* first. Kind of hacky, but only way I can figure out
* right now how to not change the grammar.
elementName.text = ns + elementName.text;
elementName.col -= ns.length;
var tokenStream = this._tokenStream,
if (tokenStream.match(Tokens.DOT)) {
tokenStream.mustMatch(Tokens.IDENT);
token = tokenStream.token();
return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1);
_element_name: function() {
var tokenStream = this._tokenStream,
if (tokenStream.match(Tokens.IDENT)) {
token = tokenStream.token();
return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol);
_namespace_prefix: function() {
var tokenStream = this._tokenStream,
//verify that this is a namespace prefix
if (tokenStream.LA(1) === Tokens.PIPE || tokenStream.LA(2) === Tokens.PIPE) {
if (tokenStream.match([Tokens.IDENT, Tokens.STAR])) {
value += tokenStream.token().value;
tokenStream.mustMatch(Tokens.PIPE);
return value.length ? value : null;
* : [ namespace_prefix ]? '*'
var tokenStream = this._tokenStream,
ns = this._namespace_prefix();
if (tokenStream.match(Tokens.STAR)) {
return value.length ? value : null;
* : '[' S* [ namespace_prefix ]? IDENT S*
* DASHMATCH ] S* [ IDENT | STRING ] S*
var tokenStream = this._tokenStream,
if (tokenStream.match(Tokens.LBRACKET)) {
token = tokenStream.token();
value += this._readWhitespace();
ns = this._namespace_prefix();
tokenStream.mustMatch(Tokens.IDENT);
value += tokenStream.token().value;
value += this._readWhitespace();
if (tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH,
Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])) {
value += tokenStream.token().value;
value += this._readWhitespace();
tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]);
value += tokenStream.token().value;
value += this._readWhitespace();
tokenStream.mustMatch(Tokens.RBRACKET);
return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol);
* : ':' ':'? [ IDENT | functional_pseudo ]
var tokenStream = this._tokenStream,
if (tokenStream.match(Tokens.COLON)) {
if (tokenStream.match(Tokens.COLON)) {
if (tokenStream.match(Tokens.IDENT)) {
pseudo = tokenStream.token().value;
line = tokenStream.token().startLine;
col = tokenStream.token().startCol - colons.length;
} else if (tokenStream.peek() === Tokens.FUNCTION) {
line = tokenStream.LT(1).startLine;
col = tokenStream.LT(1).startCol - colons.length;
pseudo = this._functional_pseudo();
pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col);
var startLine = tokenStream.LT(1).startLine,
startCol = tokenStream.LT(0).startCol;
throw new SyntaxError("Expected a `FUNCTION` or `IDENT` after colon at line " + startLine + ", col " + startCol + ".", startLine, startCol);
_functional_pseudo: function() {
* : FUNCTION S* expression ')'
var tokenStream = this._tokenStream,
if (tokenStream.match(Tokens.FUNCTION)) {
value = tokenStream.token().value;
value += this._readWhitespace();
value += this._expression();
tokenStream.mustMatch(Tokens.RPAREN);
_expression: function() {
* : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
var tokenStream = this._tokenStream,
while (tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION,
Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH,
Tokens.FREQ, Tokens.ANGLE, Tokens.TIME,
Tokens.RESOLUTION, Tokens.SLASH])) {
value += tokenStream.token().value;
value += this._readWhitespace();
return value.length ? value : null;
* : NOT S* negation_arg S* ')'
var tokenStream = this._tokenStream,
if (tokenStream.match(Tokens.NOT)) {
value = tokenStream.token().value;
line = tokenStream.token().startLine;
col = tokenStream.token().startCol;
value += this._readWhitespace();
arg = this._negation_arg();
value += this._readWhitespace();
tokenStream.match(Tokens.RPAREN);
value += tokenStream.token().value;
subpart = new SelectorSubPart(value, "not", line, col);
_negation_arg: function() {
* : type_selector | universal | HASH | class | attrib | pseudo
var tokenStream = this._tokenStream,
return tokenStream.match(Tokens.HASH) ?
new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) :
line = tokenStream.LT(1).startLine;
col = tokenStream.LT(1).startCol;
while (i < len && arg === null) {
arg = args[i].call(this);
this._unexpectedToken(tokenStream.LT(1));
if (arg.type === "elementName") {
part = new SelectorPart(arg, [], arg.toString(), line, col);
part = new SelectorPart(null, [arg], arg.toString(), line, col);
_declaration: function() {
* : property ':' S* expr prio?
var tokenStream = this._tokenStream,
property = this._property();
tokenStream.mustMatch(Tokens.COLON);
//if there's no parts for the value, it's an error
if (!expr || expr.length === 0) {
this._unexpectedToken(tokenStream.LT(1));