* first character is provided and the rest is read by the function to determine
* the correct token to create.
* @param {String} first The first character in the token.
* @param {int} startLine The beginning line for the character.
* @param {int} startCol The beginning column for the character.
* @return {Object} A token object.
* @method htmlCommentStartToken
htmlCommentStartToken: function(first, startLine, startCol) {
var reader = this._reader,
text += reader.readCount(3);
return this.createToken(Tokens.CDO, text, startLine, startCol);
return this.charToken(first, startLine, startCol);
* Produces a CDC or CHAR token based on the specified information. The
* first character is provided and the rest is read by the function to determine
* the correct token to create.
* @param {String} first The first character in the token.
* @param {int} startLine The beginning line for the character.
* @param {int} startCol The beginning column for the character.
* @return {Object} A token object.
* @method htmlCommentEndToken
htmlCommentEndToken: function(first, startLine, startCol) {
var reader = this._reader,
text += reader.readCount(2);
return this.createToken(Tokens.CDC, text, startLine, startCol);
return this.charToken(first, startLine, startCol);
* Produces an IDENT or FUNCTION token based on the specified information. The
* first character is provided and the rest is read by the function to determine
* the correct token to create.
* @param {String} first The first character in the identifier.
* @param {int} startLine The beginning line for the character.
* @param {int} startCol The beginning column for the character.
* @return {Object} A token object.
* @method identOrFunctionToken
identOrFunctionToken: function(first, startLine, startCol) {
var reader = this._reader,
ident = this.readName(first),
uriFns = ["url(", "url-prefix(", "domain("],
//if there's a left paren immediately after, it's a URI or function
if (reader.peek() === "(") {
if (uriFns.indexOf(ident.toLowerCase()) > -1) {
uri = this.readURI(ident);
//didn't find a valid URL or there's no closing paren
} else if (reader.peek() === ":") { //might be an IE function
//IE-specific functions always being with progid:
if (ident.toLowerCase() === "progid") {
ident += reader.readTo("(");
return this.createToken(tt, ident, startLine, startCol);
* Produces an IMPORTANT_SYM or CHAR token based on the specified information. The
* first character is provided and the rest is read by the function to determine
* the correct token to create.
* @param {String} first The first character in the token.
* @param {int} startLine The beginning line for the character.
* @param {int} startCol The beginning column for the character.
* @return {Object} A token object.
importantToken: function(first, startLine, startCol) {
var reader = this._reader,
//there can be a comment in here
//if the next character isn't a star, then this isn't a valid !important token
if (reader.peek() !== "*") {
temp = this.readComment(c);
if (temp === "") { //broken!
} else if (isWhitespace(c)) {
important += c + this.readWhitespace();
} else if (/i/i.test(c)) {
temp = reader.readCount(8);
if (/mportant/i.test(temp)) {
tt = Tokens.IMPORTANT_SYM;
if (tt === Tokens.CHAR) {
return this.charToken(first, startLine, startCol);
return this.createToken(tt, important, startLine, startCol);
* Produces a NOT or CHAR token based on the specified information. The
* first character is provided and the rest is read by the function to determine
* the correct token to create.
* @param {String} first The first character in the token.
* @param {int} startLine The beginning line for the character.
* @param {int} startCol The beginning column for the character.
* @return {Object} A token object.
notToken: function(first, startLine, startCol) {
var reader = this._reader,
text += reader.readCount(4);
if (text.toLowerCase() === ":not(") {
return this.createToken(Tokens.NOT, text, startLine, startCol);
return this.charToken(first, startLine, startCol);
* Produces a number token based on the given character
* and location in the stream. This may return a token of
* NUMBER, EMS, EXS, LENGTH, ANGLE, TIME, FREQ, DIMENSION,
* @param {String} first The first character for the token.
* @param {int} startLine The beginning line for the character.
* @param {int} startCol The beginning column for the character.
* @return {Object} A token object.
numberToken: function(first, startLine, startCol) {
var reader = this._reader,
value = this.readNumber(first),
ident = this.readName(reader.read());
if (/^em$|^ex$|^px$|^gd$|^rem$|^vw$|^vh$|^vmax$|^vmin$|^ch$|^cm$|^mm$|^in$|^pt$|^pc$/i.test(ident)) {
} else if (/^deg|^rad$|^grad$|^turn$/i.test(ident)) {
} else if (/^ms$|^s$/i.test(ident)) {
} else if (/^hz$|^khz$/i.test(ident)) {
} else if (/^dpi$|^dpcm$/i.test(ident)) {
return this.createToken(tt, value, startLine, startCol);
* Produces a string token based on the given character
* and location in the stream. Since strings may be indicated
* by single or double quotes, a failure to match starting
* and ending quotes results in an INVALID token being generated.
* The first character in the string is passed in and then
* the rest are read up to and including the final quotation mark.
* @param {String} first The first character in the string.
* @param {int} startLine The beginning line for the character.
* @param {int} startCol The beginning column for the character.
* @return {Object} A token object.
stringToken: function(first, startLine, startCol) {
break; // premature EOF after backslash
} else if (/[^\r\n\f0-9a-f]/i.test(c)) {
// single-character escape
// read up to six hex digits
for (i=0; isHexDigit(c) && i<6; i++) {
// swallow trailing newline or space
if (c === "\r" && reader.peek() === "\n") {
// This character is null or not part of the escape;
// jump back to the top to process it.
} else if (c === delim) {
break; // delimiter found.
} else if (isNewLine(reader.peek())) {
// newline without an escapement: it's an invalid string
//if c is null, that means we're out of input and the string was never closed
return this.createToken(tt, string, startLine, startCol);
unicodeRangeToken: function(first, startLine, startCol) {
var reader = this._reader,
//then it should be a unicode range
if (reader.peek() === "+") {
value += this.readUnicodeRangePart(true);
//ensure there's an actual unicode range here
if (value.length === 2) {
tt = Tokens.UNICODE_RANGE;
//if there's a ? in the first part, there can't be a second part
if (value.indexOf("?") === -1) {
if (reader.peek() === "-") {
temp += this.readUnicodeRangePart(false);
//if there's not another value, back up and just take the first
return this.createToken(tt, value, startLine, startCol);
* Produces a S token based on the specified information. Since whitespace
* may have multiple characters, this consumes all whitespace characters
* @param {String} first The first character in the token.
* @param {int} startLine The beginning line for the character.
* @param {int} startCol The beginning column for the character.
* @return {Object} A token object.
* @method whitespaceToken
whitespaceToken: function(first, startLine, startCol) {
var value = first + this.readWhitespace();
return this.createToken(Tokens.S, value, startLine, startCol);
//-------------------------------------------------------------------------
// Methods to read values from the string stream
//-------------------------------------------------------------------------
readUnicodeRangePart: function(allowQuestionMark) {
var reader = this._reader,
while (isHexDigit(c) && part.length < 6) {
//then read question marks if allowed
while (c === "?" && part.length < 6) {
//there can't be any other characters after this point
readWhitespace: function() {
var reader = this._reader,
while (isWhitespace(c)) {
readNumber: function(first) {
var reader = this._reader,
hasDot = (first === "."),
// returns null w/o resetting reader if string is invalid.
var token = this.stringToken(this._reader.read(), 0, 0);
return token.type === Tokens.INVALID ? null : token.value;
// returns null w/o resetting reader if URI is invalid.
readURI: function(first) {
var reader = this._reader,
while (c && isWhitespace(c)) {
if (c === "'" || c === "\"") {
inner = this.readString();
inner = PropertyValuePart.parseString(inner);
inner = this.readUnquotedURL();
while (c && isWhitespace(c)) {
//if there was no inner value or the next character isn't closing paren, it's not a URI
if (inner === null || c !== ")") {
// Ensure argument to URL is always double-quoted
// (This simplifies later processing in PropertyValuePart.)
uri += PropertyValuePart.serializeString(inner) + reader.read();
// This method never fails, although it may return an empty string.
readUnquotedURL: function(first) {
var reader = this._reader,
for (c = reader.peek(); c; c = reader.peek()) {
// Note that the grammar at
// https://www.w3.org/TR/CSS2/grammar.html#scanner
// incorrectly includes the backslash character in the
// `url` production, although it is correctly omitted in
// the `baduri1` production.
if (nonascii.test(c) || /^[\-!#$%&*-\[\]-~]$/.test(c)) {
if (/^[^\r\n\f]$/.test(reader.peek(2))) {
url += this.readEscape(reader.read(), true);
break; // bad escape sequence.
readName: function(first) {
var reader = this._reader,