* If hacks should be allowed, then only check the root
* property. If hacks should not be allowed, treat
* _property or *property as invalid properties.
propertyName = property.toString();
if (this.options.starHack && property.hack === "*" ||
this.options.underscoreHack && property.hack === "_") {
propertyName = property.text;
this._validateProperty(propertyName, expr);
var tokenStream = this._tokenStream,
result = tokenStream.match(Tokens.IMPORTANT_SYM);
_expr: function(inFunction) {
* : term [ operator term ]*
value = this._term(inFunction);
operator = this._operator(inFunction);
//if there's an operator, keep building up the value parts
//if there's not an operator, you have a full value
values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col));
value = this._term(inFunction);
/*if (valueParts.length) {
values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col));
return values.length > 0 ? new PropertyValue(values, values[0].line, values[0].col) : null;
_term: function(inFunction) {
* [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* |
* TIME S* | FREQ S* | function | ie_function ]
* | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor
var tokenStream = this._tokenStream,
//returns the operator or null
unary = this._unary_operator();
line = tokenStream.token().startLine;
col = tokenStream.token().startCol;
//exception for IE filters
if (tokenStream.peek() === Tokens.IE_FUNCTION && this.options.ieFilters) {
value = this._ie_function();
line = tokenStream.token().startLine;
col = tokenStream.token().startCol;
//see if it's a simple block
} else if (inFunction && tokenStream.match([Tokens.LPAREN, Tokens.LBRACE, Tokens.LBRACKET])) {
token = tokenStream.token();
value = token.value + this._expr(inFunction).text;
line = tokenStream.token().startLine;
col = tokenStream.token().startCol;
tokenStream.mustMatch(Tokens.type(endChar));
//see if there's a simple match
} else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH,
Tokens.ANGLE, Tokens.TIME,
Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])) {
value = tokenStream.token().value;
line = tokenStream.token().startLine;
col = tokenStream.token().startCol;
// Correct potentially-inaccurate IDENT parsing in
// PropertyValuePart constructor.
part = PropertyValuePart.fromToken(tokenStream.token());
token = this._hexcolor();
//if there's no unary, get the start of the next token for line/col info
line = tokenStream.LT(1).startLine;
col = tokenStream.LT(1).startCol;
* This checks for alpha(opacity=0) style of IE
* functions. IE_FUNCTION only presents progid: style.
if (tokenStream.LA(3) === Tokens.EQUALS && this.options.ieFilters) {
value = this._ie_function();
value = this._function();
//throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + ".");
return part !== null ? part : value !== null ?
new PropertyValuePart(unary !== null ? unary + value : value, line, col) :
* : FUNCTION S* expr ')' S*
var tokenStream = this._tokenStream,
if (tokenStream.match(Tokens.FUNCTION)) {
functionText = tokenStream.token().value;
//START: Horrible hack in case it's an IE filter
if (this.options.ieFilters && tokenStream.peek() === Tokens.EQUALS) {
if (this._readWhitespace()) {
functionText += tokenStream.token().value;
//might be second time in the loop
if (tokenStream.LA(0) === Tokens.COMMA) {
functionText += tokenStream.token().value;
tokenStream.match(Tokens.IDENT);
functionText += tokenStream.token().value;
tokenStream.match(Tokens.EQUALS);
functionText += tokenStream.token().value;
//functionText += this._term();
while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) {
functionText += tokenStream.token().value;
} while (tokenStream.match([Tokens.COMMA, Tokens.S]));
tokenStream.match(Tokens.RPAREN);
_ie_function: function() {
* : IE_FUNCTION S* IDENT '=' term [S* ','? IDENT '=' term]+ ')' S*
var tokenStream = this._tokenStream,
//IE function can begin like a regular function, too
if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])) {
functionText = tokenStream.token().value;
if (this._readWhitespace()) {
functionText += tokenStream.token().value;
//might be second time in the loop
if (tokenStream.LA(0) === Tokens.COMMA) {
functionText += tokenStream.token().value;
tokenStream.match(Tokens.IDENT);
functionText += tokenStream.token().value;
tokenStream.match(Tokens.EQUALS);
functionText += tokenStream.token().value;
//functionText += this._term();
while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) {
functionText += tokenStream.token().value;
} while (tokenStream.match([Tokens.COMMA, Tokens.S]));
tokenStream.match(Tokens.RPAREN);
* There is a constraint on the color that it must
* have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
* after the "#"; e.g., "#000" is OK, but "#abcd" is not.
var tokenStream = this._tokenStream,
if (tokenStream.match(Tokens.HASH)) {
//need to do some validation here
token = tokenStream.token();
if (!/#[a-f0-9]{3,6}/i.test(color)) {
throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
//-----------------------------------------------------------------
//-----------------------------------------------------------------
* : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' {
var tokenStream = this._tokenStream,
tokenStream.mustMatch(Tokens.KEYFRAMES_SYM);
token = tokenStream.token();
if (/^@\-([^\-]+)\-/.test(token.value)) {
name = this._keyframe_name();
tokenStream.mustMatch(Tokens.LBRACE);
while (tt === Tokens.IDENT || tt === Tokens.PERCENTAGE) {
tokenStream.mustMatch(Tokens.RBRACE);
_keyframe_name: function() {
var tokenStream = this._tokenStream;
tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]);
return SyntaxUnit.fromToken(tokenStream.token());
_keyframe_rule: function() {
* '{' S* declaration [ ';' S* declaration ]* '}' S*
var keyList = this._key_list();
type: "startkeyframerule",
this._readDeclarations(true);
* : key [ S* ',' S* key]*
var tokenStream = this._tokenStream,
keyList.push(this._key());
while (tokenStream.match(Tokens.COMMA)) {
keyList.push(this._key());
* There is a restriction that IDENT can be only "from" or "to".
var tokenStream = this._tokenStream,
if (tokenStream.match(Tokens.PERCENTAGE)) {
return SyntaxUnit.fromToken(tokenStream.token());
} else if (tokenStream.match(Tokens.IDENT)) {
token = tokenStream.token();
if (/from|to/i.test(token.value)) {
return SyntaxUnit.fromToken(token);