diff --git a/src/main/resources/csslint-rhino.js b/src/main/resources/csslint-rhino.js index 5809258..2d01634 100644 --- a/src/main/resources/csslint-rhino.js +++ b/src/main/resources/csslint-rhino.js @@ -1,9 +1,9 @@ /*! -CSSLint -Copyright (c) 2011 Nicole Sullivan and Nicholas C. Zakas. All rights reserved. +CSSLint v1.0.4 +Copyright (c) 2016 Nicole Sullivan and Nicholas C. Zakas. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal +of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is @@ -12,7 +12,7 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER @@ -21,11 +21,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Build time: 17-January-2013 10:55:01 */ + var CSSLint = (function(){ + var module = module || {}, + exports = exports || {}; + /*! Parser-Lib -Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved. +Copyright (c) 2009-2016 Nicholas C. Zakas. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -44,1595 +47,1246 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -/* Version v0.2.2, Build time: 17-January-2013 10:26:34 */ -var parserlib = {}; -(function(){ +/* Version v1.1.0, Build time: 6-December-2016 10:31:29 */ +var parserlib = (function () { +var require; +require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o). + * @namespace parserlib.css + * @class Combinator + * @extends parserlib.util.SyntaxUnit * @constructor + * @param {String} text The text representation of the unit. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. */ -function EventTarget(){ +function Combinator(text, line, col) { + + SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE); /** - * The array of listeners for various events. - * @type Object - * @property _listeners - * @private + * The type of modifier. + * @type String + * @property type */ - this._listeners = {}; + this.type = "unknown"; + + //pretty simple + if (/^\s+$/.test(text)) { + this.type = "descendant"; + } else if (text === ">") { + this.type = "child"; + } else if (text === "+") { + this.type = "adjacent-sibling"; + } else if (text === "~") { + this.type = "sibling"; + } + } -EventTarget.prototype = { +Combinator.prototype = new SyntaxUnit(); +Combinator.prototype.constructor = Combinator; - //restore constructor - constructor: EventTarget, - /** - * Adds a listener for a given event type. - * @param {String} type The type of event to add a listener for. - * @param {Function} listener The function to call when the event occurs. - * @return {void} - * @method addListener - */ - addListener: function(type, listener){ - if (!this._listeners[type]){ - this._listeners[type] = []; +},{"../util/SyntaxUnit":26,"./Parser":6}],3:[function(require,module,exports){ +"use strict"; + +module.exports = Matcher; + +var StringReader = require("../util/StringReader"); +var SyntaxError = require("../util/SyntaxError"); + +/** + * This class implements a combinator library for matcher functions. + * The combinators are described at: + * https://developer.mozilla.org/en-US/docs/Web/CSS/Value_definition_syntax#Component_value_combinators + */ +function Matcher(matchFunc, toString) { + this.match = function(expression) { + // Save/restore marks to ensure that failed matches always restore + // the original location in the expression. + var result; + expression.mark(); + result = matchFunc(expression); + if (result) { + expression.drop(); + } else { + expression.restore(); } + return result; + }; + this.toString = typeof toString === "function" ? toString : function() { + return toString; + }; +} - this._listeners[type].push(listener); - }, - - /** - * Fires an event based on the passed-in object. - * @param {Object|String} event An object with at least a 'type' attribute - * or a string indicating the event name. - * @return {void} - * @method fire - */ - fire: function(event){ - if (typeof event == "string"){ - event = { type: event }; +/** Precedence table of combinators. */ +Matcher.prec = { + MOD: 5, + SEQ: 4, + ANDAND: 3, + OROR: 2, + ALT: 1 +}; + +/** Simple recursive-descent grammar to build matchers from strings. */ +Matcher.parse = function(str) { + var reader, eat, expr, oror, andand, seq, mod, term, result; + reader = new StringReader(str); + eat = function(matcher) { + var result = reader.readMatch(matcher); + if (result === null) { + throw new SyntaxError( + "Expected "+matcher, reader.getLine(), reader.getCol()); } - if (typeof event.target != "undefined"){ - event.target = this; + return result; + }; + expr = function() { + // expr = oror (" | " oror)* + var m = [ oror() ]; + while (reader.readMatch(" | ") !== null) { + m.push(oror()); } - - if (typeof event.type == "undefined"){ - throw new Error("Event object missing 'type' property."); + return m.length === 1 ? m[0] : Matcher.alt.apply(Matcher, m); + }; + oror = function() { + // oror = andand ( " || " andand)* + var m = [ andand() ]; + while (reader.readMatch(" || ") !== null) { + m.push(andand()); } - - if (this._listeners[event.type]){ - - //create a copy of the array and use that so listeners can't chane - var listeners = this._listeners[event.type].concat(); - for (var i=0, len=listeners.length; i < len; i++){ - listeners[i].call(this, event); - } - } - }, - - /** - * Removes a listener for a given event type. - * @param {String} type The type of event to remove a listener from. - * @param {Function} listener The function to remove from the event. - * @return {void} - * @method removeListener - */ - removeListener: function(type, listener){ - if (this._listeners[type]){ - var listeners = this._listeners[type]; - for (var i=0, len=listeners.length; i < len; i++){ - if (listeners[i] === listener){ - listeners.splice(i, 1); + return m.length === 1 ? m[0] : Matcher.oror.apply(Matcher, m); + }; + andand = function() { + // andand = seq ( " && " seq)* + var m = [ seq() ]; + while (reader.readMatch(" && ") !== null) { + m.push(seq()); + } + return m.length === 1 ? m[0] : Matcher.andand.apply(Matcher, m); + }; + seq = function() { + // seq = mod ( " " mod)* + var m = [ mod() ]; + while (reader.readMatch(/^ (?![&|\]])/) !== null) { + m.push(mod()); + } + return m.length === 1 ? m[0] : Matcher.seq.apply(Matcher, m); + }; + mod = function() { + // mod = term ( "?" | "*" | "+" | "#" | "{,}" )? + var m = term(); + if (reader.readMatch("?") !== null) { + return m.question(); + } else if (reader.readMatch("*") !== null) { + return m.star(); + } else if (reader.readMatch("+") !== null) { + return m.plus(); + } else if (reader.readMatch("#") !== null) { + return m.hash(); + } else if (reader.readMatch(/^\{\s*/) !== null) { + var min = eat(/^\d+/); + eat(/^\s*,\s*/); + var max = eat(/^\d+/); + eat(/^\s*\}/); + return m.braces(+min, +max); + } + return m; + }; + term = function() { + // term = | literal | "[ " expression " ]" + if (reader.readMatch("[ ") !== null) { + var m = expr(); + eat(" ]"); + return m; + } + return Matcher.fromType(eat(/^[^ ?*+#{]+/)); + }; + result = expr(); + if (!reader.eof()) { + throw new SyntaxError( + "Expected end of string", reader.getLine(), reader.getCol()); + } + return result; +}; + +/** + * Convert a string to a matcher (parsing simple alternations), + * or do nothing if the argument is already a matcher. + */ +Matcher.cast = function(m) { + if (m instanceof Matcher) { + return m; + } + return Matcher.parse(m); +}; + +/** + * Create a matcher for a single type. + */ +Matcher.fromType = function(type) { + // Late require of ValidationTypes to break a dependency cycle. + var ValidationTypes = require("./ValidationTypes"); + return new Matcher(function(expression) { + return expression.hasNext() && ValidationTypes.isType(expression, type); + }, type); +}; + +/** + * Create a matcher for one or more juxtaposed words, which all must + * occur, in the given order. + */ +Matcher.seq = function() { + var ms = Array.prototype.slice.call(arguments).map(Matcher.cast); + if (ms.length === 1) { + return ms[0]; + } + return new Matcher(function(expression) { + var i, result = true; + for (i = 0; result && i < ms.length; i++) { + result = ms[i].match(expression); + } + return result; + }, function(prec) { + var p = Matcher.prec.SEQ; + var s = ms.map(function(m) { + return m.toString(p); + }).join(" "); + if (prec > p) { + s = "[ " + s + " ]"; + } + return s; + }); +}; + +/** + * Create a matcher for one or more alternatives, where exactly one + * must occur. + */ +Matcher.alt = function() { + var ms = Array.prototype.slice.call(arguments).map(Matcher.cast); + if (ms.length === 1) { + return ms[0]; + } + return new Matcher(function(expression) { + var i, result = false; + for (i = 0; !result && i < ms.length; i++) { + result = ms[i].match(expression); + } + return result; + }, function(prec) { + var p = Matcher.prec.ALT; + var s = ms.map(function(m) { + return m.toString(p); + }).join(" | "); + if (prec > p) { + s = "[ " + s + " ]"; + } + return s; + }); +}; + +/** + * Create a matcher for two or more options. This implements the + * double bar (||) and double ampersand (&&) operators, as well as + * variants of && where some of the alternatives are optional. + * This will backtrack through even successful matches to try to + * maximize the number of items matched. + */ +Matcher.many = function(required) { + var ms = Array.prototype.slice.call(arguments, 1).reduce(function(acc, v) { + if (v.expand) { + // Insert all of the options for the given complex rule as + // individual options. + var ValidationTypes = require("./ValidationTypes"); + acc.push.apply(acc, ValidationTypes.complex[v.expand].options); + } else { + acc.push(Matcher.cast(v)); + } + return acc; + }, []); + + if (required === true) { + required = ms.map(function() { + return true; + }); + } + + var result = new Matcher(function(expression) { + var seen = [], max = 0, pass = 0; + var success = function(matchCount) { + if (pass === 0) { + max = Math.max(matchCount, max); + return matchCount === ms.length; + } else { + return matchCount === max; + } + }; + var tryMatch = function(matchCount) { + for (var i = 0; i < ms.length; i++) { + if (seen[i]) { + continue; + } + expression.mark(); + if (ms[i].match(expression)) { + seen[i] = true; + // Increase matchCount iff this was a required element + // (or if all the elements are optional) + if (tryMatch(matchCount + ((required === false || required[i]) ? 1 : 0))) { + expression.drop(); + return true; + } + // Backtrack: try *not* matching using this rule, and + // let's see if it leads to a better overall match. + expression.restore(); + seen[i] = false; + } else { + expression.drop(); + } + } + return success(matchCount); + }; + if (!tryMatch(0)) { + // Couldn't get a complete match, retrace our steps to make the + // match with the maximum # of required elements. + pass++; + tryMatch(0); + } + + if (required === false) { + return max > 0; + } + // Use finer-grained specification of which matchers are required. + for (var i = 0; i < ms.length; i++) { + if (required[i] && !seen[i]) { + return false; + } + } + return true; + }, function(prec) { + var p = required === false ? Matcher.prec.OROR : Matcher.prec.ANDAND; + var s = ms.map(function(m, i) { + if (required !== false && !required[i]) { + return m.toString(Matcher.prec.MOD) + "?"; + } + return m.toString(p); + }).join(required === false ? " || " : " && "); + if (prec > p) { + s = "[ " + s + " ]"; + } + return s; + }); + result.options = ms; + return result; +}; + +/** + * Create a matcher for two or more options, where all options are + * mandatory but they may appear in any order. + */ +Matcher.andand = function() { + var args = Array.prototype.slice.call(arguments); + args.unshift(true); + return Matcher.many.apply(Matcher, args); +}; + +/** + * Create a matcher for two or more options, where options are + * optional and may appear in any order, but at least one must be + * present. + */ +Matcher.oror = function() { + var args = Array.prototype.slice.call(arguments); + args.unshift(false); + return Matcher.many.apply(Matcher, args); +}; + +/** Instance methods on Matchers. */ +Matcher.prototype = { + constructor: Matcher, + // These are expected to be overridden in every instance. + match: function() { throw new Error("unimplemented"); }, + toString: function() { throw new Error("unimplemented"); }, + // This returns a standalone function to do the matching. + func: function() { return this.match.bind(this); }, + // Basic combinators + then: function(m) { return Matcher.seq(this, m); }, + or: function(m) { return Matcher.alt(this, m); }, + andand: function(m) { return Matcher.many(true, this, m); }, + oror: function(m) { return Matcher.many(false, this, m); }, + // Component value multipliers + star: function() { return this.braces(0, Infinity, "*"); }, + plus: function() { return this.braces(1, Infinity, "+"); }, + question: function() { return this.braces(0, 1, "?"); }, + hash: function() { + return this.braces(1, Infinity, "#", Matcher.cast(",")); + }, + braces: function(min, max, marker, optSep) { + var m1 = this, m2 = optSep ? optSep.then(this) : this; + if (!marker) { + marker = "{" + min + "," + max + "}"; + } + return new Matcher(function(expression) { + var result = true, i; + for (i = 0; i < max; i++) { + if (i > 0 && optSep) { + result = m2.match(expression); + } else { + result = m1.match(expression); + } + if (!result) { break; } } - - - } + return i >= min; + }, function() { + return m1.toString(Matcher.prec.MOD) + marker; + }); } }; + +},{"../util/StringReader":24,"../util/SyntaxError":25,"./ValidationTypes":21}],4:[function(require,module,exports){ +"use strict"; + +module.exports = MediaFeature; + +var SyntaxUnit = require("../util/SyntaxUnit"); + +var Parser = require("./Parser"); + /** - * Convenient way to read through strings. - * @namespace parserlib.util - * @class StringReader + * Represents a media feature, such as max-width:500. + * @namespace parserlib.css + * @class MediaFeature + * @extends parserlib.util.SyntaxUnit * @constructor - * @param {String} text The text to read. + * @param {SyntaxUnit} name The name of the feature. + * @param {SyntaxUnit} value The value of the feature or null if none. */ -function StringReader(text){ +function MediaFeature(name, value) { + + SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol, Parser.MEDIA_FEATURE_TYPE); /** - * The input text with line endings normalized. - * @property _input + * The name of the media feature * @type String - * @private + * @property name */ - this._input = text.replace(/\n\r?/g, "\n"); - + this.name = name; /** - * The row for the character to be read next. - * @property _line - * @type int - * @private + * The value for the feature or null if there is none. + * @type SyntaxUnit + * @property value */ - this._line = 1; + this.value = value; +} +MediaFeature.prototype = new SyntaxUnit(); +MediaFeature.prototype.constructor = MediaFeature; - /** - * The column for the character to be read next. - * @property _col - * @type int - * @private - */ - this._col = 1; - /** - * The index of the character in the input to be read next. - * @property _cursor - * @type int - * @private - */ - this._cursor = 0; -} +},{"../util/SyntaxUnit":26,"./Parser":6}],5:[function(require,module,exports){ +"use strict"; -StringReader.prototype = { +module.exports = MediaQuery; - //restore constructor - constructor: StringReader, +var SyntaxUnit = require("../util/SyntaxUnit"); - //------------------------------------------------------------------------- - // Position info - //------------------------------------------------------------------------- +var Parser = require("./Parser"); - /** - * Returns the column of the character to be read next. - * @return {int} The column of the character to be read next. - * @method getCol - */ - getCol: function(){ - return this._col; - }, +/** + * Represents an individual media query. + * @namespace parserlib.css + * @class MediaQuery + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} modifier The modifier "not" or "only" (or null). + * @param {String} mediaType The type of media (i.e., "print"). + * @param {Array} parts Array of selectors parts making up this selector. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function MediaQuery(modifier, mediaType, features, line, col) { + + SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType : "") + (mediaType && features.length > 0 ? " and " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE); /** - * Returns the row of the character to be read next. - * @return {int} The row of the character to be read next. - * @method getLine + * The media modifier ("not" or "only") + * @type String + * @property modifier */ - getLine: function(){ - return this._line ; - }, + this.modifier = modifier; /** - * Determines if you're at the end of the input. - * @return {Boolean} True if there's no more input, false otherwise. - * @method eof + * The mediaType (i.e., "print") + * @type String + * @property mediaType */ - eof: function(){ - return (this._cursor == this._input.length); - }, - - //------------------------------------------------------------------------- - // Basic reading - //------------------------------------------------------------------------- + this.mediaType = mediaType; /** - * Reads the next character without advancing the cursor. - * @param {int} count How many characters to look ahead (default is 1). - * @return {String} The next character or null if there is no next character. - * @method peek + * The parts that make up the selector. + * @type Array + * @property features */ - peek: function(count){ - var c = null; - count = (typeof count == "undefined" ? 1 : count); + this.features = features; - //if we're not at the end of the input... - if (this._cursor < this._input.length){ +} - //get character and increment cursor and column - c = this._input.charAt(this._cursor + count - 1); - } +MediaQuery.prototype = new SyntaxUnit(); +MediaQuery.prototype.constructor = MediaQuery; - return c; - }, - /** - * Reads the next character from the input and adjusts the row and column - * accordingly. - * @return {String} The next character or null if there is no next character. - * @method read - */ - read: function(){ - var c = null; +},{"../util/SyntaxUnit":26,"./Parser":6}],6:[function(require,module,exports){ +"use strict"; - //if we're not at the end of the input... - if (this._cursor < this._input.length){ +module.exports = Parser; - //if the last character was a newline, increment row count - //and reset column count - if (this._input.charAt(this._cursor) == "\n"){ - this._line++; - this._col=1; - } else { - this._col++; - } +var EventTarget = require("../util/EventTarget"); +var SyntaxError = require("../util/SyntaxError"); +var SyntaxUnit = require("../util/SyntaxUnit"); - //get character and increment cursor and column - c = this._input.charAt(this._cursor++); - } +var Combinator = require("./Combinator"); +var MediaFeature = require("./MediaFeature"); +var MediaQuery = require("./MediaQuery"); +var PropertyName = require("./PropertyName"); +var PropertyValue = require("./PropertyValue"); +var PropertyValuePart = require("./PropertyValuePart"); +var Selector = require("./Selector"); +var SelectorPart = require("./SelectorPart"); +var SelectorSubPart = require("./SelectorSubPart"); +var TokenStream = require("./TokenStream"); +var Tokens = require("./Tokens"); +var Validation = require("./Validation"); - return c; - }, +/** + * A CSS3 parser. + * @namespace parserlib.css + * @class Parser + * @constructor + * @param {Object} options (Optional) Various options for the parser: + * starHack (true|false) to allow IE6 star hack as valid, + * underscoreHack (true|false) to interpret leading underscores + * as IE6-7 targeting for known properties, ieFilters (true|false) + * to indicate that IE < 8 filters should be accepted and not throw + * syntax errors. + */ +function Parser(options) { - //------------------------------------------------------------------------- - // Misc - //------------------------------------------------------------------------- + //inherit event functionality + EventTarget.call(this); - /** - * Saves the current location so it can be returned to later. - * @method mark - * @return {void} - */ - mark: function(){ - this._bookmark = { - cursor: this._cursor, - line: this._line, - col: this._col - }; - }, - reset: function(){ - if (this._bookmark){ - this._cursor = this._bookmark.cursor; - this._line = this._bookmark.line; - this._col = this._bookmark.col; - delete this._bookmark; - } - }, + this.options = options || {}; - //------------------------------------------------------------------------- - // Advanced reading - //------------------------------------------------------------------------- + this._tokenStream = null; +} - /** - * Reads up to and including the given string. Throws an error if that - * string is not found. - * @param {String} pattern The string to read. - * @return {String} The string when it is found. - * @throws Error when the string pattern is not found. - * @method readTo - */ - readTo: function(pattern){ +//Static constants +Parser.DEFAULT_TYPE = 0; +Parser.COMBINATOR_TYPE = 1; +Parser.MEDIA_FEATURE_TYPE = 2; +Parser.MEDIA_QUERY_TYPE = 3; +Parser.PROPERTY_NAME_TYPE = 4; +Parser.PROPERTY_VALUE_TYPE = 5; +Parser.PROPERTY_VALUE_PART_TYPE = 6; +Parser.SELECTOR_TYPE = 7; +Parser.SELECTOR_PART_TYPE = 8; +Parser.SELECTOR_SUB_PART_TYPE = 9; - var buffer = "", - c; +Parser.prototype = function() { - /* - * First, buffer must be the same length as the pattern. - * Then, buffer must end with the pattern or else reach the - * end of the input. - */ - while (buffer.length < pattern.length || buffer.lastIndexOf(pattern) != buffer.length - pattern.length){ - c = this.read(); - if (c){ - buffer += c; - } else { - throw new Error("Expected \"" + pattern + "\" at line " + this._line + ", col " + this._col + "."); - } - } + var proto = new EventTarget(), //new prototype + prop, + additions = { + __proto__: null, - return buffer; + //restore constructor + constructor: Parser, - }, + //instance constants - yuck + DEFAULT_TYPE : 0, + COMBINATOR_TYPE : 1, + MEDIA_FEATURE_TYPE : 2, + MEDIA_QUERY_TYPE : 3, + PROPERTY_NAME_TYPE : 4, + PROPERTY_VALUE_TYPE : 5, + PROPERTY_VALUE_PART_TYPE : 6, + SELECTOR_TYPE : 7, + SELECTOR_PART_TYPE : 8, + SELECTOR_SUB_PART_TYPE : 9, - /** - * Reads characters while each character causes the given - * filter function to return true. The function is passed - * in each character and either returns true to continue - * reading or false to stop. - * @param {Function} filter The function to read on each character. - * @return {String} The string made up of all characters that passed the - * filter check. - * @method readWhile - */ - readWhile: function(filter){ + //----------------------------------------------------------------- + // Grammar + //----------------------------------------------------------------- - var buffer = "", - c = this.read(); + _stylesheet: function() { - while(c !== null && filter(c)){ - buffer += c; - c = this.read(); - } + /* + * stylesheet + * : [ CHARSET_SYM S* STRING S* ';' ]? + * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* + * [ namespace [S|CDO|CDC]* ]* + * [ [ ruleset | media | page | font_face | keyframes_rule | supports_rule ] [S|CDO|CDC]* ]* + * ; + */ - return buffer; + var tokenStream = this._tokenStream, + count, + token, + tt; - }, + this.fire("startstylesheet"); - /** - * Reads characters that match either text or a regular expression and - * returns those characters. If a match is found, the row and column - * are adjusted; if no match is found, the reader's state is unchanged. - * reading or false to stop. - * @param {String|RegExp} matchter If a string, then the literal string - * value is searched for. If a regular expression, then any string - * matching the pattern is search for. - * @return {String} The string made up of all characters that matched or - * null if there was no match. - * @method readMatch - */ - readMatch: function(matcher){ + //try to read character set + this._charset(); - var source = this._input.substring(this._cursor), - value = null; + this._skipCruft(); - //if it's a string, just do a straight match - if (typeof matcher == "string"){ - if (source.indexOf(matcher) === 0){ - value = this.readCount(matcher.length); - } - } else if (matcher instanceof RegExp){ - if (matcher.test(source)){ - value = this.readCount(RegExp.lastMatch.length); - } - } + //try to read imports - may be more than one + while (tokenStream.peek() === Tokens.IMPORT_SYM) { + this._import(); + this._skipCruft(); + } - return value; - }, + //try to read namespaces - may be more than one + while (tokenStream.peek() === Tokens.NAMESPACE_SYM) { + this._namespace(); + this._skipCruft(); + } + //get the next token + tt = tokenStream.peek(); - /** - * Reads a given number of characters. If the end of the input is reached, - * it reads only the remaining characters and does not throw an error. - * @param {int} count The number of characters to read. - * @return {String} The string made up the read characters. - * @method readCount - */ - readCount: function(count){ - var buffer = ""; + //try to read the rest + while (tt > Tokens.EOF) { - while(count--){ - buffer += this.read(); - } + try { - return buffer; - } + switch (tt) { + case Tokens.MEDIA_SYM: + this._media(); + this._skipCruft(); + break; + case Tokens.PAGE_SYM: + this._page(); + this._skipCruft(); + break; + case Tokens.FONT_FACE_SYM: + this._font_face(); + this._skipCruft(); + break; + case Tokens.KEYFRAMES_SYM: + this._keyframes(); + this._skipCruft(); + break; + case Tokens.VIEWPORT_SYM: + this._viewport(); + this._skipCruft(); + break; + case Tokens.DOCUMENT_SYM: + this._document(); + this._skipCruft(); + break; + case Tokens.SUPPORTS_SYM: + this._supports(); + this._skipCruft(); + break; + case Tokens.UNKNOWN_SYM: //unknown @ rule + tokenStream.get(); + if (!this.options.strict) { -}; -/** - * Type to use when a syntax error occurs. - * @class SyntaxError - * @namespace parserlib.util - * @constructor - * @param {String} message The error message. - * @param {int} line The line at which the error occurred. - * @param {int} col The column at which the error occurred. - */ -function SyntaxError(message, line, col){ + //fire error event + this.fire({ + type: "error", + error: null, + message: "Unknown @ rule: " + tokenStream.LT(0).value + ".", + line: tokenStream.LT(0).startLine, + col: tokenStream.LT(0).startCol + }); - /** - * The column at which the error occurred. - * @type int - * @property col - */ - this.col = col; + //skip braces + count=0; + while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) === Tokens.LBRACE) { + count++; //keep track of nesting depth + } - /** - * The line at which the error occurred. - * @type int - * @property line - */ - this.line = line; + while (count) { + tokenStream.advance([Tokens.RBRACE]); + count--; + } - /** - * The text representation of the unit. - * @type String - * @property text - */ - this.message = message; + } else { + //not a syntax error, rethrow it + throw new SyntaxError("Unknown @ rule.", tokenStream.LT(0).startLine, tokenStream.LT(0).startCol); + } + break; + case Tokens.S: + this._readWhitespace(); + break; + default: + if (!this._ruleset()) { -} + //error handling for known issues + switch (tt) { + case Tokens.CHARSET_SYM: + token = tokenStream.LT(1); + this._charset(false); + throw new SyntaxError("@charset not allowed here.", token.startLine, token.startCol); + case Tokens.IMPORT_SYM: + token = tokenStream.LT(1); + this._import(false); + throw new SyntaxError("@import not allowed here.", token.startLine, token.startCol); + case Tokens.NAMESPACE_SYM: + token = tokenStream.LT(1); + this._namespace(false); + throw new SyntaxError("@namespace not allowed here.", token.startLine, token.startCol); + default: + tokenStream.get(); //get the last token + this._unexpectedToken(tokenStream.token()); + } -//inherit from Error -SyntaxError.prototype = new Error(); -/** - * Base type to represent a single syntactic unit. - * @class SyntaxUnit - * @namespace parserlib.util - * @constructor - * @param {String} text The text of the unit. - * @param {int} line The line of text on which the unit resides. - * @param {int} col The column of text on which the unit resides. - */ -function SyntaxUnit(text, line, col, type){ + } + } + } catch (ex) { + if (ex instanceof SyntaxError && !this.options.strict) { + this.fire({ + type: "error", + error: ex, + message: ex.message, + line: ex.line, + col: ex.col + }); + } else { + throw ex; + } + } + tt = tokenStream.peek(); + } - /** - * The column of text on which the unit resides. - * @type int - * @property col - */ - this.col = col; + if (tt !== Tokens.EOF) { + this._unexpectedToken(tokenStream.token()); + } - /** - * The line of text on which the unit resides. - * @type int - * @property line - */ - this.line = line; + this.fire("endstylesheet"); + }, - /** - * The text representation of the unit. - * @type String - * @property text - */ - this.text = text; + _charset: function(emit) { + var tokenStream = this._tokenStream, + charset, + token, + line, + col; - /** - * The type of syntax unit. - * @type int - * @property type - */ - this.type = type; -} + if (tokenStream.match(Tokens.CHARSET_SYM)) { + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; -/** - * Create a new syntax unit based solely on the given token. - * Convenience method for creating a new syntax unit when - * it represents a single token instead of multiple. - * @param {Object} token The token object to represent. - * @return {parserlib.util.SyntaxUnit} The object representing the token. - * @static - * @method fromToken - */ -SyntaxUnit.fromToken = function(token){ - return new SyntaxUnit(token.value, token.startLine, token.startCol); -}; + this._readWhitespace(); + tokenStream.mustMatch(Tokens.STRING); -SyntaxUnit.prototype = { + token = tokenStream.token(); + charset = token.value; - //restore constructor - constructor: SyntaxUnit, - - /** - * Returns the text representation of the unit. - * @return {String} The text representation of the unit. - * @method valueOf - */ - valueOf: function(){ - return this.toString(); - }, - - /** - * Returns the text representation of the unit. - * @return {String} The text representation of the unit. - * @method toString - */ - toString: function(){ - return this.text; - } + this._readWhitespace(); + tokenStream.mustMatch(Tokens.SEMICOLON); -}; -/*global StringReader, SyntaxError*/ + if (emit !== false) { + this.fire({ + type: "charset", + charset:charset, + line: line, + col: col + }); + } + } + }, -/** - * Generic TokenStream providing base functionality. - * @class TokenStreamBase - * @namespace parserlib.util - * @constructor - * @param {String|StringReader} input The text to tokenize or a reader from - * which to read the input. - */ -function TokenStreamBase(input, tokenData){ + _import: function(emit) { + /* + * import + * : IMPORT_SYM S* + * [STRING|URI] S* media_query_list? ';' S* + */ - /** - * The string reader for easy access to the text. - * @type StringReader - * @property _reader - * @private - */ - this._reader = input ? new StringReader(input.toString()) : null; - - /** - * Token object for the last consumed token. - * @type Token - * @property _token - * @private - */ - this._token = null; - - /** - * The array of token information. - * @type Array - * @property _tokenData - * @private - */ - this._tokenData = tokenData; - - /** - * Lookahead token buffer. - * @type Array - * @property _lt - * @private - */ - this._lt = []; - - /** - * Lookahead token buffer index. - * @type int - * @property _ltIndex - * @private - */ - this._ltIndex = 0; - - this._ltIndexCache = []; -} + var tokenStream = this._tokenStream, + uri, + importToken, + mediaList = []; -/** - * Accepts an array of token information and outputs - * an array of token data containing key-value mappings - * and matching functions that the TokenStream needs. - * @param {Array} tokens An array of token descriptors. - * @return {Array} An array of processed token data. - * @method createTokenData - * @static - */ -TokenStreamBase.createTokenData = function(tokens){ + //read import symbol + tokenStream.mustMatch(Tokens.IMPORT_SYM); + importToken = tokenStream.token(); + this._readWhitespace(); - var nameMap = [], - typeMap = {}, - tokenData = tokens.concat([]), - i = 0, - len = tokenData.length+1; - - tokenData.UNKNOWN = -1; - tokenData.unshift({name:"EOF"}); + tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); - for (; i < len; i++){ - nameMap.push(tokenData[i].name); - tokenData[tokenData[i].name] = i; - if (tokenData[i].text){ - typeMap[tokenData[i].text] = i; - } - } - - tokenData.name = function(tt){ - return nameMap[tt]; - }; - - tokenData.type = function(c){ - return typeMap[c]; - }; - - return tokenData; -}; + //grab the URI value + uri = tokenStream.token().value.replace(/^(?:url\()?["']?([^"']+?)["']?\)?$/, "$1"); -TokenStreamBase.prototype = { + this._readWhitespace(); - //restore constructor - constructor: TokenStreamBase, - - //------------------------------------------------------------------------- - // Matching methods - //------------------------------------------------------------------------- - - /** - * Determines if the next token matches the given token type. - * If so, that token is consumed; if not, the token is placed - * back onto the token stream. You can pass in any number of - * token types and this will return true if any of the token - * types is found. - * @param {int|int[]} tokenTypes Either a single token type or an array of - * token types that the next token might be. If an array is passed, - * it's assumed that the token can be any of these. - * @param {variant} channel (Optional) The channel to read from. If not - * provided, reads from the default (unnamed) channel. - * @return {Boolean} True if the token type matches, false if not. - * @method match - */ - match: function(tokenTypes, channel){ - - //always convert to an array, makes things easier - if (!(tokenTypes instanceof Array)){ - tokenTypes = [tokenTypes]; - } - - var tt = this.get(channel), - i = 0, - len = tokenTypes.length; - - while(i < len){ - if (tt == tokenTypes[i++]){ - return true; - } - } - - //no match found, put the token back - this.unget(); - return false; - }, - - /** - * Determines if the next token matches the given token type. - * If so, that token is consumed; if not, an error is thrown. - * @param {int|int[]} tokenTypes Either a single token type or an array of - * token types that the next token should be. If an array is passed, - * it's assumed that the token must be one of these. - * @param {variant} channel (Optional) The channel to read from. If not - * provided, reads from the default (unnamed) channel. - * @return {void} - * @method mustMatch - */ - mustMatch: function(tokenTypes, channel){ + mediaList = this._media_query_list(); - var token; + //must end with a semicolon + tokenStream.mustMatch(Tokens.SEMICOLON); + this._readWhitespace(); - //always convert to an array, makes things easier - if (!(tokenTypes instanceof Array)){ - tokenTypes = [tokenTypes]; - } + if (emit !== false) { + this.fire({ + type: "import", + uri: uri, + media: mediaList, + line: importToken.startLine, + col: importToken.startCol + }); + } - if (!this.match.apply(this, arguments)){ - token = this.LT(1); - throw new SyntaxError("Expected " + this._tokenData[tokenTypes[0]].name + - " at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); - } - }, - - //------------------------------------------------------------------------- - // Consuming methods - //------------------------------------------------------------------------- - - /** - * Keeps reading from the token stream until either one of the specified - * token types is found or until the end of the input is reached. - * @param {int|int[]} tokenTypes Either a single token type or an array of - * token types that the next token should be. If an array is passed, - * it's assumed that the token must be one of these. - * @param {variant} channel (Optional) The channel to read from. If not - * provided, reads from the default (unnamed) channel. - * @return {void} - * @method advance - */ - advance: function(tokenTypes, channel){ - - while(this.LA(0) !== 0 && !this.match(tokenTypes, channel)){ - this.get(); - } + }, - return this.LA(0); - }, - - /** - * Consumes the next token from the token stream. - * @return {int} The token type of the token that was just consumed. - * @method get - */ - get: function(channel){ - - var tokenInfo = this._tokenData, - reader = this._reader, - value, - i =0, - len = tokenInfo.length, - found = false, - token, - info; - - //check the lookahead buffer first - if (this._lt.length && this._ltIndex >= 0 && this._ltIndex < this._lt.length){ - - i++; - this._token = this._lt[this._ltIndex++]; - info = tokenInfo[this._token.type]; - - //obey channels logic - while((info.channel !== undefined && channel !== info.channel) && - this._ltIndex < this._lt.length){ - this._token = this._lt[this._ltIndex++]; - info = tokenInfo[this._token.type]; - i++; - } - - //here be dragons - if ((info.channel === undefined || channel === info.channel) && - this._ltIndex <= this._lt.length){ - this._ltIndexCache.push(i); - return this._token.type; - } - } - - //call token retriever method - token = this._getToken(); + _namespace: function(emit) { + /* + * namespace + * : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* + */ - //if it should be hidden, don't save a token - if (token.type > -1 && !tokenInfo[token.type].hide){ - - //apply token channel - token.channel = tokenInfo[token.type].channel; - - //save for later - this._token = token; - this._lt.push(token); + var tokenStream = this._tokenStream, + line, + col, + prefix, + uri; - //save space that will be moved (must be done before array is truncated) - this._ltIndexCache.push(this._lt.length - this._ltIndex + i); - - //keep the buffer under 5 items - if (this._lt.length > 5){ - this._lt.shift(); - } - - //also keep the shift buffer under 5 items - if (this._ltIndexCache.length > 5){ - this._ltIndexCache.shift(); - } - - //update lookahead index - this._ltIndex = this._lt.length; - } - - /* - * Skip to the next token if: - * 1. The token type is marked as hidden. - * 2. The token type has a channel specified and it isn't the current channel. - */ - info = tokenInfo[token.type]; - if (info && - (info.hide || - (info.channel !== undefined && channel !== info.channel))){ - return this.get(channel); - } else { - //return just the type - return token.type; - } - }, - - /** - * Looks ahead a certain number of tokens and returns the token type at - * that position. This will throw an error if you lookahead past the - * end of input, past the size of the lookahead buffer, or back past - * the first token in the lookahead buffer. - * @param {int} The index of the token type to retrieve. 0 for the - * current token, 1 for the next, -1 for the previous, etc. - * @return {int} The token type of the token in the given position. - * @method LA - */ - LA: function(index){ - var total = index, - tt; - if (index > 0){ - //TODO: Store 5 somewhere - if (index > 5){ - throw new Error("Too much lookahead."); - } - - //get all those tokens - while(total){ - tt = this.get(); - total--; - } - - //unget all those tokens - while(total < index){ - this.unget(); - total++; - } - } else if (index < 0){ - - if(this._lt[this._ltIndex+index]){ - tt = this._lt[this._ltIndex+index].type; - } else { - throw new Error("Too much lookbehind."); - } - - } else { - tt = this._token.type; - } - - return tt; - - }, - - /** - * Looks ahead a certain number of tokens and returns the token at - * that position. This will throw an error if you lookahead past the - * end of input, past the size of the lookahead buffer, or back past - * the first token in the lookahead buffer. - * @param {int} The index of the token type to retrieve. 0 for the - * current token, 1 for the next, -1 for the previous, etc. - * @return {Object} The token of the token in the given position. - * @method LA - */ - LT: function(index){ - - //lookahead first to prime the token buffer - this.LA(index); - - //now find the token, subtract one because _ltIndex is already at the next index - return this._lt[this._ltIndex+index-1]; - }, - - /** - * Returns the token type for the next token in the stream without - * consuming it. - * @return {int} The token type of the next token in the stream. - * @method peek - */ - peek: function(){ - return this.LA(1); - }, - - /** - * Returns the actual token object for the last consumed token. - * @return {Token} The token object for the last consumed token. - * @method token - */ - token: function(){ - return this._token; - }, - - /** - * Returns the name of the token for the given token type. - * @param {int} tokenType The type of token to get the name of. - * @return {String} The name of the token or "UNKNOWN_TOKEN" for any - * invalid token type. - * @method tokenName - */ - tokenName: function(tokenType){ - if (tokenType < 0 || tokenType > this._tokenData.length){ - return "UNKNOWN_TOKEN"; - } else { - return this._tokenData[tokenType].name; - } - }, - - /** - * Returns the token type value for the given token name. - * @param {String} tokenName The name of the token whose value should be returned. - * @return {int} The token type value for the given token name or -1 - * for an unknown token. - * @method tokenName - */ - tokenType: function(tokenName){ - return this._tokenData[tokenName] || -1; - }, - - /** - * Returns the last consumed token to the token stream. - * @method unget - */ - unget: function(){ - //if (this._ltIndex > -1){ - if (this._ltIndexCache.length){ - this._ltIndex -= this._ltIndexCache.pop();//--; - this._token = this._lt[this._ltIndex - 1]; - } else { - throw new Error("Too much lookahead."); - } - } + //read import symbol + tokenStream.mustMatch(Tokens.NAMESPACE_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + this._readWhitespace(); -}; + //it's a namespace prefix - no _namespace_prefix() method because it's just an IDENT + if (tokenStream.match(Tokens.IDENT)) { + prefix = tokenStream.token().value; + this._readWhitespace(); + } + tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); + /*if (!tokenStream.match(Tokens.STRING)){ + tokenStream.mustMatch(Tokens.URI); + }*/ + //grab the URI value + uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1"); + this._readWhitespace(); -parserlib.util = { -StringReader: StringReader, -SyntaxError : SyntaxError, -SyntaxUnit : SyntaxUnit, -EventTarget : EventTarget, -TokenStreamBase : TokenStreamBase -}; -})(); + //must end with a semicolon + tokenStream.mustMatch(Tokens.SEMICOLON); + this._readWhitespace(); + if (emit !== false) { + this.fire({ + type: "namespace", + prefix: prefix, + uri: uri, + line: line, + col: col + }); + } -/* -Parser-Lib -Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved. + }, -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + _supports: function(emit) { + /* + * supports_rule + * : SUPPORTS_SYM S* supports_condition S* group_rule_body + * ; + */ + var tokenStream = this._tokenStream, + line, + col; -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + if (tokenStream.match(Tokens.SUPPORTS_SYM)) { + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. + this._readWhitespace(); + this._supports_condition(); + this._readWhitespace(); -*/ -/* Version v0.2.2, Build time: 17-January-2013 10:26:34 */ -(function(){ -var EventTarget = parserlib.util.EventTarget, -TokenStreamBase = parserlib.util.TokenStreamBase, -StringReader = parserlib.util.StringReader, -SyntaxError = parserlib.util.SyntaxError, -SyntaxUnit = parserlib.util.SyntaxUnit; + tokenStream.mustMatch(Tokens.LBRACE); + this._readWhitespace(); + if (emit !== false) { + this.fire({ + type: "startsupports", + line: line, + col: col + }); + } -var Colors = { - aliceblue :"#f0f8ff", - antiquewhite :"#faebd7", - aqua :"#00ffff", - aquamarine :"#7fffd4", - azure :"#f0ffff", - beige :"#f5f5dc", - bisque :"#ffe4c4", - black :"#000000", - blanchedalmond :"#ffebcd", - blue :"#0000ff", - blueviolet :"#8a2be2", - brown :"#a52a2a", - burlywood :"#deb887", - cadetblue :"#5f9ea0", - chartreuse :"#7fff00", - chocolate :"#d2691e", - coral :"#ff7f50", - cornflowerblue :"#6495ed", - cornsilk :"#fff8dc", - crimson :"#dc143c", - cyan :"#00ffff", - darkblue :"#00008b", - darkcyan :"#008b8b", - darkgoldenrod :"#b8860b", - darkgray :"#a9a9a9", - darkgreen :"#006400", - darkkhaki :"#bdb76b", - darkmagenta :"#8b008b", - darkolivegreen :"#556b2f", - darkorange :"#ff8c00", - darkorchid :"#9932cc", - darkred :"#8b0000", - darksalmon :"#e9967a", - darkseagreen :"#8fbc8f", - darkslateblue :"#483d8b", - darkslategray :"#2f4f4f", - darkturquoise :"#00ced1", - darkviolet :"#9400d3", - deeppink :"#ff1493", - deepskyblue :"#00bfff", - dimgray :"#696969", - dodgerblue :"#1e90ff", - firebrick :"#b22222", - floralwhite :"#fffaf0", - forestgreen :"#228b22", - fuchsia :"#ff00ff", - gainsboro :"#dcdcdc", - ghostwhite :"#f8f8ff", - gold :"#ffd700", - goldenrod :"#daa520", - gray :"#808080", - green :"#008000", - greenyellow :"#adff2f", - honeydew :"#f0fff0", - hotpink :"#ff69b4", - indianred :"#cd5c5c", - indigo :"#4b0082", - ivory :"#fffff0", - khaki :"#f0e68c", - lavender :"#e6e6fa", - lavenderblush :"#fff0f5", - lawngreen :"#7cfc00", - lemonchiffon :"#fffacd", - lightblue :"#add8e6", - lightcoral :"#f08080", - lightcyan :"#e0ffff", - lightgoldenrodyellow :"#fafad2", - lightgray :"#d3d3d3", - lightgreen :"#90ee90", - lightpink :"#ffb6c1", - lightsalmon :"#ffa07a", - lightseagreen :"#20b2aa", - lightskyblue :"#87cefa", - lightslategray :"#778899", - lightsteelblue :"#b0c4de", - lightyellow :"#ffffe0", - lime :"#00ff00", - limegreen :"#32cd32", - linen :"#faf0e6", - magenta :"#ff00ff", - maroon :"#800000", - mediumaquamarine:"#66cdaa", - mediumblue :"#0000cd", - mediumorchid :"#ba55d3", - mediumpurple :"#9370d8", - mediumseagreen :"#3cb371", - mediumslateblue :"#7b68ee", - mediumspringgreen :"#00fa9a", - mediumturquoise :"#48d1cc", - mediumvioletred :"#c71585", - midnightblue :"#191970", - mintcream :"#f5fffa", - mistyrose :"#ffe4e1", - moccasin :"#ffe4b5", - navajowhite :"#ffdead", - navy :"#000080", - oldlace :"#fdf5e6", - olive :"#808000", - olivedrab :"#6b8e23", - orange :"#ffa500", - orangered :"#ff4500", - orchid :"#da70d6", - palegoldenrod :"#eee8aa", - palegreen :"#98fb98", - paleturquoise :"#afeeee", - palevioletred :"#d87093", - papayawhip :"#ffefd5", - peachpuff :"#ffdab9", - peru :"#cd853f", - pink :"#ffc0cb", - plum :"#dda0dd", - powderblue :"#b0e0e6", - purple :"#800080", - red :"#ff0000", - rosybrown :"#bc8f8f", - royalblue :"#4169e1", - saddlebrown :"#8b4513", - salmon :"#fa8072", - sandybrown :"#f4a460", - seagreen :"#2e8b57", - seashell :"#fff5ee", - sienna :"#a0522d", - silver :"#c0c0c0", - skyblue :"#87ceeb", - slateblue :"#6a5acd", - slategray :"#708090", - snow :"#fffafa", - springgreen :"#00ff7f", - steelblue :"#4682b4", - tan :"#d2b48c", - teal :"#008080", - thistle :"#d8bfd8", - tomato :"#ff6347", - turquoise :"#40e0d0", - violet :"#ee82ee", - wheat :"#f5deb3", - white :"#ffffff", - whitesmoke :"#f5f5f5", - yellow :"#ffff00", - yellowgreen :"#9acd32", - //CSS2 system colors http://www.w3.org/TR/css3-color/#css2-system - activeBorder :"Active window border.", - activecaption :"Active window caption.", - appworkspace :"Background color of multiple document interface.", - background :"Desktop background.", - buttonface :"The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.", - buttonhighlight :"The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border.", - buttonshadow :"The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border.", - buttontext :"Text on push buttons.", - captiontext :"Text in caption, size box, and scrollbar arrow box.", - graytext :"Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.", - highlight :"Item(s) selected in a control.", - highlighttext :"Text of item(s) selected in a control.", - inactiveborder :"Inactive window border.", - inactivecaption :"Inactive window caption.", - inactivecaptiontext :"Color of text in an inactive caption.", - infobackground :"Background color for tooltip controls.", - infotext :"Text color for tooltip controls.", - menu :"Menu background.", - menutext :"Text in menus.", - scrollbar :"Scroll bar gray area.", - threeddarkshadow :"The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", - threedface :"The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", - threedhighlight :"The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", - threedlightshadow :"The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", - threedshadow :"The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", - window :"Window background.", - windowframe :"Window frame.", - windowtext :"Text in windows." -}; -/*global SyntaxUnit, Parser*/ -/** - * Represents a selector combinator (whitespace, +, >). - * @namespace parserlib.css - * @class Combinator - * @extends parserlib.util.SyntaxUnit - * @constructor - * @param {String} text The text representation of the unit. - * @param {int} line The line of text on which the unit resides. - * @param {int} col The column of text on which the unit resides. - */ -function Combinator(text, line, col){ - - SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE); - - /** - * The type of modifier. - * @type String - * @property type - */ - this.type = "unknown"; - - //pretty simple - if (/^\s+$/.test(text)){ - this.type = "descendant"; - } else if (text == ">"){ - this.type = "child"; - } else if (text == "+"){ - this.type = "adjacent-sibling"; - } else if (text == "~"){ - this.type = "sibling"; - } + while (true) { + if (!this._ruleset()) { + break; + } + } -} + tokenStream.mustMatch(Tokens.RBRACE); + this._readWhitespace(); -Combinator.prototype = new SyntaxUnit(); -Combinator.prototype.constructor = Combinator; + this.fire({ + type: "endsupports", + line: line, + col: col + }); + } + }, + _supports_condition: function() { + /* + * supports_condition + * : supports_negation | supports_conjunction | supports_disjunction | + * supports_condition_in_parens + * ; + */ + var tokenStream = this._tokenStream, + ident; -/*global SyntaxUnit, Parser*/ -/** - * Represents a media feature, such as max-width:500. - * @namespace parserlib.css - * @class MediaFeature - * @extends parserlib.util.SyntaxUnit - * @constructor - * @param {SyntaxUnit} name The name of the feature. - * @param {SyntaxUnit} value The value of the feature or null if none. - */ -function MediaFeature(name, value){ - - SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol, Parser.MEDIA_FEATURE_TYPE); + if (tokenStream.match(Tokens.IDENT)) { + ident = tokenStream.token().value.toLowerCase(); - /** - * The name of the media feature - * @type String - * @property name - */ - this.name = name; + if (ident === "not") { + tokenStream.mustMatch(Tokens.S); + this._supports_condition_in_parens(); + } else { + tokenStream.unget(); + } + } else { + this._supports_condition_in_parens(); + this._readWhitespace(); - /** - * The value for the feature or null if there is none. - * @type SyntaxUnit - * @property value - */ - this.value = value; -} + while (tokenStream.peek() === Tokens.IDENT) { + ident = tokenStream.LT(1).value.toLowerCase(); + if (ident === "and" || ident === "or") { + tokenStream.mustMatch(Tokens.IDENT); + this._readWhitespace(); + this._supports_condition_in_parens(); + this._readWhitespace(); + } + } + } + }, -MediaFeature.prototype = new SyntaxUnit(); -MediaFeature.prototype.constructor = MediaFeature; + _supports_condition_in_parens: function() { + /* + * supports_condition_in_parens + * : ( '(' S* supports_condition S* ')' ) | supports_declaration_condition | + * general_enclosed + * ; + */ + var tokenStream = this._tokenStream, + ident; + if (tokenStream.match(Tokens.LPAREN)) { + this._readWhitespace(); + if (tokenStream.match(Tokens.IDENT)) { + // look ahead for not keyword, if not given, continue with declaration condition. + ident = tokenStream.token().value.toLowerCase(); + if (ident === "not") { + this._readWhitespace(); + this._supports_condition(); + this._readWhitespace(); + tokenStream.mustMatch(Tokens.RPAREN); + } else { + tokenStream.unget(); + this._supports_declaration_condition(false); + } + } else { + this._supports_condition(); + this._readWhitespace(); + tokenStream.mustMatch(Tokens.RPAREN); + } + } else { + this._supports_declaration_condition(); + } + }, -/*global SyntaxUnit, Parser*/ -/** - * Represents an individual media query. - * @namespace parserlib.css - * @class MediaQuery - * @extends parserlib.util.SyntaxUnit - * @constructor - * @param {String} modifier The modifier "not" or "only" (or null). - * @param {String} mediaType The type of media (i.e., "print"). - * @param {Array} parts Array of selectors parts making up this selector. - * @param {int} line The line of text on which the unit resides. - * @param {int} col The column of text on which the unit resides. - */ -function MediaQuery(modifier, mediaType, features, line, col){ - - SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType : "") + (mediaType && features.length > 0 ? " and " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE); + _supports_declaration_condition: function(requireStartParen) { + /* + * supports_declaration_condition + * : '(' S* declaration ')' + * ; + */ + var tokenStream = this._tokenStream; - /** - * The media modifier ("not" or "only") - * @type String - * @property modifier - */ - this.modifier = modifier; + if (requireStartParen !== false) { + tokenStream.mustMatch(Tokens.LPAREN); + } + this._readWhitespace(); + this._declaration(); + tokenStream.mustMatch(Tokens.RPAREN); + }, - /** - * The mediaType (i.e., "print") - * @type String - * @property mediaType - */ - this.mediaType = mediaType; - - /** - * The parts that make up the selector. - * @type Array - * @property features - */ - this.features = features; + _media: function() { + /* + * media + * : MEDIA_SYM S* media_query_list S* '{' S* ruleset* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col, + mediaList;// = []; -} + //look for @media + tokenStream.mustMatch(Tokens.MEDIA_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; -MediaQuery.prototype = new SyntaxUnit(); -MediaQuery.prototype.constructor = MediaQuery; + this._readWhitespace(); + mediaList = this._media_query_list(); -/*global Tokens, TokenStream, SyntaxError, Properties, Validation, ValidationError, SyntaxUnit, - PropertyValue, PropertyValuePart, SelectorPart, SelectorSubPart, Selector, - PropertyName, Combinator, MediaFeature, MediaQuery, EventTarget */ + tokenStream.mustMatch(Tokens.LBRACE); + this._readWhitespace(); -/** - * A CSS3 parser. - * @namespace parserlib.css - * @class Parser - * @constructor - * @param {Object} options (Optional) Various options for the parser: - * starHack (true|false) to allow IE6 star hack as valid, - * underscoreHack (true|false) to interpret leading underscores - * as IE6-7 targeting for known properties, ieFilters (true|false) - * to indicate that IE < 8 filters should be accepted and not throw - * syntax errors. - */ -function Parser(options){ + this.fire({ + type: "startmedia", + media: mediaList, + line: line, + col: col + }); - //inherit event functionality - EventTarget.call(this); - - - this.options = options || {}; - - this._tokenStream = null; -} - -//Static constants -Parser.DEFAULT_TYPE = 0; -Parser.COMBINATOR_TYPE = 1; -Parser.MEDIA_FEATURE_TYPE = 2; -Parser.MEDIA_QUERY_TYPE = 3; -Parser.PROPERTY_NAME_TYPE = 4; -Parser.PROPERTY_VALUE_TYPE = 5; -Parser.PROPERTY_VALUE_PART_TYPE = 6; -Parser.SELECTOR_TYPE = 7; -Parser.SELECTOR_PART_TYPE = 8; -Parser.SELECTOR_SUB_PART_TYPE = 9; - -Parser.prototype = function(){ - - var proto = new EventTarget(), //new prototype - prop, - additions = { - - //restore constructor - constructor: Parser, - - //instance constants - yuck - DEFAULT_TYPE : 0, - COMBINATOR_TYPE : 1, - MEDIA_FEATURE_TYPE : 2, - MEDIA_QUERY_TYPE : 3, - PROPERTY_NAME_TYPE : 4, - PROPERTY_VALUE_TYPE : 5, - PROPERTY_VALUE_PART_TYPE : 6, - SELECTOR_TYPE : 7, - SELECTOR_PART_TYPE : 8, - SELECTOR_SUB_PART_TYPE : 9, - - //----------------------------------------------------------------- - // Grammar - //----------------------------------------------------------------- - - _stylesheet: function(){ - - /* - * stylesheet - * : [ CHARSET_SYM S* STRING S* ';' ]? - * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* - * [ namespace [S|CDO|CDC]* ]* - * [ [ ruleset | media | page | font_face | keyframes ] [S|CDO|CDC]* ]* - * ; - */ - - var tokenStream = this._tokenStream, - charset = null, - count, - token, - tt; - - this.fire("startstylesheet"); - - //try to read character set - this._charset(); - - this._skipCruft(); - - //try to read imports - may be more than one - while (tokenStream.peek() == Tokens.IMPORT_SYM){ - this._import(); - this._skipCruft(); - } - - //try to read namespaces - may be more than one - while (tokenStream.peek() == Tokens.NAMESPACE_SYM){ - this._namespace(); - this._skipCruft(); - } - - //get the next token - tt = tokenStream.peek(); - - //try to read the rest - while(tt > Tokens.EOF){ - - try { - - switch(tt){ - case Tokens.MEDIA_SYM: - this._media(); - this._skipCruft(); - break; - case Tokens.PAGE_SYM: - this._page(); - this._skipCruft(); - break; - case Tokens.FONT_FACE_SYM: - this._font_face(); - this._skipCruft(); - break; - case Tokens.KEYFRAMES_SYM: - this._keyframes(); - this._skipCruft(); - break; - case Tokens.UNKNOWN_SYM: //unknown @ rule - tokenStream.get(); - if (!this.options.strict){ - - //fire error event - this.fire({ - type: "error", - error: null, - message: "Unknown @ rule: " + tokenStream.LT(0).value + ".", - line: tokenStream.LT(0).startLine, - col: tokenStream.LT(0).startCol - }); - - //skip braces - count=0; - while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) == Tokens.LBRACE){ - count++; //keep track of nesting depth - } - - while(count){ - tokenStream.advance([Tokens.RBRACE]); - count--; - } - - } else { - //not a syntax error, rethrow it - throw new SyntaxError("Unknown @ rule.", tokenStream.LT(0).startLine, tokenStream.LT(0).startCol); - } - break; - case Tokens.S: - this._readWhitespace(); - break; - default: - if(!this._ruleset()){ - - //error handling for known issues - switch(tt){ - case Tokens.CHARSET_SYM: - token = tokenStream.LT(1); - this._charset(false); - throw new SyntaxError("@charset not allowed here.", token.startLine, token.startCol); - case Tokens.IMPORT_SYM: - token = tokenStream.LT(1); - this._import(false); - throw new SyntaxError("@import not allowed here.", token.startLine, token.startCol); - case Tokens.NAMESPACE_SYM: - token = tokenStream.LT(1); - this._namespace(false); - throw new SyntaxError("@namespace not allowed here.", token.startLine, token.startCol); - default: - tokenStream.get(); //get the last token - this._unexpectedToken(tokenStream.token()); - } - - } - } - } catch(ex) { - if (ex instanceof SyntaxError && !this.options.strict){ - this.fire({ - type: "error", - error: ex, - message: ex.message, - line: ex.line, - col: ex.col - }); - } else { - throw ex; - } - } - - tt = tokenStream.peek(); - } - - if (tt != Tokens.EOF){ - this._unexpectedToken(tokenStream.token()); - } - - this.fire("endstylesheet"); - }, - - _charset: function(emit){ - var tokenStream = this._tokenStream, - charset, - token, - line, - col; - - if (tokenStream.match(Tokens.CHARSET_SYM)){ - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - tokenStream.mustMatch(Tokens.STRING); - - token = tokenStream.token(); - charset = token.value; - - this._readWhitespace(); - tokenStream.mustMatch(Tokens.SEMICOLON); - - if (emit !== false){ - this.fire({ - type: "charset", - charset:charset, - line: line, - col: col - }); - } - } - }, - - _import: function(emit){ - /* - * import - * : IMPORT_SYM S* - * [STRING|URI] S* media_query_list? ';' S* - */ - - var tokenStream = this._tokenStream, - tt, - uri, - importToken, - mediaList = []; - - //read import symbol - tokenStream.mustMatch(Tokens.IMPORT_SYM); - importToken = tokenStream.token(); - this._readWhitespace(); - - tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); - - //grab the URI value - uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1"); - - this._readWhitespace(); - - mediaList = this._media_query_list(); - - //must end with a semicolon - tokenStream.mustMatch(Tokens.SEMICOLON); - this._readWhitespace(); - - if (emit !== false){ - this.fire({ - type: "import", - uri: uri, - media: mediaList, - line: importToken.startLine, - col: importToken.startCol - }); - } - - }, - - _namespace: function(emit){ - /* - * namespace - * : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* - */ - - var tokenStream = this._tokenStream, - line, - col, - prefix, - uri; - - //read import symbol - tokenStream.mustMatch(Tokens.NAMESPACE_SYM); - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - this._readWhitespace(); - - //it's a namespace prefix - no _namespace_prefix() method because it's just an IDENT - if (tokenStream.match(Tokens.IDENT)){ - prefix = tokenStream.token().value; - this._readWhitespace(); - } - - tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); - /*if (!tokenStream.match(Tokens.STRING)){ - tokenStream.mustMatch(Tokens.URI); - }*/ - - //grab the URI value - uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1"); - - this._readWhitespace(); - - //must end with a semicolon - tokenStream.mustMatch(Tokens.SEMICOLON); - this._readWhitespace(); - - if (emit !== false){ - this.fire({ - type: "namespace", - prefix: prefix, - uri: uri, - line: line, - col: col - }); - } - - }, - - _media: function(){ - /* - * media - * : MEDIA_SYM S* media_query_list S* '{' S* ruleset* '}' S* - * ; - */ - var tokenStream = this._tokenStream, - line, - col, - mediaList;// = []; - - //look for @media - tokenStream.mustMatch(Tokens.MEDIA_SYM); - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - - mediaList = this._media_query_list(); - - tokenStream.mustMatch(Tokens.LBRACE); - this._readWhitespace(); - - this.fire({ - type: "startmedia", - media: mediaList, - line: line, - col: col - }); - - while(true) { - if (tokenStream.peek() == Tokens.PAGE_SYM){ + while (true) { + if (tokenStream.peek() === Tokens.PAGE_SYM) { this._page(); - } else if (!this._ruleset()){ + } else if (tokenStream.peek() === Tokens.FONT_FACE_SYM) { + this._font_face(); + } else if (tokenStream.peek() === Tokens.VIEWPORT_SYM) { + this._viewport(); + } else if (tokenStream.peek() === Tokens.DOCUMENT_SYM) { + this._document(); + } else if (tokenStream.peek() === Tokens.SUPPORTS_SYM) { + this._supports(); + } else if (tokenStream.peek() === Tokens.MEDIA_SYM) { + this._media(); + } else if (!this._ruleset()) { break; - } + } } - + tokenStream.mustMatch(Tokens.RBRACE); this._readWhitespace(); - + this.fire({ type: "endmedia", media: mediaList, line: line, col: col }); - }, - + }, + //CSS3 Media Queries - _media_query_list: function(){ + _media_query_list: function() { /* * media_query_list * : S* [media_query [ ',' S* media_query ]* ]? @@ -1640,28 +1294,28 @@ Parser.prototype = function(){ */ var tokenStream = this._tokenStream, mediaList = []; - - + + this._readWhitespace(); - - if (tokenStream.peek() == Tokens.IDENT || tokenStream.peek() == Tokens.LPAREN){ + + if (tokenStream.peek() === Tokens.IDENT || tokenStream.peek() === Tokens.LPAREN) { mediaList.push(this._media_query()); } - - while(tokenStream.match(Tokens.COMMA)){ + + while (tokenStream.match(Tokens.COMMA)) { this._readWhitespace(); mediaList.push(this._media_query()); } - + return mediaList; }, - + /* * Note: "expression" in the grammar maps to the _media_expression * method. - + */ - _media_query: function(){ + _media_query: function() { /* * media_query * : [ONLY | NOT]? S* media_type S* [ AND S* expression ]* @@ -1673,42 +1327,42 @@ Parser.prototype = function(){ ident = null, token = null, expressions = []; - - if (tokenStream.match(Tokens.IDENT)){ + + if (tokenStream.match(Tokens.IDENT)) { ident = tokenStream.token().value.toLowerCase(); - + //since there's no custom tokens for these, need to manually check - if (ident != "only" && ident != "not"){ + if (ident !== "only" && ident !== "not") { tokenStream.unget(); ident = null; } else { token = tokenStream.token(); } } - + this._readWhitespace(); - - if (tokenStream.peek() == Tokens.IDENT){ + + if (tokenStream.peek() === Tokens.IDENT) { type = this._media_type(); - if (token === null){ + if (token === null) { token = tokenStream.token(); } - } else if (tokenStream.peek() == Tokens.LPAREN){ - if (token === null){ + } else if (tokenStream.peek() === Tokens.LPAREN) { + if (token === null) { token = tokenStream.LT(1); } expressions.push(this._media_expression()); - } - - if (type === null && expressions.length === 0){ + } + + if (type === null && expressions.length === 0) { return null; - } else { + } else { this._readWhitespace(); - while (tokenStream.match(Tokens.IDENT)){ - if (tokenStream.token().value.toLowerCase() != "and"){ + while (tokenStream.match(Tokens.IDENT)) { + if (tokenStream.token().value.toLowerCase() !== "and") { this._unexpectedToken(tokenStream.token()); } - + this._readWhitespace(); expressions.push(this._media_expression()); } @@ -1718,13 +1372,13 @@ Parser.prototype = function(){ }, //CSS3 Media Queries - _media_type: function(){ + _media_type: function() { /* * media_type * : IDENT * ; */ - return this._media_feature(); + return this._media_feature(); }, /** @@ -1735,7 +1389,7 @@ Parser.prototype = function(){ * @method _media_expression * @private */ - _media_expression: function(){ + _media_expression: function() { /* * expression * : '(' S* media_feature S* [ ':' S* expr ]? ')' S* @@ -1745,97 +1399,99 @@ Parser.prototype = function(){ feature = null, token, expression = null; - + tokenStream.mustMatch(Tokens.LPAREN); - + feature = this._media_feature(); this._readWhitespace(); - - if (tokenStream.match(Tokens.COLON)){ + + if (tokenStream.match(Tokens.COLON)) { this._readWhitespace(); token = tokenStream.LT(1); expression = this._expression(); } - + tokenStream.mustMatch(Tokens.RPAREN); this._readWhitespace(); - return new MediaFeature(feature, (expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null)); + return new MediaFeature(feature, expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null); }, //CSS3 Media Queries - _media_feature: function(){ + _media_feature: function() { /* * media_feature * : IDENT * ; */ var tokenStream = this._tokenStream; - + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.IDENT); - - return SyntaxUnit.fromToken(tokenStream.token()); + + return SyntaxUnit.fromToken(tokenStream.token()); }, - + //CSS3 Paged Media - _page: function(){ + _page: function() { /* * page: - * PAGE_SYM S* IDENT? pseudo_page? S* + * PAGE_SYM S* IDENT? pseudo_page? S* * '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* * ; - */ + */ var tokenStream = this._tokenStream, line, col, identifier = null, pseudoPage = null; - + //look for @page tokenStream.mustMatch(Tokens.PAGE_SYM); line = tokenStream.token().startLine; col = tokenStream.token().startCol; - + this._readWhitespace(); - - if (tokenStream.match(Tokens.IDENT)){ + + if (tokenStream.match(Tokens.IDENT)) { identifier = tokenStream.token().value; //The value 'auto' may not be used as a page name and MUST be treated as a syntax error. - if (identifier.toLowerCase() === "auto"){ + if (identifier.toLowerCase() === "auto") { this._unexpectedToken(tokenStream.token()); } - } - + } + //see if there's a colon upcoming - if (tokenStream.peek() == Tokens.COLON){ + if (tokenStream.peek() === Tokens.COLON) { pseudoPage = this._pseudo_page(); } - + this._readWhitespace(); - + this.fire({ type: "startpage", id: identifier, pseudo: pseudoPage, line: line, col: col - }); + }); + + this._readDeclarations(true, true); - this._readDeclarations(true, true); - this.fire({ type: "endpage", id: identifier, pseudo: pseudoPage, line: line, col: col - }); - + }); + }, - + //CSS3 Paged Media - _margin: function(){ + _margin: function() { /* * margin : * margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* @@ -1846,17 +1502,17 @@ Parser.prototype = function(){ col, marginSym = this._margin_sym(); - if (marginSym){ + if (marginSym) { line = tokenStream.token().startLine; col = tokenStream.token().startCol; - + this.fire({ type: "startpagemargin", margin: marginSym, line: line, col: col - }); - + }); + this._readDeclarations(true); this.fire({ @@ -1864,7 +1520,7 @@ Parser.prototype = function(){ margin: marginSym, line: line, col: col - }); + }); return true; } else { return false; @@ -1872,18 +1528,18 @@ Parser.prototype = function(){ }, //CSS3 Paged Media - _margin_sym: function(){ - + _margin_sym: function() { + /* * margin_sym : - * TOPLEFTCORNER_SYM | - * TOPLEFT_SYM | - * TOPCENTER_SYM | - * TOPRIGHT_SYM | + * TOPLEFTCORNER_SYM | + * TOPLEFT_SYM | + * TOPCENTER_SYM | + * TOPRIGHT_SYM | * TOPRIGHTCORNER_SYM | - * BOTTOMLEFTCORNER_SYM | - * BOTTOMLEFT_SYM | - * BOTTOMCENTER_SYM | + * BOTTOMLEFTCORNER_SYM | + * BOTTOMLEFT_SYM | + * BOTTOMCENTER_SYM | * BOTTOMRIGHT_SYM | * BOTTOMRIGHTCORNER_SYM | * LEFTTOP_SYM | @@ -1891,145 +1547,275 @@ Parser.prototype = function(){ * LEFTBOTTOM_SYM | * RIGHTTOP_SYM | * RIGHTMIDDLE_SYM | - * RIGHTBOTTOM_SYM + * RIGHTBOTTOM_SYM * ; */ - + var tokenStream = this._tokenStream; - - if(tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM, + + if (tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM, Tokens.TOPCENTER_SYM, Tokens.TOPRIGHT_SYM, Tokens.TOPRIGHTCORNER_SYM, - Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM, + Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM, Tokens.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_SYM, - Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM, + Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM, Tokens.LEFTMIDDLE_SYM, Tokens.LEFTBOTTOM_SYM, Tokens.RIGHTTOP_SYM, - Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM])) - { - return SyntaxUnit.fromToken(tokenStream.token()); + Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM])) { + return SyntaxUnit.fromToken(tokenStream.token()); } else { return null; } - + }, - - _pseudo_page: function(){ + + _pseudo_page: function() { /* * pseudo_page * : ':' IDENT - * ; + * ; */ - + var tokenStream = this._tokenStream; - + tokenStream.mustMatch(Tokens.COLON); tokenStream.mustMatch(Tokens.IDENT); - + //TODO: CSS3 Paged Media says only "left", "center", and "right" are allowed - + return tokenStream.token().value; }, - - _font_face: function(){ + + _font_face: function() { /* * font_face - * : FONT_FACE_SYM S* + * : FONT_FACE_SYM S* * '{' S* declaration [ ';' S* declaration ]* '}' S* * ; - */ + */ var tokenStream = this._tokenStream, line, col; - + //look for @page tokenStream.mustMatch(Tokens.FONT_FACE_SYM); line = tokenStream.token().startLine; col = tokenStream.token().startCol; - + this._readWhitespace(); this.fire({ type: "startfontface", line: line, col: col - }); - + }); + this._readDeclarations(true); - + this.fire({ type: "endfontface", line: line, col: col - }); + }); + }, + + _viewport: function() { + /* + * viewport + * : VIEWPORT_SYM S* + * '{' S* declaration? [ ';' S* declaration? ]* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col; + + tokenStream.mustMatch(Tokens.VIEWPORT_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this._readWhitespace(); + + this.fire({ + type: "startviewport", + line: line, + col: col + }); + + this._readDeclarations(true); + + this.fire({ + type: "endviewport", + line: line, + col: col + }); + + }, + + _document: function() { + /* + * document + * : DOCUMENT_SYM S* + * _document_function [ ',' S* _document_function ]* S* + * '{' S* ruleset* '}' + * ; + */ + + var tokenStream = this._tokenStream, + token, + functions = [], + prefix = ""; + + tokenStream.mustMatch(Tokens.DOCUMENT_SYM); + token = tokenStream.token(); + if (/^@\-([^\-]+)\-/.test(token.value)) { + prefix = RegExp.$1; + } + + this._readWhitespace(); + functions.push(this._document_function()); + + while (tokenStream.match(Tokens.COMMA)) { + this._readWhitespace(); + functions.push(this._document_function()); + } + + tokenStream.mustMatch(Tokens.LBRACE); + this._readWhitespace(); + + this.fire({ + type: "startdocument", + functions: functions, + prefix: prefix, + line: token.startLine, + col: token.startCol + }); + + var ok = true; + while (ok) { + switch (tokenStream.peek()) { + case Tokens.PAGE_SYM: + this._page(); + break; + case Tokens.FONT_FACE_SYM: + this._font_face(); + break; + case Tokens.VIEWPORT_SYM: + this._viewport(); + break; + case Tokens.MEDIA_SYM: + this._media(); + break; + case Tokens.KEYFRAMES_SYM: + this._keyframes(); + break; + case Tokens.DOCUMENT_SYM: + this._document(); + break; + default: + ok = Boolean(this._ruleset()); + } + } + + tokenStream.mustMatch(Tokens.RBRACE); + token = tokenStream.token(); + this._readWhitespace(); + + this.fire({ + type: "enddocument", + functions: functions, + prefix: prefix, + line: token.startLine, + col: token.startCol + }); + }, + + _document_function: function() { + /* + * document_function + * : function | URI S* + * ; + */ + + var tokenStream = this._tokenStream, + value; + + if (tokenStream.match(Tokens.URI)) { + value = tokenStream.token().value; + this._readWhitespace(); + } else { + value = this._function(); + } + + return value; }, - _operator: function(inFunction){ - + _operator: function(inFunction) { + /* * operator (outside function) * : '/' S* | ',' S* | /( empty )/ * operator (inside function) * : '/' S* | '+' S* | '*' S* | '-' S* /( empty )/ * ; - */ - + */ + var tokenStream = this._tokenStream, token = null; - + if (tokenStream.match([Tokens.SLASH, Tokens.COMMA]) || - (inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))){ + (inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))) { token = tokenStream.token(); this._readWhitespace(); - } + } return token ? PropertyValuePart.fromToken(token) : null; - + }, - - _combinator: function(){ - + + _combinator: function() { + /* * combinator * : PLUS S* | GREATER S* | TILDE S* | S+ * ; - */ - + */ + var tokenStream = this._tokenStream, value = null, token; - - if(tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])){ + + if (tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])) { token = tokenStream.token(); value = new Combinator(token.value, token.startLine, token.startCol); this._readWhitespace(); } - + return value; }, - - _unary_operator: function(){ - + + _unary_operator: function() { + /* * unary_operator * : '-' | '+' * ; */ - + var tokenStream = this._tokenStream; - - if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])){ + + if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])) { return tokenStream.token().value; } else { return null; - } + } }, - - _property: function(){ - + + _property: function() { + /* * property * : IDENT S* - * ; + * ; */ - + var tokenStream = this._tokenStream, value = null, hack = null, @@ -2037,42 +1823,42 @@ Parser.prototype = function(){ token, line, col; - + //check for star hack - throws error if not allowed - if (tokenStream.peek() == Tokens.STAR && this.options.starHack){ + if (tokenStream.peek() === Tokens.STAR && this.options.starHack) { tokenStream.get(); token = tokenStream.token(); hack = token.value; line = token.startLine; col = token.startCol; } - - if(tokenStream.match(Tokens.IDENT)){ + + if (tokenStream.match(Tokens.IDENT)) { token = tokenStream.token(); tokenValue = token.value; - + //check for underscore hack - no error if not allowed because it's valid CSS syntax - if (tokenValue.charAt(0) == "_" && this.options.underscoreHack){ + if (tokenValue.charAt(0) === "_" && this.options.underscoreHack) { hack = "_"; tokenValue = tokenValue.substring(1); } - + value = new PropertyName(tokenValue, hack, (line||token.startLine), (col||token.startCol)); this._readWhitespace(); } - + return value; }, - + //Augmented with CSS3 Selectors - _ruleset: function(){ + _ruleset: function() { /* * ruleset * : selectors_group * '{' S* declaration? [ ';' S* declaration? ]* '}' S* - * ; - */ - + * ; + */ + var tokenStream = this._tokenStream, tt, selectors; @@ -2084,9 +1870,9 @@ Parser.prototype = function(){ */ try { selectors = this._selectors_group(); - } catch (ex){ - if (ex instanceof SyntaxError && !this.options.strict){ - + } catch (ex) { + if (ex instanceof SyntaxError && !this.options.strict) { + //fire error event this.fire({ type: "error", @@ -2094,71 +1880,71 @@ Parser.prototype = function(){ message: ex.message, line: ex.line, col: ex.col - }); - + }); + //skip over everything until closing brace tt = tokenStream.advance([Tokens.RBRACE]); - if (tt == Tokens.RBRACE){ + if (tt === Tokens.RBRACE) { //if there's a right brace, the rule is finished so don't do anything } else { //otherwise, rethrow the error because it wasn't handled properly throw ex; - } - + } + } else { //not a syntax error, rethrow it throw ex; - } - + } + //trigger parser to continue return true; } - + //if it got here, all selectors parsed - if (selectors){ - + if (selectors) { + this.fire({ type: "startrule", selectors: selectors, line: selectors[0].line, col: selectors[0].col - }); - - this._readDeclarations(true); - + }); + + this._readDeclarations(true); + this.fire({ type: "endrule", selectors: selectors, line: selectors[0].line, col: selectors[0].col - }); - + }); + } - + return selectors; - + }, //CSS3 Selectors - _selectors_group: function(){ - - /* + _selectors_group: function() { + + /* * selectors_group * : selector [ COMMA S* selector ]* * ; - */ + */ var tokenStream = this._tokenStream, selectors = [], selector; - + selector = this._selector(); - if (selector !== null){ - + if (selector !== null) { + selectors.push(selector); - while(tokenStream.match(Tokens.COMMA)){ + while (tokenStream.match(Tokens.COMMA)) { this._readWhitespace(); selector = this._selector(); - if (selector !== null){ + if (selector !== null) { selectors.push(selector); } else { this._unexpectedToken(tokenStream.LT(1)); @@ -2168,85 +1954,85 @@ Parser.prototype = function(){ return selectors.length ? selectors : null; }, - + //CSS3 Selectors - _selector: function(){ + _selector: function() { /* * selector * : simple_selector_sequence [ combinator simple_selector_sequence ]* - * ; + * ; */ - + var tokenStream = this._tokenStream, selector = [], nextSelector = null, combinator = null, ws = null; - + //if there's no simple selector, then there's no selector nextSelector = this._simple_selector_sequence(); - if (nextSelector === null){ + if (nextSelector === null) { return null; } - + selector.push(nextSelector); - + do { - + //look for a combinator combinator = this._combinator(); - - if (combinator !== null){ + + if (combinator !== null) { selector.push(combinator); nextSelector = this._simple_selector_sequence(); - + //there must be a next selector - if (nextSelector === null){ + if (nextSelector === null) { this._unexpectedToken(tokenStream.LT(1)); } else { - + //nextSelector is an instance of SelectorPart selector.push(nextSelector); } } else { - + //if there's not whitespace, we're done - if (this._readWhitespace()){ - + if (this._readWhitespace()) { + //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){ + if (nextSelector === null) { + if (combinator !== null) { this._unexpectedToken(tokenStream.LT(1)); } } else { - - if (combinator !== null){ + + if (combinator !== null) { selector.push(combinator); } else { selector.push(ws); } - + selector.push(nextSelector); - } + } } else { break; - } - + } + } - } while(true); - + } while (true); + return new Selector(selector, selector[0].line, selector[0].col); }, - + //CSS3 Selectors - _simple_selector_sequence: function(){ + _simple_selector_sequence: function() { /* * simple_selector_sequence * : [ type_selector | universal ] @@ -2254,20 +2040,20 @@ Parser.prototype = function(){ * | [ HASH | class | attrib | pseudo | negation ]+ * ; */ - + var tokenStream = this._tokenStream, - + //parts of a simple selector elementName = null, modifiers = [], - + //complete selector text selectorText= "", //the different parts after the element name to search for components = [ //HASH - function(){ + function() { return tokenStream.match(Tokens.HASH) ? new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : null; @@ -2280,40 +2066,39 @@ Parser.prototype = function(){ i = 0, len = components.length, component = null, - found = false, line, col; - - + + //get starting line and column for the selector line = tokenStream.LT(1).startLine; col = tokenStream.LT(1).startCol; - + elementName = this._type_selector(); - if (!elementName){ + if (!elementName) { elementName = this._universal(); } - - if (elementName !== null){ + + if (elementName !== null) { selectorText += elementName; - } - - while(true){ + } + + while (true) { //whitespace means we're done - if (tokenStream.peek() === Tokens.S){ + if (tokenStream.peek() === Tokens.S) { break; } - + //check for each component - while(i < len && component === null){ + while (i < len && component === null) { component = components[i++].call(this); } - - if (component === null){ - + + if (component === null) { + //we don't have a selector - if (selectorText === ""){ + if (selectorText === "") { return null; } else { break; @@ -2321,145 +2106,145 @@ Parser.prototype = function(){ } else { i = 0; modifiers.push(component); - selectorText += component.toString(); + selectorText += component.toString(); component = null; } } - + return selectorText !== "" ? new SelectorPart(elementName, modifiers, selectorText, line, col) : null; - }, - + }, + //CSS3 Selectors - _type_selector: function(){ + _type_selector: function() { /* * type_selector * : [ namespace_prefix ]? element_name * ; */ - + var tokenStream = this._tokenStream, ns = this._namespace_prefix(), elementName = this._element_name(); - - if (!elementName){ + + if (!elementName) { /* * 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. */ - if (ns){ + if (ns) { tokenStream.unget(); - if (ns.length > 1){ + if (ns.length > 1) { tokenStream.unget(); } } - + return null; - } else { - if (ns){ + } else { + if (ns) { elementName.text = ns + elementName.text; elementName.col -= ns.length; } return elementName; } }, - + //CSS3 Selectors - _class: function(){ + _class: function() { /* * class * : '.' IDENT * ; - */ - + */ + var tokenStream = this._tokenStream, token; - - if (tokenStream.match(Tokens.DOT)){ - tokenStream.mustMatch(Tokens.IDENT); + + if (tokenStream.match(Tokens.DOT)) { + tokenStream.mustMatch(Tokens.IDENT); token = tokenStream.token(); - return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1); + return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1); } else { return null; } - + }, - + //CSS3 Selectors - _element_name: function(){ + _element_name: function() { /* * element_name * : IDENT * ; - */ - + */ + var tokenStream = this._tokenStream, token; - - if (tokenStream.match(Tokens.IDENT)){ + + if (tokenStream.match(Tokens.IDENT)) { token = tokenStream.token(); - return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol); - + return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol); + } else { return null; } }, - + //CSS3 Selectors - _namespace_prefix: function(){ - /* + _namespace_prefix: function() { + /* * namespace_prefix * : [ IDENT | '*' ]? '|' * ; */ var tokenStream = this._tokenStream, value = ""; - + //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])){ + 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); value += "|"; - + } - - return value.length ? value : null; + + return value.length ? value : null; }, - + //CSS3 Selectors - _universal: function(){ + _universal: function() { /* * universal * : [ namespace_prefix ]? '*' - * ; + * ; */ var tokenStream = this._tokenStream, value = "", ns; - + ns = this._namespace_prefix(); - if(ns){ + if (ns) { value += ns; } - - if(tokenStream.match(Tokens.STAR)){ + + if (tokenStream.match(Tokens.STAR)) { value += "*"; } - + return value.length ? value : null; - - }, - + + }, + //CSS3 Selectors - _attrib: function(){ + _attrib: function() { /* * attrib * : '[' S* [ namespace_prefix ]? IDENT S* @@ -2470,136 +2255,140 @@ Parser.prototype = function(){ * INCLUDES | * DASHMATCH ] S* [ IDENT | STRING ] S* * ]? ']' - * ; + * ; */ - + var tokenStream = this._tokenStream, value = null, ns, token; - - if (tokenStream.match(Tokens.LBRACKET)){ + + if (tokenStream.match(Tokens.LBRACKET)) { token = tokenStream.token(); value = token.value; value += this._readWhitespace(); - + ns = this._namespace_prefix(); - - if (ns){ + + if (ns) { value += ns; } - + tokenStream.mustMatch(Tokens.IDENT); - value += tokenStream.token().value; + 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; + + 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 += tokenStream.token().value; value += this._readWhitespace(); } - + tokenStream.mustMatch(Tokens.RBRACKET); - + return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol); } else { return null; } }, - + //CSS3 Selectors - _pseudo: function(){ - + _pseudo: function() { + /* * pseudo * : ':' ':'? [ IDENT | functional_pseudo ] - * ; - */ - + * ; + */ + var tokenStream = this._tokenStream, pseudo = null, colons = ":", line, col; - - if (tokenStream.match(Tokens.COLON)){ - - if (tokenStream.match(Tokens.COLON)){ + + if (tokenStream.match(Tokens.COLON)) { + + if (tokenStream.match(Tokens.COLON)) { colons += ":"; } - - if (tokenStream.match(Tokens.IDENT)){ + + 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){ + } else if (tokenStream.peek() === Tokens.FUNCTION) { line = tokenStream.LT(1).startLine; col = tokenStream.LT(1).startCol - colons.length; pseudo = this._functional_pseudo(); } - - if (pseudo){ + + if (pseudo) { pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col); + } else { + 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); } } - + return pseudo; }, - + //CSS3 Selectors - _functional_pseudo: function(){ + _functional_pseudo: function() { /* * functional_pseudo * : FUNCTION S* expression ')' * ; - */ - + */ + var tokenStream = this._tokenStream, value = null; - - if(tokenStream.match(Tokens.FUNCTION)){ + + if (tokenStream.match(Tokens.FUNCTION)) { value = tokenStream.token().value; value += this._readWhitespace(); value += this._expression(); tokenStream.mustMatch(Tokens.RPAREN); value += ")"; } - + return value; }, - + //CSS3 Selectors - _expression: function(){ + _expression: function() { /* * expression * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ * ; */ - + var tokenStream = this._tokenStream, value = ""; - - while(tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION, + + 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])){ - + Tokens.RESOLUTION, Tokens.SLASH])) { + value += tokenStream.token().value; - value += this._readWhitespace(); + value += this._readWhitespace(); } - + return value.length ? value : null; - + }, //CSS3 Selectors - _negation: function(){ - /* + _negation: function() { + /* * negation * : NOT S* negation_arg S* ')' * ; @@ -2611,8 +2400,8 @@ Parser.prototype = function(){ value = "", arg, subpart = null; - - if (tokenStream.match(Tokens.NOT)){ + + if (tokenStream.match(Tokens.NOT)) { value = tokenStream.token().value; line = tokenStream.token().startLine; col = tokenStream.token().startCol; @@ -2622,117 +2411,115 @@ Parser.prototype = function(){ value += this._readWhitespace(); tokenStream.match(Tokens.RPAREN); value += tokenStream.token().value; - + subpart = new SelectorSubPart(value, "not", line, col); subpart.args.push(arg); } - + return subpart; }, - + //CSS3 Selectors - _negation_arg: function(){ + _negation_arg: function() { /* * negation_arg * : type_selector | universal | HASH | class | attrib | pseudo - * ; - */ - + * ; + */ + var tokenStream = this._tokenStream, args = [ this._type_selector, this._universal, - function(){ + function() { return tokenStream.match(Tokens.HASH) ? new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : - null; + null; }, this._class, this._attrib, - this._pseudo + this._pseudo ], arg = null, i = 0, len = args.length, - elementName, line, col, part; - + line = tokenStream.LT(1).startLine; col = tokenStream.LT(1).startCol; - - while(i < len && arg === null){ - + + while (i < len && arg === null) { + arg = args[i].call(this); i++; } - + //must be a negation arg - if (arg === null){ + if (arg === null) { this._unexpectedToken(tokenStream.LT(1)); } - + //it's an element name - if (arg.type == "elementName"){ + if (arg.type === "elementName") { part = new SelectorPart(arg, [], arg.toString(), line, col); } else { part = new SelectorPart(null, [arg], arg.toString(), line, col); } - - return part; + + return part; }, - - _declaration: function(){ - + + _declaration: function() { + /* * declaration * : property ':' S* expr prio? * | /( empty )/ - * ; - */ - + * ; + */ + var tokenStream = this._tokenStream, property = null, expr = null, prio = null, - error = null, invalid = null, propertyName= ""; - + property = this._property(); - if (property !== null){ + if (property !== null) { tokenStream.mustMatch(Tokens.COLON); this._readWhitespace(); - + expr = this._expr(); - + //if there's no parts for the value, it's an error - if (!expr || expr.length === 0){ + if (!expr || expr.length === 0) { this._unexpectedToken(tokenStream.LT(1)); } - + prio = this._prio(); - + /* * 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 == "_") { - + if (this.options.starHack && property.hack === "*" || + this.options.underscoreHack && property.hack === "_") { + propertyName = property.text; } - + try { this._validateProperty(propertyName, expr); } catch (ex) { invalid = ex; } - + this.fire({ type: "property", property: property, @@ -2741,78 +2528,77 @@ Parser.prototype = function(){ line: property.line, col: property.col, invalid: invalid - }); - + }); + return true; } else { return false; } }, - - _prio: function(){ + + _prio: function() { /* * prio * : IMPORTANT_SYM S* - * ; + * ; */ - + var tokenStream = this._tokenStream, result = tokenStream.match(Tokens.IMPORTANT_SYM); - + this._readWhitespace(); return result; }, - - _expr: function(inFunction){ + + _expr: function(inFunction) { /* * expr * : term [ operator term ]* * ; */ - - var tokenStream = this._tokenStream, - values = [], - //valueParts = [], + + var values = [], + //valueParts = [], value = null, operator = null; - - value = this._term(); - if (value !== null){ - + + value = this._term(inFunction); + if (value !== null) { + values.push(value); - + do { operator = this._operator(inFunction); //if there's an operator, keep building up the value parts - if (operator){ + if (operator) { values.push(operator); } /*else { //if there's not an operator, you have a full value - values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); - valueParts = []; - }*/ - - value = this._term(); - - if (value === null){ + values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); + valueParts = []; + }*/ + + value = this._term(inFunction); + + if (value === null) { break; } else { values.push(value); } - } while(true); + } while (true); } - - //cleanup - /*if (valueParts.length){ + + //cleanup + /*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(){ - + + _term: function(inFunction) { + /* * term * : unary_operator? @@ -2820,199 +2606,217 @@ Parser.prototype = function(){ * TIME S* | FREQ S* | function | ie_function ] * | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor * ; - */ - + */ + var tokenStream = this._tokenStream, unary = null, value = null, + endChar = null, + part = null, token, line, col; - + //returns the operator or null unary = this._unary_operator(); - if (unary !== null){ + if (unary !== null) { line = tokenStream.token().startLine; col = tokenStream.token().startCol; - } - + } + //exception for IE filters - if (tokenStream.peek() == Tokens.IE_FUNCTION && this.options.ieFilters){ - + if (tokenStream.peek() === Tokens.IE_FUNCTION && this.options.ieFilters) { + value = this._ie_function(); - if (unary === null){ + if (unary === null) { + 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(); + endChar = token.endChar; + value = token.value + this._expr(inFunction).text; + if (unary === null) { line = tokenStream.token().startLine; col = tokenStream.token().startCol; } - + tokenStream.mustMatch(Tokens.type(endChar)); + value += endChar; + this._readWhitespace(); + //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])){ - + Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])) { + value = tokenStream.token().value; - if (unary === null){ + if (unary === null) { line = tokenStream.token().startLine; col = tokenStream.token().startCol; + // Correct potentially-inaccurate IDENT parsing in + // PropertyValuePart constructor. + part = PropertyValuePart.fromToken(tokenStream.token()); } this._readWhitespace(); } else { - + //see if it's a color token = this._hexcolor(); - if (token === null){ - + if (token === null) { + //if there's no unary, get the start of the next token for line/col info - if (unary === null){ + if (unary === null) { line = tokenStream.LT(1).startLine; col = tokenStream.LT(1).startCol; - } - + } + //has to be a function - if (value === null){ - + if (value === null) { + /* * 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){ + if (tokenStream.LA(3) === Tokens.EQUALS && this.options.ieFilters) { value = this._ie_function(); } else { value = this._function(); } } - /*if (value === null){ + /*if (value === null) { return null; //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + "."); }*/ - + } else { value = token.value; - if (unary === null){ + if (unary === null) { line = token.startLine; col = token.startCol; - } + } } - - } - - return value !== null ? + + } + + return part !== null ? part : value !== null ? new PropertyValuePart(unary !== null ? unary + value : value, line, col) : null; - + }, - - _function: function(){ - + + _function: function() { + /* * function * : FUNCTION S* expr ')' S* * ; */ - + var tokenStream = this._tokenStream, functionText = null, expr = null, lt; - - if (tokenStream.match(Tokens.FUNCTION)){ + + if (tokenStream.match(Tokens.FUNCTION)) { functionText = tokenStream.token().value; this._readWhitespace(); expr = this._expr(true); functionText += expr; - + //START: Horrible hack in case it's an IE filter - if (this.options.ieFilters && tokenStream.peek() == Tokens.EQUALS){ + if (this.options.ieFilters && tokenStream.peek() === Tokens.EQUALS) { do { - - if (this._readWhitespace()){ + + if (this._readWhitespace()) { functionText += tokenStream.token().value; } - + //might be second time in the loop - if (tokenStream.LA(0) == Tokens.COMMA){ + 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(); lt = tokenStream.peek(); - while(lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN){ + while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) { tokenStream.get(); functionText += tokenStream.token().value; lt = tokenStream.peek(); } - } while(tokenStream.match([Tokens.COMMA, Tokens.S])); + } while (tokenStream.match([Tokens.COMMA, Tokens.S])); } //END: Horrible Hack - - tokenStream.match(Tokens.RPAREN); + + tokenStream.match(Tokens.RPAREN); functionText += ")"; this._readWhitespace(); - } - + } + return functionText; - }, - - _ie_function: function(){ - + }, + + _ie_function: function() { + /* (My own extension) * ie_function * : IE_FUNCTION S* IDENT '=' term [S* ','? IDENT '=' term]+ ')' S* * ; */ - + var tokenStream = this._tokenStream, functionText = null, - expr = null, lt; - + //IE function can begin like a regular function, too - if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])){ + if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])) { functionText = tokenStream.token().value; - + do { - - if (this._readWhitespace()){ + + if (this._readWhitespace()) { functionText += tokenStream.token().value; } - + //might be second time in the loop - if (tokenStream.LA(0) == Tokens.COMMA){ + 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(); lt = tokenStream.peek(); - while(lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN){ + while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) { tokenStream.get(); functionText += tokenStream.token().value; lt = tokenStream.peek(); } - } while(tokenStream.match([Tokens.COMMA, Tokens.S])); - - tokenStream.match(Tokens.RPAREN); + } while (tokenStream.match([Tokens.COMMA, Tokens.S])); + + tokenStream.match(Tokens.RPAREN); functionText += ")"; this._readWhitespace(); - } - + } + return functionText; - }, - - _hexcolor: function(){ + }, + + _hexcolor: function() { /* * There is a constraint on the color that it must * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F]) @@ -3022,32 +2826,32 @@ Parser.prototype = function(){ * : HASH S* * ; */ - + var tokenStream = this._tokenStream, token = null, color; - - if(tokenStream.match(Tokens.HASH)){ - + + if (tokenStream.match(Tokens.HASH)) { + //need to do some validation here - + token = tokenStream.token(); color = token.value; - if (!/#[a-f0-9]{3,6}/i.test(color)){ + 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); } this._readWhitespace(); } - + return token; }, - + //----------------------------------------------------------------- // Animations methods //----------------------------------------------------------------- - - _keyframes: function(){ - + + _keyframes: function() { + /* * keyframes: * : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' { @@ -3057,114 +2861,110 @@ Parser.prototype = function(){ token, tt, name, - prefix = ""; - + prefix = ""; + tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); token = tokenStream.token(); if (/^@\-([^\-]+)\-/.test(token.value)) { prefix = RegExp.$1; } - + this._readWhitespace(); name = this._keyframe_name(); - + this._readWhitespace(); tokenStream.mustMatch(Tokens.LBRACE); - + this.fire({ type: "startkeyframes", name: name, prefix: prefix, line: token.startLine, col: token.startCol - }); - + }); + this._readWhitespace(); tt = tokenStream.peek(); - + //check for key - while(tt == Tokens.IDENT || tt == Tokens.PERCENTAGE) { + while (tt === Tokens.IDENT || tt === Tokens.PERCENTAGE) { this._keyframe_rule(); this._readWhitespace(); tt = tokenStream.peek(); - } - + } + this.fire({ type: "endkeyframes", name: name, prefix: prefix, line: token.startLine, col: token.startCol - }); - + }); + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.RBRACE); this._readWhitespace(); - tokenStream.mustMatch(Tokens.RBRACE); - + }, - - _keyframe_name: function(){ - + + _keyframe_name: function() { + /* * keyframe_name: * : IDENT * | STRING * ; */ - var tokenStream = this._tokenStream, - token; + var tokenStream = this._tokenStream; tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); - return SyntaxUnit.fromToken(tokenStream.token()); + return SyntaxUnit.fromToken(tokenStream.token()); }, - - _keyframe_rule: function(){ - + + _keyframe_rule: function() { + /* * keyframe_rule: - * : key_list S* + * : key_list S* * '{' S* declaration [ ';' S* declaration ]* '}' S* * ; */ - var tokenStream = this._tokenStream, - token, - keyList = this._key_list(); - + var keyList = this._key_list(); + this.fire({ type: "startkeyframerule", keys: keyList, line: keyList[0].line, col: keyList[0].col - }); - - this._readDeclarations(true); - + }); + + this._readDeclarations(true); + this.fire({ type: "endkeyframerule", keys: keyList, line: keyList[0].line, col: keyList[0].col - }); - + }); + }, - - _key_list: function(){ - + + _key_list: function() { + /* * key_list: * : key [ S* ',' S* key]* * ; */ var tokenStream = this._tokenStream, - token, - key, keyList = []; - + //must be least one key keyList.push(this._key()); - + this._readWhitespace(); - - while(tokenStream.match(Tokens.COMMA)){ + + while (tokenStream.match(Tokens.COMMA)) { this._readWhitespace(); keyList.push(this._key()); this._readWhitespace(); @@ -3172,8 +2972,8 @@ Parser.prototype = function(){ return keyList; }, - - _key: function(){ + + _key: function() { /* * There is a restriction that IDENT can be only "from" or "to". * @@ -3182,30 +2982,30 @@ Parser.prototype = function(){ * | IDENT * ; */ - + var tokenStream = this._tokenStream, token; - - if (tokenStream.match(Tokens.PERCENTAGE)){ + + 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)){ + } else if (tokenStream.match(Tokens.IDENT)) { + token = tokenStream.token(); + + if (/from|to/i.test(token.value)) { return SyntaxUnit.fromToken(token); } - + tokenStream.unget(); } - + //if it gets here, there wasn't a valid token, so time to explode this._unexpectedToken(tokenStream.LT(1)); }, - + //----------------------------------------------------------------- // Helper methods //----------------------------------------------------------------- - + /** * Not part of CSS grammar, but useful for skipping over * combination of white space and HTML-style comments. @@ -3213,8 +3013,8 @@ Parser.prototype = function(){ * @method _skipCruft * @private */ - _skipCruft: function(){ - while(this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])){ + _skipCruft: function() { + while (this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])) { //noop } }, @@ -3231,54 +3031,54 @@ Parser.prototype = function(){ * @method _readDeclarations * @private */ - _readDeclarations: function(checkStart, readMargins){ + _readDeclarations: function(checkStart, readMargins) { /* * Reads the pattern * S* '{' S* declaration [ ';' S* declaration ]* '}' S* * or * S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* * Note that this is how it is described in CSS3 Paged Media, but is actually incorrect. - * A semicolon is only necessary following a delcaration is there's another declaration - * or margin afterwards. + * A semicolon is only necessary following a declaration if there's another declaration + * or margin afterwards. */ var tokenStream = this._tokenStream, tt; - + this._readWhitespace(); - - if (checkStart){ - tokenStream.mustMatch(Tokens.LBRACE); + + if (checkStart) { + tokenStream.mustMatch(Tokens.LBRACE); } - + this._readWhitespace(); try { - - while(true){ - - if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())){ + + while (true) { + + if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())) { //noop - } else if (this._declaration()){ - if (!tokenStream.match(Tokens.SEMICOLON)){ + } else if (this._declaration()) { + if (!tokenStream.match(Tokens.SEMICOLON)) { break; } } else { break; } - + //if ((!this._margin() && !this._declaration()) || !tokenStream.match(Tokens.SEMICOLON)){ // break; //} this._readWhitespace(); } - + tokenStream.mustMatch(Tokens.RBRACE); this._readWhitespace(); - + } catch (ex) { - if (ex instanceof SyntaxError && !this.options.strict){ - + if (ex instanceof SyntaxError && !this.options.strict) { + //fire error event this.fire({ type: "error", @@ -3286,27 +3086,27 @@ Parser.prototype = function(){ message: ex.message, line: ex.line, col: ex.col - }); - + }); + //see if there's another declaration tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE]); - if (tt == Tokens.SEMICOLON){ + if (tt === Tokens.SEMICOLON) { //if there's a semicolon, then there might be another declaration - this._readDeclarations(false, readMargins); - } else if (tt != Tokens.RBRACE){ + this._readDeclarations(false, readMargins); + } else if (tt !== Tokens.RBRACE) { //if there's a right brace, the rule is finished so don't do anything //otherwise, rethrow the error because it wasn't handled properly throw ex; - } - + } + } else { //not a syntax error, rethrow it throw ex; } - } - - }, - + } + + }, + /** * In some cases, you can end up with two white space tokens in a * row. Instead of making a change in every function that looks for @@ -3316,18 +3116,18 @@ Parser.prototype = function(){ * @return {String} The white space if found, empty string if not. * @private */ - _readWhitespace: function(){ - + _readWhitespace: function() { + var tokenStream = this._tokenStream, ws = ""; - - while(tokenStream.match(Tokens.S)){ + + while (tokenStream.match(Tokens.S)) { ws += tokenStream.token().value; } - + return ws; }, - + /** * Throws an error when an unexpected token is found. @@ -3336,77 +3136,77 @@ Parser.prototype = function(){ * @return {void} * @private */ - _unexpectedToken: function(token){ + _unexpectedToken: function(token) { throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); }, - + /** * Helper method used for parsing subparts of a style sheet. * @return {void} * @method _verifyEnd * @private */ - _verifyEnd: function(){ - if (this._tokenStream.LA(1) != Tokens.EOF){ + _verifyEnd: function() { + if (this._tokenStream.LA(1) !== Tokens.EOF) { this._unexpectedToken(this._tokenStream.LT(1)); - } + } }, - + //----------------------------------------------------------------- // Validation methods //----------------------------------------------------------------- - _validateProperty: function(property, value){ + _validateProperty: function(property, value) { Validation.validate(property, value); }, - + //----------------------------------------------------------------- // Parsing methods //----------------------------------------------------------------- - - parse: function(input){ + + parse: function(input) { this._tokenStream = new TokenStream(input, Tokens); this._stylesheet(); }, - - parseStyleSheet: function(input){ + + parseStyleSheet: function(input) { //just passthrough return this.parse(input); }, - - parseMediaQuery: function(input){ + + parseMediaQuery: function(input) { this._tokenStream = new TokenStream(input, Tokens); var result = this._media_query(); - + //if there's anything more, then it's an invalid selector this._verifyEnd(); - + //otherwise return result - return result; + return result; }, - + /** * Parses a property value (everything after the semicolon). * @return {parserlib.css.PropertyValue} The property value. * @throws parserlib.util.SyntaxError If an unexpected token is found. * @method parserPropertyValue - */ - parsePropertyValue: function(input){ - + */ + parsePropertyValue: function(input) { + this._tokenStream = new TokenStream(input, Tokens); this._readWhitespace(); - + var result = this._expr(); - + //okay to have a trailing white space this._readWhitespace(); - + //if there's anything more, then it's an invalid selector this._verifyEnd(); - + //otherwise return result return result; }, - + /** * Parses a complete CSS rule, including selectors and * properties. @@ -3414,24 +3214,24 @@ Parser.prototype = function(){ * @return {Boolean} True if the parse completed successfully, false if not. * @method parseRule */ - parseRule: function(input){ + parseRule: function(input) { this._tokenStream = new TokenStream(input, Tokens); - + //skip any leading white space this._readWhitespace(); - + var result = this._ruleset(); - + //skip any trailing white space this._readWhitespace(); //if there's anything more, then it's an invalid selector this._verifyEnd(); - + //otherwise return result - return result; + return result; }, - + /** * Parses a single CSS selector (no comma) * @param {String} input The text to parse as a CSS selector. @@ -3439,46 +3239,46 @@ Parser.prototype = function(){ * @throws parserlib.util.SyntaxError If an unexpected token is found. * @method parseSelector */ - parseSelector: function(input){ - + parseSelector: function(input) { + this._tokenStream = new TokenStream(input, Tokens); - + //skip any leading white space this._readWhitespace(); - + var result = this._selector(); - + //skip any trailing white space this._readWhitespace(); //if there's anything more, then it's an invalid selector this._verifyEnd(); - + //otherwise return result return result; }, /** - * Parses an HTML style attribute: a set of CSS declarations + * Parses an HTML style attribute: a set of CSS declarations * separated by semicolons. * @param {String} input The text to parse as a style attribute - * @return {void} + * @return {void} * @method parseStyleAttribute */ - parseStyleAttribute: function(input){ + parseStyleAttribute: function(input) { input += "}"; // for error recovery in _readDeclarations() this._tokenStream = new TokenStream(input, Tokens); this._readDeclarations(); } }; - + //copy over onto prototype - for (prop in additions){ - if (additions.hasOwnProperty(prop)){ + for (prop in additions) { + if (Object.prototype.hasOwnProperty.call(additions, prop)) { proto[prop] = additions[prop]; } - } - + } + return proto; }(); @@ -3489,93 +3289,84 @@ nth ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S* ; */ -/*global Validation, ValidationTypes, ValidationError*/ -var Properties = { + +},{"../util/EventTarget":23,"../util/SyntaxError":25,"../util/SyntaxUnit":26,"./Combinator":2,"./MediaFeature":4,"./MediaQuery":5,"./PropertyName":8,"./PropertyValue":9,"./PropertyValuePart":11,"./Selector":13,"./SelectorPart":14,"./SelectorSubPart":15,"./TokenStream":17,"./Tokens":18,"./Validation":19}],7:[function(require,module,exports){ +"use strict"; + +/* exported Properties */ + +var Properties = module.exports = { + __proto__: null, //A + "align-items" : "flex-start | flex-end | center | baseline | stretch", + "align-content" : "flex-start | flex-end | center | space-between | space-around | stretch", + "align-self" : "auto | flex-start | flex-end | center | baseline | stretch", + "all" : "initial | inherit | unset", + "-webkit-align-items" : "flex-start | flex-end | center | baseline | stretch", + "-webkit-align-content" : "flex-start | flex-end | center | space-between | space-around | stretch", + "-webkit-align-self" : "auto | flex-start | flex-end | center | baseline | stretch", "alignment-adjust" : "auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | | ", - "alignment-baseline" : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", + "alignment-baseline" : "auto | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", "animation" : 1, - "animation-delay" : { multi: "