9126 lines
		
	
	
		
			293 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			9126 lines
		
	
	
		
			293 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*!
 | |
| CSSLint
 | |
| Copyright (c) 2011 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
 | |
| 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:
 | |
| 
 | |
| 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
 | |
| 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.
 | |
| 
 | |
| */
 | |
| /* Build time: 14-May-2012 10:24:48 */
 | |
| 
 | |
| /*!
 | |
| 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:
 | |
| 
 | |
| 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
 | |
| 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.
 | |
| 
 | |
| */
 | |
| /* Version v0.1.7, Build time: 4-May-2012 03:57:04 */
 | |
| var parserlib = {};
 | |
| (function(){
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * A generic base to inherit from for any object
 | |
|  * that needs event handling.
 | |
|  * @class EventTarget
 | |
|  * @constructor
 | |
|  */
 | |
| function EventTarget(){
 | |
| 
 | |
|     /**
 | |
|      * The array of listeners for various events.
 | |
|      * @type Object
 | |
|      * @property _listeners
 | |
|      * @private
 | |
|      */
 | |
|     this._listeners = {};
 | |
| }
 | |
| 
 | |
| EventTarget.prototype = {
 | |
| 
 | |
|     //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] = [];
 | |
|         }
 | |
| 
 | |
|         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 };
 | |
|         }
 | |
|         if (typeof event.target != "undefined"){
 | |
|             event.target = this;
 | |
|         }
 | |
| 
 | |
|         if (typeof event.type == "undefined"){
 | |
|             throw new Error("Event object missing 'type' property.");
 | |
|         }
 | |
| 
 | |
|         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);
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
| 
 | |
|         }
 | |
|     }
 | |
| };
 | |
| /**
 | |
|  * Convenient way to read through strings.
 | |
|  * @namespace parserlib.util
 | |
|  * @class StringReader
 | |
|  * @constructor
 | |
|  * @param {String} text The text to read.
 | |
|  */
 | |
| function StringReader(text){
 | |
| 
 | |
|     /**
 | |
|      * The input text with line endings normalized.
 | |
|      * @property _input
 | |
|      * @type String
 | |
|      * @private
 | |
|      */
 | |
|     this._input = text.replace(/\n\r?/g, "\n");
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * The row for the character to be read next.
 | |
|      * @property _line
 | |
|      * @type int
 | |
|      * @private
 | |
|      */
 | |
|     this._line = 1;
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * 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;
 | |
| }
 | |
| 
 | |
| StringReader.prototype = {
 | |
| 
 | |
|     //restore constructor
 | |
|     constructor: StringReader,
 | |
| 
 | |
|     //-------------------------------------------------------------------------
 | |
|     // Position info
 | |
|     //-------------------------------------------------------------------------
 | |
| 
 | |
|     /**
 | |
|      * 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;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Returns the row of the character to be read next.
 | |
|      * @return {int} The row of the character to be read next.
 | |
|      * @method getLine
 | |
|      */
 | |
|     getLine: function(){
 | |
|         return this._line ;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Determines if you're at the end of the input.
 | |
|      * @return {Boolean} True if there's no more input, false otherwise.
 | |
|      * @method eof
 | |
|      */
 | |
|     eof: function(){
 | |
|         return (this._cursor == this._input.length);
 | |
|     },
 | |
| 
 | |
|     //-------------------------------------------------------------------------
 | |
|     // Basic reading
 | |
|     //-------------------------------------------------------------------------
 | |
| 
 | |
|     /**
 | |
|      * 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
 | |
|      */
 | |
|     peek: function(count){
 | |
|         var c = null;
 | |
|         count = (typeof count == "undefined" ? 1 : count);
 | |
| 
 | |
|         //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);
 | |
|         }
 | |
| 
 | |
|         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;
 | |
| 
 | |
|         //if we're not at the end of the input...
 | |
|         if (this._cursor < this._input.length){
 | |
| 
 | |
|             //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++;
 | |
|             }
 | |
| 
 | |
|             //get character and increment cursor and column
 | |
|             c = this._input.charAt(this._cursor++);
 | |
|         }
 | |
| 
 | |
|         return c;
 | |
|     },
 | |
| 
 | |
|     //-------------------------------------------------------------------------
 | |
|     // Misc
 | |
|     //-------------------------------------------------------------------------
 | |
| 
 | |
|     /**
 | |
|      * 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;
 | |
|         }
 | |
|     },
 | |
| 
 | |
|     //-------------------------------------------------------------------------
 | |
|     // Advanced reading
 | |
|     //-------------------------------------------------------------------------
 | |
| 
 | |
|     /**
 | |
|      * 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){
 | |
| 
 | |
|         var buffer = "",
 | |
|             c;
 | |
| 
 | |
|         /*
 | |
|          * 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 + ".");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return buffer;
 | |
| 
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * 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){
 | |
| 
 | |
|         var buffer = "",
 | |
|             c = this.read();
 | |
| 
 | |
|         while(c !== null && filter(c)){
 | |
|             buffer += c;
 | |
|             c = this.read();
 | |
|         }
 | |
| 
 | |
|         return buffer;
 | |
| 
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * 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){
 | |
| 
 | |
|         var source = this._input.substring(this._cursor),
 | |
|             value = null;
 | |
| 
 | |
|         //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);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return value;
 | |
|     },
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * 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 = "";
 | |
| 
 | |
|         while(count--){
 | |
|             buffer += this.read();
 | |
|         }
 | |
| 
 | |
|         return buffer;
 | |
|     }
 | |
| 
 | |
| };
 | |
| /**
 | |
|  * 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){
 | |
| 
 | |
|     /**
 | |
|      * The column at which the error occurred.
 | |
|      * @type int
 | |
|      * @property col
 | |
|      */
 | |
|     this.col = col;
 | |
| 
 | |
|     /**
 | |
|      * The line at which the error occurred.
 | |
|      * @type int
 | |
|      * @property line
 | |
|      */
 | |
|     this.line = line;
 | |
| 
 | |
|     /**
 | |
|      * The text representation of the unit.
 | |
|      * @type String
 | |
|      * @property text
 | |
|      */
 | |
|     this.message = message;
 | |
| 
 | |
| }
 | |
| 
 | |
| //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){
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * The column of text on which the unit resides.
 | |
|      * @type int
 | |
|      * @property col
 | |
|      */
 | |
|     this.col = col;
 | |
| 
 | |
|     /**
 | |
|      * The line of text on which the unit resides.
 | |
|      * @type int
 | |
|      * @property line
 | |
|      */
 | |
|     this.line = line;
 | |
| 
 | |
|     /**
 | |
|      * The text representation of the unit.
 | |
|      * @type String
 | |
|      * @property text
 | |
|      */
 | |
|     this.text = text;
 | |
| 
 | |
|     /**
 | |
|      * The type of syntax unit.
 | |
|      * @type int
 | |
|      * @property type
 | |
|      */
 | |
|     this.type = type;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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);
 | |
| };
 | |
| 
 | |
| SyntaxUnit.prototype = {
 | |
| 
 | |
|     //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;
 | |
|     }
 | |
| 
 | |
| };
 | |
| /*global StringReader, SyntaxError*/
 | |
| 
 | |
| /**
 | |
|  * 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){
 | |
| 
 | |
|     /**
 | |
|      * 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 = [];
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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){
 | |
| 
 | |
|     var nameMap     = [],
 | |
|         typeMap     = {},
 | |
|         tokenData     = tokens.concat([]),
 | |
|         i            = 0,
 | |
|         len            = tokenData.length+1;
 | |
| 
 | |
|     tokenData.UNKNOWN = -1;
 | |
|     tokenData.unshift({name:"EOF"});
 | |
| 
 | |
|     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;
 | |
| };
 | |
| 
 | |
| TokenStreamBase.prototype = {
 | |
| 
 | |
|     //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){
 | |
| 
 | |
|         var token;
 | |
| 
 | |
|         //always convert to an array, makes things easier
 | |
|         if (!(tokenTypes instanceof Array)){
 | |
|             tokenTypes = [tokenTypes];
 | |
|         }
 | |
| 
 | |
|         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();
 | |
| 
 | |
|         //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);
 | |
| 
 | |
|             //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.");
 | |
|         }
 | |
|     }
 | |
| 
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| parserlib.util = {
 | |
| StringReader: StringReader,
 | |
| SyntaxError : SyntaxError,
 | |
| SyntaxUnit  : SyntaxUnit,
 | |
| EventTarget : EventTarget,
 | |
| TokenStreamBase : TokenStreamBase
 | |
| };
 | |
| })();
 | |
| 
 | |
| 
 | |
| /*
 | |
| 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:
 | |
| 
 | |
| 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
 | |
| 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.
 | |
| 
 | |
| */
 | |
| /* Version v0.1.7, Build time: 4-May-2012 03:57:04 */
 | |
| (function(){
 | |
| var EventTarget = parserlib.util.EventTarget,
 | |
| TokenStreamBase = parserlib.util.TokenStreamBase,
 | |
| StringReader = parserlib.util.StringReader,
 | |
| SyntaxError = parserlib.util.SyntaxError,
 | |
| SyntaxUnit  = parserlib.util.SyntaxUnit;
 | |
| 
 | |
| 
 | |
| 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"
 | |
| };
 | |
| /*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";
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| Combinator.prototype = new SyntaxUnit();
 | |
| Combinator.prototype.constructor = Combinator;
 | |
| 
 | |
| 
 | |
| /*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);
 | |
| 
 | |
|     /**
 | |
|      * The name of the media feature
 | |
|      * @type String
 | |
|      * @property name
 | |
|      */
 | |
|     this.name = name;
 | |
| 
 | |
|     /**
 | |
|      * The value for the feature or null if there is none.
 | |
|      * @type SyntaxUnit
 | |
|      * @property value
 | |
|      */
 | |
|     this.value = value;
 | |
| }
 | |
| 
 | |
| MediaFeature.prototype = new SyntaxUnit();
 | |
| MediaFeature.prototype.constructor = MediaFeature;
 | |
| 
 | |
| 
 | |
| /*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 + " " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE);
 | |
| 
 | |
|     /**
 | |
|      * The media modifier ("not" or "only")
 | |
|      * @type String
 | |
|      * @property modifier
 | |
|      */
 | |
|     this.modifier = modifier;
 | |
| 
 | |
|     /**
 | |
|      * 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;
 | |
| 
 | |
| }
 | |
| 
 | |
| MediaQuery.prototype = new SyntaxUnit();
 | |
| MediaQuery.prototype.constructor = MediaQuery;
 | |
| 
 | |
| 
 | |
| /*global Tokens, TokenStream, SyntaxError, Properties, Validation, ValidationError, SyntaxUnit,
 | |
|     PropertyValue, PropertyValuePart, SelectorPart, SelectorSubPart, Selector,
 | |
|     PropertyName, Combinator, MediaFeature, MediaQuery, EventTarget */
 | |
| 
 | |
| /**
 | |
|  * 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){
 | |
| 
 | |
|     //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){
 | |
|                         this._page();
 | |
|                     } 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
 | |
|                  *   : S* [media_query [ ',' S* media_query ]* ]?
 | |
|                  *   ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     mediaList   = [];
 | |
| 
 | |
| 
 | |
|                 this._readWhitespace();
 | |
| 
 | |
|                 if (tokenStream.peek() == Tokens.IDENT || tokenStream.peek() == Tokens.LPAREN){
 | |
|                     mediaList.push(this._media_query());
 | |
|                 }
 | |
| 
 | |
|                 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
 | |
|                  *   : [ONLY | NOT]? S* media_type S* [ AND S* expression ]*
 | |
|                  *   | expression [ AND S* expression ]*
 | |
|                  *   ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     type        = null,
 | |
|                     ident       = null,
 | |
|                     token       = null,
 | |
|                     expressions = [];
 | |
| 
 | |
|                 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"){
 | |
|                         tokenStream.unget();
 | |
|                         ident = null;
 | |
|                     } else {
 | |
|                         token = tokenStream.token();
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 this._readWhitespace();
 | |
| 
 | |
|                 if (tokenStream.peek() == Tokens.IDENT){
 | |
|                     type = this._media_type();
 | |
|                     if (token === null){
 | |
|                         token = tokenStream.token();
 | |
|                     }
 | |
|                 } else if (tokenStream.peek() == Tokens.LPAREN){
 | |
|                     if (token === null){
 | |
|                         token = tokenStream.LT(1);
 | |
|                     }
 | |
|                     expressions.push(this._media_expression());
 | |
|                 }
 | |
| 
 | |
|                 if (type === null && expressions.length === 0){
 | |
|                     return null;
 | |
|                 } else {
 | |
|                     this._readWhitespace();
 | |
|                     while (tokenStream.match(Tokens.IDENT)){
 | |
|                         if (tokenStream.token().value.toLowerCase() != "and"){
 | |
|                             this._unexpectedToken(tokenStream.token());
 | |
|                         }
 | |
| 
 | |
|                         this._readWhitespace();
 | |
|                         expressions.push(this._media_expression());
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 return new MediaQuery(ident, type, expressions, token.startLine, token.startCol);
 | |
|             },
 | |
| 
 | |
|             //CSS3 Media Queries
 | |
|             _media_type: function(){
 | |
|                 /*
 | |
|                  * media_type
 | |
|                  *   : IDENT
 | |
|                  *   ;
 | |
|                  */
 | |
|                 return this._media_feature();
 | |
|             },
 | |
| 
 | |
|             /**
 | |
|              * Note: in CSS3 Media Queries, this is called "expression".
 | |
|              * Renamed here to avoid conflict with CSS3 Selectors
 | |
|              * definition of "expression". Also note that "expr" in the
 | |
|              * grammar now maps to "expression" from CSS3 selectors.
 | |
|              * @method _media_expression
 | |
|              * @private
 | |
|              */
 | |
|             _media_expression: function(){
 | |
|                 /*
 | |
|                  * expression
 | |
|                  *  : '(' S* media_feature S* [ ':' S* expr ]? ')' S*
 | |
|                  *  ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     feature     = null,
 | |
|                     token,
 | |
|                     expression  = null;
 | |
| 
 | |
|                 tokenStream.mustMatch(Tokens.LPAREN);
 | |
| 
 | |
|                 feature = this._media_feature();
 | |
|                 this._readWhitespace();
 | |
| 
 | |
|                 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));
 | |
|             },
 | |
| 
 | |
|             //CSS3 Media Queries
 | |
|             _media_feature: function(){
 | |
|                 /*
 | |
|                  * media_feature
 | |
|                  *   : IDENT
 | |
|                  *   ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream;
 | |
| 
 | |
|                 tokenStream.mustMatch(Tokens.IDENT);
 | |
| 
 | |
|                 return SyntaxUnit.fromToken(tokenStream.token());
 | |
|             },
 | |
| 
 | |
|             //CSS3 Paged Media
 | |
|             _page: function(){
 | |
|                 /*
 | |
|                  * page:
 | |
|                  *    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)){
 | |
|                     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"){
 | |
|                         this._unexpectedToken(tokenStream.token());
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 //see if there's a colon upcoming
 | |
|                 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.fire({
 | |
|                     type:   "endpage",
 | |
|                     id:     identifier,
 | |
|                     pseudo: pseudoPage,
 | |
|                     line:   line,
 | |
|                     col:    col
 | |
|                 });
 | |
| 
 | |
|             },
 | |
| 
 | |
|             //CSS3 Paged Media
 | |
|             _margin: function(){
 | |
|                 /*
 | |
|                  * margin :
 | |
|                  *    margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
 | |
|                  *    ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     line,
 | |
|                     col,
 | |
|                     marginSym   = this._margin_sym();
 | |
| 
 | |
|                 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({
 | |
|                         type: "endpagemargin",
 | |
|                         margin: marginSym,
 | |
|                         line:   line,
 | |
|                         col:    col
 | |
|                     });
 | |
|                     return true;
 | |
|                 } else {
 | |
|                     return false;
 | |
|                 }
 | |
|             },
 | |
| 
 | |
|             //CSS3 Paged Media
 | |
|             _margin_sym: function(){
 | |
| 
 | |
|                 /*
 | |
|                  * margin_sym :
 | |
|                  *    TOPLEFTCORNER_SYM |
 | |
|                  *    TOPLEFT_SYM |
 | |
|                  *    TOPCENTER_SYM |
 | |
|                  *    TOPRIGHT_SYM |
 | |
|                  *    TOPRIGHTCORNER_SYM |
 | |
|                  *    BOTTOMLEFTCORNER_SYM |
 | |
|                  *    BOTTOMLEFT_SYM |
 | |
|                  *    BOTTOMCENTER_SYM |
 | |
|                  *    BOTTOMRIGHT_SYM |
 | |
|                  *    BOTTOMRIGHTCORNER_SYM |
 | |
|                  *    LEFTTOP_SYM |
 | |
|                  *    LEFTMIDDLE_SYM |
 | |
|                  *    LEFTBOTTOM_SYM |
 | |
|                  *    RIGHTTOP_SYM |
 | |
|                  *    RIGHTMIDDLE_SYM |
 | |
|                  *    RIGHTBOTTOM_SYM
 | |
|                  *    ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream;
 | |
| 
 | |
|                 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.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_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());
 | |
|                 } else {
 | |
|                     return null;
 | |
|                 }
 | |
| 
 | |
|             },
 | |
| 
 | |
|             _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
 | |
|                  *   : 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
 | |
|                 });
 | |
|             },
 | |
| 
 | |
|             _operator: function(){
 | |
| 
 | |
|                 /*
 | |
|                  * operator
 | |
|                  *  : '/' S* | ',' S* | /( empty )/
 | |
|                  *  ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     token       = null;
 | |
| 
 | |
|                 if (tokenStream.match([Tokens.SLASH, Tokens.COMMA])){
 | |
|                     token =  tokenStream.token();
 | |
|                     this._readWhitespace();
 | |
|                 }
 | |
|                 return token ? PropertyValuePart.fromToken(token) : null;
 | |
| 
 | |
|             },
 | |
| 
 | |
|             _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])){
 | |
|                     token = tokenStream.token();
 | |
|                     value = new Combinator(token.value, token.startLine, token.startCol);
 | |
|                     this._readWhitespace();
 | |
|                 }
 | |
| 
 | |
|                 return value;
 | |
|             },
 | |
| 
 | |
|             _unary_operator: function(){
 | |
| 
 | |
|                 /*
 | |
|                  * unary_operator
 | |
|                  *  : '-' | '+'
 | |
|                  *  ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream;
 | |
| 
 | |
|                 if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])){
 | |
|                     return tokenStream.token().value;
 | |
|                 } else {
 | |
|                     return null;
 | |
|                 }
 | |
|             },
 | |
| 
 | |
|             _property: function(){
 | |
| 
 | |
|                 /*
 | |
|                  * property
 | |
|                  *   : IDENT S*
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     value       = null,
 | |
|                     hack        = null,
 | |
|                     tokenValue,
 | |
|                     token,
 | |
|                     line,
 | |
|                     col;
 | |
| 
 | |
|                 //check for star hack - throws error if not allowed
 | |
|                 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)){
 | |
|                     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){
 | |
|                         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
 | |
|                  *   : selectors_group
 | |
|                  *     '{' S* declaration? [ ';' S* declaration? ]* '}' S*
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     tt,
 | |
|                     selectors;
 | |
| 
 | |
| 
 | |
|                 /*
 | |
|                  * Error Recovery: If even a single selector fails to parse,
 | |
|                  * then the entire ruleset should be thrown away.
 | |
|                  */
 | |
|                 try {
 | |
|                     selectors = this._selectors_group();
 | |
|                 } catch (ex){
 | |
|                     if (ex instanceof SyntaxError && !this.options.strict){
 | |
| 
 | |
|                         //fire error event
 | |
|                         this.fire({
 | |
|                             type:       "error",
 | |
|                             error:      ex,
 | |
|                             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 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){
 | |
| 
 | |
|                     this.fire({
 | |
|                         type:       "startrule",
 | |
|                         selectors:  selectors,
 | |
|                         line:       selectors[0].line,
 | |
|                         col:        selectors[0].col
 | |
|                     });
 | |
| 
 | |
|                     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
 | |
|                  *   : selector [ COMMA S* selector ]*
 | |
|                  *   ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     selectors   = [],
 | |
|                     selector;
 | |
| 
 | |
|                 selector = this._selector();
 | |
|                 if (selector !== null){
 | |
| 
 | |
|                     selectors.push(selector);
 | |
|                     while(tokenStream.match(Tokens.COMMA)){
 | |
|                         this._readWhitespace();
 | |
|                         selector = this._selector();
 | |
|                         if (selector !== null){
 | |
|                             selectors.push(selector);
 | |
|                         } else {
 | |
|                             this._unexpectedToken(tokenStream.LT(1));
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 return selectors.length ? selectors : null;
 | |
|             },
 | |
| 
 | |
|             //CSS3 Selectors
 | |
|             _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){
 | |
|                     return null;
 | |
|                 }
 | |
| 
 | |
|                 selector.push(nextSelector);
 | |
| 
 | |
|                 do {
 | |
| 
 | |
|                     //look for a combinator
 | |
|                     combinator = this._combinator();
 | |
| 
 | |
|                     if (combinator !== null){
 | |
|                         selector.push(combinator);
 | |
|                         nextSelector = this._simple_selector_sequence();
 | |
| 
 | |
|                         //there must be a next selector
 | |
|                         if (nextSelector === null){
 | |
|                             this._unexpectedToken(this.LT(1));
 | |
|                         } else {
 | |
| 
 | |
|                             //nextSelector is an instance of SelectorPart
 | |
|                             selector.push(nextSelector);
 | |
|                         }
 | |
|                     } else {
 | |
| 
 | |
|                         //if there's not whitespace, we're done
 | |
|                         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){
 | |
|                                     this._unexpectedToken(tokenStream.LT(1));
 | |
|                                 }
 | |
|                             } else {
 | |
| 
 | |
|                                 if (combinator !== null){
 | |
|                                     selector.push(combinator);
 | |
|                                 } else {
 | |
|                                     selector.push(ws);
 | |
|                                 }
 | |
| 
 | |
|                                 selector.push(nextSelector);
 | |
|                             }
 | |
|                         } else {
 | |
|                             break;
 | |
|                         }
 | |
| 
 | |
|                     }
 | |
|                 } while(true);
 | |
| 
 | |
|                 return new Selector(selector, selector[0].line, selector[0].col);
 | |
|             },
 | |
| 
 | |
|             //CSS3 Selectors
 | |
|             _simple_selector_sequence: function(){
 | |
|                 /*
 | |
|                  * simple_selector_sequence
 | |
|                  *   : [ type_selector | universal ]
 | |
|                  *     [ HASH | class | attrib | pseudo | negation ]*
 | |
|                  *   | [ HASH | class | attrib | pseudo | negation ]+
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
| 
 | |
|                     //parts of a simple selector
 | |
|                     elementName = null,
 | |
|                     modifiers   = [],
 | |
| 
 | |
|                     //complete selector text
 | |
|                     selectorText= "",
 | |
| 
 | |
|                     //the different parts after the element name to search for
 | |
|                     components  = [
 | |
|                         //HASH
 | |
|                         function(){
 | |
|                             return tokenStream.match(Tokens.HASH) ?
 | |
|                                     new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) :
 | |
|                                     null;
 | |
|                         },
 | |
|                         this._class,
 | |
|                         this._attrib,
 | |
|                         this._pseudo,
 | |
|                         this._negation
 | |
|                     ],
 | |
|                     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){
 | |
|                     elementName = this._universal();
 | |
|                 }
 | |
| 
 | |
|                 if (elementName !== null){
 | |
|                     selectorText += elementName;
 | |
|                 }
 | |
| 
 | |
|                 while(true){
 | |
| 
 | |
|                     //whitespace means we're done
 | |
|                     if (tokenStream.peek() === Tokens.S){
 | |
|                         break;
 | |
|                     }
 | |
| 
 | |
|                     //check for each component
 | |
|                     while(i < len && component === null){
 | |
|                         component = components[i++].call(this);
 | |
|                     }
 | |
| 
 | |
|                     if (component === null){
 | |
| 
 | |
|                         //we don't have a selector
 | |
|                         if (selectorText === ""){
 | |
|                             return null;
 | |
|                         } else {
 | |
|                             break;
 | |
|                         }
 | |
|                     } else {
 | |
|                         i = 0;
 | |
|                         modifiers.push(component);
 | |
|                         selectorText += component.toString();
 | |
|                         component = null;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
| 
 | |
|                 return selectorText !== "" ?
 | |
|                         new SelectorPart(elementName, modifiers, selectorText, line, col) :
 | |
|                         null;
 | |
|             },
 | |
| 
 | |
|             //CSS3 Selectors
 | |
|             _type_selector: function(){
 | |
|                 /*
 | |
|                  * type_selector
 | |
|                  *   : [ namespace_prefix ]? element_name
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     ns          = this._namespace_prefix(),
 | |
|                     elementName = this._element_name();
 | |
| 
 | |
|                 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){
 | |
|                         tokenStream.unget();
 | |
|                         if (ns.length > 1){
 | |
|                             tokenStream.unget();
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     return null;
 | |
|                 } else {
 | |
|                     if (ns){
 | |
|                         elementName.text = ns + elementName.text;
 | |
|                         elementName.col -= ns.length;
 | |
|                     }
 | |
|                     return elementName;
 | |
|                 }
 | |
|             },
 | |
| 
 | |
|             //CSS3 Selectors
 | |
|             _class: function(){
 | |
|                 /*
 | |
|                  * class
 | |
|                  *   : '.' IDENT
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     token;
 | |
| 
 | |
|                 if (tokenStream.match(Tokens.DOT)){
 | |
|                     tokenStream.mustMatch(Tokens.IDENT);
 | |
|                     token = tokenStream.token();
 | |
|                     return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1);
 | |
|                 } else {
 | |
|                     return null;
 | |
|                 }
 | |
| 
 | |
|             },
 | |
| 
 | |
|             //CSS3 Selectors
 | |
|             _element_name: function(){
 | |
|                 /*
 | |
|                  * element_name
 | |
|                  *   : IDENT
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     token;
 | |
| 
 | |
|                 if (tokenStream.match(Tokens.IDENT)){
 | |
|                     token = tokenStream.token();
 | |
|                     return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol);
 | |
| 
 | |
|                 } else {
 | |
|                     return null;
 | |
|                 }
 | |
|             },
 | |
| 
 | |
|             //CSS3 Selectors
 | |
|             _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])){
 | |
|                         value += tokenStream.token().value;
 | |
|                     }
 | |
| 
 | |
|                     tokenStream.mustMatch(Tokens.PIPE);
 | |
|                     value += "|";
 | |
| 
 | |
|                 }
 | |
| 
 | |
|                 return value.length ? value : null;
 | |
|             },
 | |
| 
 | |
|             //CSS3 Selectors
 | |
|             _universal: function(){
 | |
|                 /*
 | |
|                  * universal
 | |
|                  *   : [ namespace_prefix ]? '*'
 | |
|                  *   ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     value       = "",
 | |
|                     ns;
 | |
| 
 | |
|                 ns = this._namespace_prefix();
 | |
|                 if(ns){
 | |
|                     value += ns;
 | |
|                 }
 | |
| 
 | |
|                 if(tokenStream.match(Tokens.STAR)){
 | |
|                     value += "*";
 | |
|                 }
 | |
| 
 | |
|                 return value.length ? value : null;
 | |
| 
 | |
|            },
 | |
| 
 | |
|             //CSS3 Selectors
 | |
|             _attrib: function(){
 | |
|                 /*
 | |
|                  * attrib
 | |
|                  *   : '[' S* [ namespace_prefix ]? IDENT S*
 | |
|                  *         [ [ PREFIXMATCH |
 | |
|                  *             SUFFIXMATCH |
 | |
|                  *             SUBSTRINGMATCH |
 | |
|                  *             '=' |
 | |
|                  *             INCLUDES |
 | |
|                  *             DASHMATCH ] S* [ IDENT | STRING ] S*
 | |
|                  *         ]? ']'
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     value       = null,
 | |
|                     ns,
 | |
|                     token;
 | |
| 
 | |
|                 if (tokenStream.match(Tokens.LBRACKET)){
 | |
|                     token = tokenStream.token();
 | |
|                     value = token.value;
 | |
|                     value += this._readWhitespace();
 | |
| 
 | |
|                     ns = this._namespace_prefix();
 | |
| 
 | |
|                     if (ns){
 | |
|                         value += ns;
 | |
|                     }
 | |
| 
 | |
|                     tokenStream.mustMatch(Tokens.IDENT);
 | |
|                     value += tokenStream.token().value;
 | |
|                     value += this._readWhitespace();
 | |
| 
 | |
|                     if(tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH,
 | |
|                             Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])){
 | |
| 
 | |
|                         value += tokenStream.token().value;
 | |
|                         value += this._readWhitespace();
 | |
| 
 | |
|                         tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]);
 | |
|                         value += tokenStream.token().value;
 | |
|                         value += this._readWhitespace();
 | |
|                     }
 | |
| 
 | |
|                     tokenStream.mustMatch(Tokens.RBRACKET);
 | |
| 
 | |
|                     return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol);
 | |
|                 } else {
 | |
|                     return null;
 | |
|                 }
 | |
|             },
 | |
| 
 | |
|             //CSS3 Selectors
 | |
|             _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)){
 | |
|                         colons += ":";
 | |
|                     }
 | |
| 
 | |
|                     if (tokenStream.match(Tokens.IDENT)){
 | |
|                         pseudo = tokenStream.token().value;
 | |
|                         line = tokenStream.token().startLine;
 | |
|                         col = tokenStream.token().startCol - colons.length;
 | |
|                     } else if (tokenStream.peek() == Tokens.FUNCTION){
 | |
|                         line = tokenStream.LT(1).startLine;
 | |
|                         col = tokenStream.LT(1).startCol - colons.length;
 | |
|                         pseudo = this._functional_pseudo();
 | |
|                     }
 | |
| 
 | |
|                     if (pseudo){
 | |
|                         pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 return pseudo;
 | |
|             },
 | |
| 
 | |
|             //CSS3 Selectors
 | |
|             _functional_pseudo: function(){
 | |
|                 /*
 | |
|                  * functional_pseudo
 | |
|                  *   : FUNCTION S* expression ')'
 | |
|                  *   ;
 | |
|                 */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     value = null;
 | |
| 
 | |
|                 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
 | |
|                  *   : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     value       = "";
 | |
| 
 | |
|                 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])){
 | |
| 
 | |
|                     value += tokenStream.token().value;
 | |
|                     value += this._readWhitespace();
 | |
|                 }
 | |
| 
 | |
|                 return value.length ? value : null;
 | |
| 
 | |
|             },
 | |
| 
 | |
|             //CSS3 Selectors
 | |
|             _negation: function(){
 | |
|                 /*
 | |
|                  * negation
 | |
|                  *   : NOT S* negation_arg S* ')'
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     line,
 | |
|                     col,
 | |
|                     value       = "",
 | |
|                     arg,
 | |
|                     subpart     = null;
 | |
| 
 | |
|                 if (tokenStream.match(Tokens.NOT)){
 | |
|                     value = tokenStream.token().value;
 | |
|                     line = tokenStream.token().startLine;
 | |
|                     col = tokenStream.token().startCol;
 | |
|                     value += this._readWhitespace();
 | |
|                     arg = this._negation_arg();
 | |
|                     value += arg;
 | |
|                     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
 | |
|                  *   : type_selector | universal | HASH | class | attrib | pseudo
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     args        = [
 | |
|                         this._type_selector,
 | |
|                         this._universal,
 | |
|                         function(){
 | |
|                             return tokenStream.match(Tokens.HASH) ?
 | |
|                                     new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) :
 | |
|                                     null;
 | |
|                         },
 | |
|                         this._class,
 | |
|                         this._attrib,
 | |
|                         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){
 | |
| 
 | |
|                     arg = args[i].call(this);
 | |
|                     i++;
 | |
|                 }
 | |
| 
 | |
|                 //must be a negation arg
 | |
|                 if (arg === null){
 | |
|                     this._unexpectedToken(tokenStream.LT(1));
 | |
|                 }
 | |
| 
 | |
|                 //it's an element name
 | |
|                 if (arg.type == "elementName"){
 | |
|                     part = new SelectorPart(arg, [], arg.toString(), line, col);
 | |
|                 } else {
 | |
|                     part = new SelectorPart(null, [arg], arg.toString(), line, col);
 | |
|                 }
 | |
| 
 | |
|                 return part;
 | |
|             },
 | |
| 
 | |
|             _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){
 | |
| 
 | |
|                     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){
 | |
|                         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 == "_") {
 | |
| 
 | |
|                         propertyName = property.text;
 | |
|                     }
 | |
| 
 | |
|                     try {
 | |
|                         this._validateProperty(propertyName, expr);
 | |
|                     } catch (ex) {
 | |
|                         invalid = ex;
 | |
|                     }
 | |
| 
 | |
|                     this.fire({
 | |
|                         type:       "property",
 | |
|                         property:   property,
 | |
|                         value:      expr,
 | |
|                         important:  prio,
 | |
|                         line:       property.line,
 | |
|                         col:        property.col,
 | |
|                         invalid:    invalid
 | |
|                     });
 | |
| 
 | |
|                     return true;
 | |
|                 } else {
 | |
|                     return false;
 | |
|                 }
 | |
|             },
 | |
| 
 | |
|             _prio: function(){
 | |
|                 /*
 | |
|                  * prio
 | |
|                  *   : IMPORTANT_SYM S*
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     result      = tokenStream.match(Tokens.IMPORTANT_SYM);
 | |
| 
 | |
|                 this._readWhitespace();
 | |
|                 return result;
 | |
|             },
 | |
| 
 | |
|             _expr: function(){
 | |
|                 /*
 | |
|                  * expr
 | |
|                  *   : term [ operator term ]*
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     values      = [],
 | |
|                     //valueParts    = [],
 | |
|                     value       = null,
 | |
|                     operator    = null;
 | |
| 
 | |
|                 value = this._term();
 | |
|                 if (value !== null){
 | |
| 
 | |
|                     values.push(value);
 | |
| 
 | |
|                     do {
 | |
|                         operator = this._operator();
 | |
| 
 | |
|                         //if there's an operator, keep building up the value parts
 | |
|                         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){
 | |
|                             break;
 | |
|                         } else {
 | |
|                             values.push(value);
 | |
|                         }
 | |
|                     } while(true);
 | |
|                 }
 | |
| 
 | |
|                 //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
 | |
|                  *   : unary_operator?
 | |
|                  *     [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* |
 | |
|                  *       TIME S* | FREQ S* | function | ie_function ]
 | |
|                  *   | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     unary       = null,
 | |
|                     value       = null,
 | |
|                     token,
 | |
|                     line,
 | |
|                     col;
 | |
| 
 | |
|                 //returns the operator or null
 | |
|                 unary = this._unary_operator();
 | |
|                 if (unary !== null){
 | |
|                     line = tokenStream.token().startLine;
 | |
|                     col = tokenStream.token().startCol;
 | |
|                 }
 | |
| 
 | |
|                 //exception for IE filters
 | |
|                 if (tokenStream.peek() == Tokens.IE_FUNCTION && this.options.ieFilters){
 | |
| 
 | |
|                     value = this._ie_function();
 | |
|                     if (unary === null){
 | |
|                         line = tokenStream.token().startLine;
 | |
|                         col = tokenStream.token().startCol;
 | |
|                     }
 | |
| 
 | |
|                 //see if there's a simple match
 | |
|                 } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH,
 | |
|                         Tokens.ANGLE, Tokens.TIME,
 | |
|                         Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])){
 | |
| 
 | |
|                     value = tokenStream.token().value;
 | |
|                     if (unary === null){
 | |
|                         line = tokenStream.token().startLine;
 | |
|                         col = tokenStream.token().startCol;
 | |
|                     }
 | |
|                     this._readWhitespace();
 | |
|                 } else {
 | |
| 
 | |
|                     //see if it's a color
 | |
|                     token = this._hexcolor();
 | |
|                     if (token === null){
 | |
| 
 | |
|                         //if there's no unary, get the start of the next token for line/col info
 | |
|                         if (unary === null){
 | |
|                             line = tokenStream.LT(1).startLine;
 | |
|                             col = tokenStream.LT(1).startCol;
 | |
|                         }
 | |
| 
 | |
|                         //has to be a function
 | |
|                         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){
 | |
|                                 value = this._ie_function();
 | |
|                             } else {
 | |
|                                 value = this._function();
 | |
|                             }
 | |
|                         }
 | |
| 
 | |
|                         /*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){
 | |
|                             line = token.startLine;
 | |
|                             col = token.startCol;
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                 }
 | |
| 
 | |
|                 return value !== null ?
 | |
|                         new PropertyValuePart(unary !== null ? unary + value : value, line, col) :
 | |
|                         null;
 | |
| 
 | |
|             },
 | |
| 
 | |
|             _function: function(){
 | |
| 
 | |
|                 /*
 | |
|                  * function
 | |
|                  *   : FUNCTION S* expr ')' S*
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     functionText = null,
 | |
|                     expr        = null,
 | |
|                     lt;
 | |
| 
 | |
|                 if (tokenStream.match(Tokens.FUNCTION)){
 | |
|                     functionText = tokenStream.token().value;
 | |
|                     this._readWhitespace();
 | |
|                     expr = this._expr();
 | |
|                     functionText += expr;
 | |
| 
 | |
|                     //START: Horrible hack in case it's an IE filter
 | |
|                     if (this.options.ieFilters && tokenStream.peek() == Tokens.EQUALS){
 | |
|                         do {
 | |
| 
 | |
|                             if (this._readWhitespace()){
 | |
|                                 functionText += tokenStream.token().value;
 | |
|                             }
 | |
| 
 | |
|                             //might be second time in the loop
 | |
|                             if (tokenStream.LA(0) == Tokens.COMMA){
 | |
|                                 functionText += tokenStream.token().value;
 | |
|                             }
 | |
| 
 | |
|                             tokenStream.match(Tokens.IDENT);
 | |
|                             functionText += tokenStream.token().value;
 | |
| 
 | |
|                             tokenStream.match(Tokens.EQUALS);
 | |
|                             functionText += tokenStream.token().value;
 | |
| 
 | |
|                             //functionText += this._term();
 | |
|                             lt = tokenStream.peek();
 | |
|                             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]));
 | |
|                     }
 | |
| 
 | |
|                     //END: Horrible Hack
 | |
| 
 | |
|                     tokenStream.match(Tokens.RPAREN);
 | |
|                     functionText += ")";
 | |
|                     this._readWhitespace();
 | |
|                 }
 | |
| 
 | |
|                 return functionText;
 | |
|             },
 | |
| 
 | |
|             _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])){
 | |
|                     functionText = tokenStream.token().value;
 | |
| 
 | |
|                     do {
 | |
| 
 | |
|                         if (this._readWhitespace()){
 | |
|                             functionText += tokenStream.token().value;
 | |
|                         }
 | |
| 
 | |
|                         //might be second time in the loop
 | |
|                         if (tokenStream.LA(0) == Tokens.COMMA){
 | |
|                             functionText += tokenStream.token().value;
 | |
|                         }
 | |
| 
 | |
|                         tokenStream.match(Tokens.IDENT);
 | |
|                         functionText += tokenStream.token().value;
 | |
| 
 | |
|                         tokenStream.match(Tokens.EQUALS);
 | |
|                         functionText += tokenStream.token().value;
 | |
| 
 | |
|                         //functionText += this._term();
 | |
|                         lt = tokenStream.peek();
 | |
|                         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);
 | |
|                     functionText += ")";
 | |
|                     this._readWhitespace();
 | |
|                 }
 | |
| 
 | |
|                 return functionText;
 | |
|             },
 | |
| 
 | |
|             _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])
 | |
|                  * after the "#"; e.g., "#000" is OK, but "#abcd" is not.
 | |
|                  *
 | |
|                  * hexcolor
 | |
|                  *   : HASH S*
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     token = null,
 | |
|                     color;
 | |
| 
 | |
|                 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)){
 | |
|                         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:
 | |
|                  *   : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' {
 | |
|                  *   ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     token,
 | |
|                     tt,
 | |
|                     name;
 | |
| 
 | |
|                 tokenStream.mustMatch(Tokens.KEYFRAMES_SYM);
 | |
|                 this._readWhitespace();
 | |
|                 name = this._keyframe_name();
 | |
| 
 | |
|                 this._readWhitespace();
 | |
|                 tokenStream.mustMatch(Tokens.LBRACE);
 | |
| 
 | |
|                 this.fire({
 | |
|                     type:   "startkeyframes",
 | |
|                     name:   name,
 | |
|                     line:   name.line,
 | |
|                     col:    name.col
 | |
|                 });
 | |
| 
 | |
|                 this._readWhitespace();
 | |
|                 tt = tokenStream.peek();
 | |
| 
 | |
|                 //check for key
 | |
|                 while(tt == Tokens.IDENT || tt == Tokens.PERCENTAGE) {
 | |
|                     this._keyframe_rule();
 | |
|                     this._readWhitespace();
 | |
|                     tt = tokenStream.peek();
 | |
|                 }
 | |
| 
 | |
|                 this.fire({
 | |
|                     type:   "endkeyframes",
 | |
|                     name:   name,
 | |
|                     line:   name.line,
 | |
|                     col:    name.col
 | |
|                 });
 | |
| 
 | |
|                 this._readWhitespace();
 | |
|                 tokenStream.mustMatch(Tokens.RBRACE);
 | |
| 
 | |
|             },
 | |
| 
 | |
|             _keyframe_name: function(){
 | |
| 
 | |
|                 /*
 | |
|                  * keyframe_name:
 | |
|                  *   : IDENT
 | |
|                  *   | STRING
 | |
|                  *   ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     token;
 | |
| 
 | |
|                 tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]);
 | |
|                 return SyntaxUnit.fromToken(tokenStream.token());
 | |
|             },
 | |
| 
 | |
|             _keyframe_rule: function(){
 | |
| 
 | |
|                 /*
 | |
|                  * keyframe_rule:
 | |
|                  *   : key_list S*
 | |
|                  *     '{' S* declaration [ ';' S* declaration ]* '}' S*
 | |
|                  *   ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     token,
 | |
|                     keyList = this._key_list();
 | |
| 
 | |
|                 this.fire({
 | |
|                     type:   "startkeyframerule",
 | |
|                     keys:   keyList,
 | |
|                     line:   keyList[0].line,
 | |
|                     col:    keyList[0].col
 | |
|                 });
 | |
| 
 | |
|                 this._readDeclarations(true);
 | |
| 
 | |
|                 this.fire({
 | |
|                     type:   "endkeyframerule",
 | |
|                     keys:   keyList,
 | |
|                     line:   keyList[0].line,
 | |
|                     col:    keyList[0].col
 | |
|                 });
 | |
| 
 | |
|             },
 | |
| 
 | |
|             _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)){
 | |
|                     this._readWhitespace();
 | |
|                     keyList.push(this._key());
 | |
|                     this._readWhitespace();
 | |
|                 }
 | |
| 
 | |
|                 return keyList;
 | |
|             },
 | |
| 
 | |
|             _key: function(){
 | |
|                 /*
 | |
|                  * There is a restriction that IDENT can be only "from" or "to".
 | |
|                  *
 | |
|                  * key
 | |
|                  *   : PERCENTAGE
 | |
|                  *   | IDENT
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     token;
 | |
| 
 | |
|                 if (tokenStream.match(Tokens.PERCENTAGE)){
 | |
|                     return SyntaxUnit.fromToken(tokenStream.token());
 | |
|                 } else if (tokenStream.match(Tokens.IDENT)){
 | |
|                     token = tokenStream.token();
 | |
| 
 | |
|                     if (/from|to/i.test(token.value)){
 | |
|                         return SyntaxUnit.fromToken(token);
 | |
|                     }
 | |
| 
 | |
|                     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.
 | |
|              * @return {void}
 | |
|              * @method _skipCruft
 | |
|              * @private
 | |
|              */
 | |
|             _skipCruft: function(){
 | |
|                 while(this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])){
 | |
|                     //noop
 | |
|                 }
 | |
|             },
 | |
| 
 | |
|             /**
 | |
|              * Not part of CSS grammar, but this pattern occurs frequently
 | |
|              * in the official CSS grammar. Split out here to eliminate
 | |
|              * duplicate code.
 | |
|              * @param {Boolean} checkStart Indicates if the rule should check
 | |
|              *      for the left brace at the beginning.
 | |
|              * @param {Boolean} readMargins Indicates if the rule should check
 | |
|              *      for margin patterns.
 | |
|              * @return {void}
 | |
|              * @method _readDeclarations
 | |
|              * @private
 | |
|              */
 | |
|             _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.
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     tt;
 | |
| 
 | |
| 
 | |
|                 this._readWhitespace();
 | |
| 
 | |
|                 if (checkStart){
 | |
|                     tokenStream.mustMatch(Tokens.LBRACE);
 | |
|                 }
 | |
| 
 | |
|                 this._readWhitespace();
 | |
| 
 | |
|                 try {
 | |
| 
 | |
|                     while(true){
 | |
| 
 | |
|                         if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())){
 | |
|                             //noop
 | |
|                         } 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){
 | |
| 
 | |
|                         //fire error event
 | |
|                         this.fire({
 | |
|                             type:       "error",
 | |
|                             error:      ex,
 | |
|                             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 there's a semicolon, then there might be another declaration
 | |
|                             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
 | |
|              * white space, this function is used to match as much white space
 | |
|              * as necessary.
 | |
|              * @method _readWhitespace
 | |
|              * @return {String} The white space if found, empty string if not.
 | |
|              * @private
 | |
|              */
 | |
|             _readWhitespace: function(){
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     ws = "";
 | |
| 
 | |
|                 while(tokenStream.match(Tokens.S)){
 | |
|                     ws += tokenStream.token().value;
 | |
|                 }
 | |
| 
 | |
|                 return ws;
 | |
|             },
 | |
| 
 | |
| 
 | |
|             /**
 | |
|              * Throws an error when an unexpected token is found.
 | |
|              * @param {Object} token The token that was found.
 | |
|              * @method _unexpectedToken
 | |
|              * @return {void}
 | |
|              * @private
 | |
|              */
 | |
|             _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){
 | |
|                     this._unexpectedToken(this._tokenStream.LT(1));
 | |
|                 }
 | |
|             },
 | |
| 
 | |
|             //-----------------------------------------------------------------
 | |
|             // Validation methods
 | |
|             //-----------------------------------------------------------------
 | |
|             _validateProperty: function(property, value){
 | |
|                 Validation.validate(property, value);
 | |
|             },
 | |
| 
 | |
|             //-----------------------------------------------------------------
 | |
|             // Parsing methods
 | |
|             //-----------------------------------------------------------------
 | |
| 
 | |
|             parse: function(input){
 | |
|                 this._tokenStream = new TokenStream(input, Tokens);
 | |
|                 this._stylesheet();
 | |
|             },
 | |
| 
 | |
|             parseStyleSheet: function(input){
 | |
|                 //just passthrough
 | |
|                 return this.parse(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;
 | |
|             },
 | |
| 
 | |
|             /**
 | |
|              * 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){
 | |
| 
 | |
|                 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.
 | |
|              * @param {String} input The text to parser.
 | |
|              * @return {Boolean} True if the parse completed successfully, false if not.
 | |
|              * @method parseRule
 | |
|              */
 | |
|             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;
 | |
|             },
 | |
| 
 | |
|             /**
 | |
|              * Parses a single CSS selector (no comma)
 | |
|              * @param {String} input The text to parse as a CSS selector.
 | |
|              * @return {Selector} An object representing the selector.
 | |
|              * @throws parserlib.util.SyntaxError If an unexpected token is found.
 | |
|              * @method parseSelector
 | |
|              */
 | |
|             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
 | |
|              * separated by semicolons.
 | |
|              * @param {String} input The text to parse as a style attribute
 | |
|              * @return {void}
 | |
|              * @method parseStyleAttribute
 | |
|              */
 | |
|             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)){
 | |
|             proto[prop] = additions[prop];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return proto;
 | |
| }();
 | |
| 
 | |
| 
 | |
| /*
 | |
| nth
 | |
|   : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? |
 | |
|          ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S*
 | |
|   ;
 | |
| */
 | |
| /*global Validation, ValidationTypes, ValidationError*/
 | |
| var Properties = {
 | |
| 
 | |
|     //A
 | |
|     "alignment-adjust"              : "auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | <percentage> | <length>",
 | |
|     "alignment-baseline"            : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
 | |
|     "animation"                     : 1,
 | |
|     "animation-delay"               : { multi: "<time>", comma: true },
 | |
|     "animation-direction"           : { multi: "normal | alternate", comma: true },
 | |
|     "animation-duration"            : { multi: "<time>", comma: true },
 | |
|     "animation-iteration-count"     : { multi: "<number> | infinite", comma: true },
 | |
|     "animation-name"                : { multi: "none | <ident>", comma: true },
 | |
|     "animation-play-state"          : { multi: "running | paused", comma: true },
 | |
|     "animation-timing-function"     : 1,
 | |
| 
 | |
|     //vendor prefixed
 | |
|     "-moz-animation-delay"               : { multi: "<time>", comma: true },
 | |
|     "-moz-animation-direction"           : { multi: "normal | alternate", comma: true },
 | |
|     "-moz-animation-duration"            : { multi: "<time>", comma: true },
 | |
|     "-moz-animation-iteration-count"     : { multi: "<number> | infinite", comma: true },
 | |
|     "-moz-animation-name"                : { multi: "none | <ident>", comma: true },
 | |
|     "-moz-animation-play-state"          : { multi: "running | paused", comma: true },
 | |
| 
 | |
|     "-ms-animation-delay"               : { multi: "<time>", comma: true },
 | |
|     "-ms-animation-direction"           : { multi: "normal | alternate", comma: true },
 | |
|     "-ms-animation-duration"            : { multi: "<time>", comma: true },
 | |
|     "-ms-animation-iteration-count"     : { multi: "<number> | infinite", comma: true },
 | |
|     "-ms-animation-name"                : { multi: "none | <ident>", comma: true },
 | |
|     "-ms-animation-play-state"          : { multi: "running | paused", comma: true },
 | |
| 
 | |
|     "-webkit-animation-delay"               : { multi: "<time>", comma: true },
 | |
|     "-webkit-animation-direction"           : { multi: "normal | alternate", comma: true },
 | |
|     "-webkit-animation-duration"            : { multi: "<time>", comma: true },
 | |
|     "-webkit-animation-iteration-count"     : { multi: "<number> | infinite", comma: true },
 | |
|     "-webkit-animation-name"                : { multi: "none | <ident>", comma: true },
 | |
|     "-webkit-animation-play-state"          : { multi: "running | paused", comma: true },
 | |
| 
 | |
|     "-o-animation-delay"               : { multi: "<time>", comma: true },
 | |
|     "-o-animation-direction"           : { multi: "normal | alternate", comma: true },
 | |
|     "-o-animation-duration"            : { multi: "<time>", comma: true },
 | |
|     "-o-animation-iteration-count"     : { multi: "<number> | infinite", comma: true },
 | |
|     "-o-animation-name"                : { multi: "none | <ident>", comma: true },
 | |
|     "-o-animation-play-state"          : { multi: "running | paused", comma: true },
 | |
| 
 | |
|     "appearance"                    : "icon | window | desktop | workspace | document | tooltip | dialog | button | push-button | hyperlink | radio-button | checkbox | menu-item | tab | menu | menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | outline-tree | range | field | combo-box | signature | password | normal | inherit",
 | |
|     "azimuth"                       : function (expression) {
 | |
|         var simple      = "<angle> | leftwards | rightwards | inherit",
 | |
|             direction   = "left-side | far-left | left | center-left | center | center-right | right | far-right | right-side",
 | |
|             behind      = false,
 | |
|             valid       = false,
 | |
|             part;
 | |
| 
 | |
|         if (!ValidationTypes.isAny(expression, simple)) {
 | |
|             if (ValidationTypes.isAny(expression, "behind")) {
 | |
|                 behind = true;
 | |
|                 valid = true;
 | |
|             }
 | |
| 
 | |
|             if (ValidationTypes.isAny(expression, direction)) {
 | |
|                 valid = true;
 | |
|                 if (!behind) {
 | |
|                     ValidationTypes.isAny(expression, "behind");
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (expression.hasNext()) {
 | |
|             part = expression.next();
 | |
|             if (valid) {
 | |
|                 throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
 | |
|             } else {
 | |
|                 throw new ValidationError("Expected (<'azimuth'>) but found '" + part + "'.", part.line, part.col);
 | |
|             }
 | |
|         }
 | |
|     },
 | |
| 
 | |
|     //B
 | |
|     "backface-visibility"           : "visible | hidden",
 | |
|     "background"                    : 1,
 | |
|     "background-attachment"         : { multi: "<attachment>", comma: true },
 | |
|     "background-clip"               : { multi: "<box>", comma: true },
 | |
|     "background-color"              : "<color> | inherit",
 | |
|     "background-image"              : { multi: "<bg-image>", comma: true },
 | |
|     "background-origin"             : { multi: "<box>", comma: true },
 | |
|     "background-position"           : { multi: "<bg-position>", comma: true },
 | |
|     "background-repeat"             : { multi: "<repeat-style>" },
 | |
|     "background-size"               : { multi: "<bg-size>", comma: true },
 | |
|     "baseline-shift"                : "baseline | sub | super | <percentage> | <length>",
 | |
|     "behavior"                      : 1,
 | |
|     "binding"                       : 1,
 | |
|     "bleed"                         : "<length>",
 | |
|     "bookmark-label"                : "<content> | <attr> | <string>",
 | |
|     "bookmark-level"                : "none | <integer>",
 | |
|     "bookmark-state"                : "open | closed",
 | |
|     "bookmark-target"               : "none | <uri> | <attr>",
 | |
|     "border"                        : "<border-width> || <border-style> || <color>",
 | |
|     "border-bottom"                 : "<border-width> || <border-style> || <color>",
 | |
|     "border-bottom-color"           : "<color>",
 | |
|     "border-bottom-left-radius"     :  "<x-one-radius>",
 | |
|     "border-bottom-right-radius"    :  "<x-one-radius>",
 | |
|     "border-bottom-style"           : "<border-style>",
 | |
|     "border-bottom-width"           : "<border-width>",
 | |
|     "border-collapse"               : "collapse | separate | inherit",
 | |
|     "border-color"                  : { multi: "<color> | inherit", max: 4 },
 | |
|     "border-image"                  : 1,
 | |
|     "border-image-outset"           : { multi: "<length> | <number>", max: 4 },
 | |
|     "border-image-repeat"           : { multi: "stretch | repeat | round", max: 2 },
 | |
|     "border-image-slice"            : function(expression) {
 | |
| 
 | |
|         var valid   = false,
 | |
|             numeric = "<number> | <percentage>",
 | |
|             fill    = false,
 | |
|             count   = 0,
 | |
|             max     = 4,
 | |
|             part;
 | |
| 
 | |
|         if (ValidationTypes.isAny(expression, "fill")) {
 | |
|             fill = true;
 | |
|             valid = true;
 | |
|         }
 | |
| 
 | |
|         while (expression.hasNext() && count < max) {
 | |
|             valid = ValidationTypes.isAny(expression, numeric);
 | |
|             if (!valid) {
 | |
|                 break;
 | |
|             }
 | |
|             count++;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         if (!fill) {
 | |
|             ValidationTypes.isAny(expression, "fill");
 | |
|         } else {
 | |
|             valid = true;
 | |
|         }
 | |
| 
 | |
|         if (expression.hasNext()) {
 | |
|             part = expression.next();
 | |
|             if (valid) {
 | |
|                 throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
 | |
|             } else {
 | |
|                 throw new ValidationError("Expected ([<number> | <percentage>]{1,4} && fill?) but found '" + part + "'.", part.line, part.col);
 | |
|             }
 | |
|         }
 | |
|     },
 | |
|     "border-image-source"           : "<image> | none",
 | |
|     "border-image-width"            : { multi: "<length> | <percentage> | <number> | auto", max: 4 },
 | |
|     "border-left"                   : "<border-width> || <border-style> || <color>",
 | |
|     "border-left-color"             : "<color> | inherit",
 | |
|     "border-left-style"             : "<border-style>",
 | |
|     "border-left-width"             : "<border-width>",
 | |
|     "border-radius"                 : function(expression) {
 | |
| 
 | |
|         var valid   = false,
 | |
|             numeric = "<length> | <percentage>",
 | |
|             slash   = false,
 | |
|             fill    = false,
 | |
|             count   = 0,
 | |
|             max     = 8,
 | |
|             part;
 | |
| 
 | |
|         while (expression.hasNext() && count < max) {
 | |
|             valid = ValidationTypes.isAny(expression, numeric);
 | |
|             if (!valid) {
 | |
| 
 | |
|                 if (expression.peek() == "/" && count > 1 && !slash) {
 | |
|                     slash = true;
 | |
|                     max = count + 5;
 | |
|                     expression.next();
 | |
|                 } else {
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             count++;
 | |
|         }
 | |
| 
 | |
|         if (expression.hasNext()) {
 | |
|             part = expression.next();
 | |
|             if (valid) {
 | |
|                 throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
 | |
|             } else {
 | |
|                 throw new ValidationError("Expected (<'border-radius'>) but found '" + part + "'.", part.line, part.col);
 | |
|             }
 | |
|         }
 | |
|     },
 | |
|     "border-right"                  : "<border-width> || <border-style> || <color>",
 | |
|     "border-right-color"            : "<color> | inherit",
 | |
|     "border-right-style"            : "<border-style>",
 | |
|     "border-right-width"            : "<border-width>",
 | |
|     "border-spacing"                : { multi: "<length> | inherit", max: 2 },
 | |
|     "border-style"                  : { multi: "<border-style>", max: 4 },
 | |
|     "border-top"                    : "<border-width> || <border-style> || <color>",
 | |
|     "border-top-color"              : "<color> | inherit",
 | |
|     "border-top-left-radius"        : "<x-one-radius>",
 | |
|     "border-top-right-radius"       : "<x-one-radius>",
 | |
|     "border-top-style"              : "<border-style>",
 | |
|     "border-top-width"              : "<border-width>",
 | |
|     "border-width"                  : { multi: "<border-width>", max: 4 },
 | |
|     "bottom"                        : "<margin-width> | inherit",
 | |
|     "box-align"                     : "start | end | center | baseline | stretch",        //http://www.w3.org/TR/2009/WD-css3-flexbox-20090723/
 | |
|     "box-decoration-break"          : "slice |clone",
 | |
|     "box-direction"                 : "normal | reverse | inherit",
 | |
|     "box-flex"                      : "<number>",
 | |
|     "box-flex-group"                : "<integer>",
 | |
|     "box-lines"                     : "single | multiple",
 | |
|     "box-ordinal-group"             : "<integer>",
 | |
|     "box-orient"                    : "horizontal | vertical | inline-axis | block-axis | inherit",
 | |
|     "box-pack"                      : "start | end | center | justify",
 | |
|     "box-shadow"                    : function (expression) {
 | |
|         var result      = false,
 | |
|             part;
 | |
| 
 | |
|         if (!ValidationTypes.isAny(expression, "none")) {
 | |
|             Validation.multiProperty("<shadow>", expression, true, Infinity);
 | |
|         } else {
 | |
|             if (expression.hasNext()) {
 | |
|                 part = expression.next();
 | |
|                 throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
 | |
|             }
 | |
|         }
 | |
|     },
 | |
|     "box-sizing"                    : "content-box | border-box | inherit",
 | |
|     "break-after"                   : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column",
 | |
|     "break-before"                  : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column",
 | |
|     "break-inside"                  : "auto | avoid | avoid-page | avoid-column",
 | |
| 
 | |
|     //C
 | |
|     "caption-side"                  : "top | bottom | inherit",
 | |
|     "clear"                         : "none | right | left | both | inherit",
 | |
|     "clip"                          : 1,
 | |
|     "color"                         : "<color> | inherit",
 | |
|     "color-profile"                 : 1,
 | |
|     "column-count"                  : "<integer> | auto",                      //http://www.w3.org/TR/css3-multicol/
 | |
|     "column-fill"                   : "auto | balance",
 | |
|     "column-gap"                    : "<length> | normal",
 | |
|     "column-rule"                   : "<border-width> || <border-style> || <color>",
 | |
|     "column-rule-color"             : "<color>",
 | |
|     "column-rule-style"             : "<border-style>",
 | |
|     "column-rule-width"             : "<border-width>",
 | |
|     "column-span"                   : "none | all",
 | |
|     "column-width"                  : "<length> | auto",
 | |
|     "columns"                       : 1,
 | |
|     "content"                       : 1,
 | |
|     "counter-increment"             : 1,
 | |
|     "counter-reset"                 : 1,
 | |
|     "crop"                          : "<shape> | auto",
 | |
|     "cue"                           : "cue-after | cue-before | inherit",
 | |
|     "cue-after"                     : 1,
 | |
|     "cue-before"                    : 1,
 | |
|     "cursor"                        : 1,
 | |
| 
 | |
|     //D
 | |
|     "direction"                     : "ltr | rtl | inherit",
 | |
|     "display"                       : "inline | block | list-item | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | box | inline-box | grid | inline-grid | none | inherit",
 | |
|     "dominant-baseline"             : 1,
 | |
|     "drop-initial-after-adjust"     : "central | middle | after-edge | text-after-edge | ideographic | alphabetic | mathematical | <percentage> | <length>",
 | |
|     "drop-initial-after-align"      : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
 | |
|     "drop-initial-before-adjust"    : "before-edge | text-before-edge | central | middle | hanging | mathematical | <percentage> | <length>",
 | |
|     "drop-initial-before-align"     : "caps-height | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
 | |
|     "drop-initial-size"             : "auto | line | <length> | <percentage>",
 | |
|     "drop-initial-value"            : "initial | <integer>",
 | |
| 
 | |
|     //E
 | |
|     "elevation"                     : "<angle> | below | level | above | higher | lower | inherit",
 | |
|     "empty-cells"                   : "show | hide | inherit",
 | |
| 
 | |
|     //F
 | |
|     "filter"                        : 1,
 | |
|     "fit"                           : "fill | hidden | meet | slice",
 | |
|     "fit-position"                  : 1,
 | |
|     "float"                         : "left | right | none | inherit",
 | |
|     "float-offset"                  : 1,
 | |
|     "font"                          : 1,
 | |
|     "font-family"                   : 1,
 | |
|     "font-size"                     : "<absolute-size> | <relative-size> | <length> | <percentage> | inherit",
 | |
|     "font-size-adjust"              : "<number> | none | inherit",
 | |
|     "font-stretch"                  : "normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit",
 | |
|     "font-style"                    : "normal | italic | oblique | inherit",
 | |
|     "font-variant"                  : "normal | small-caps | inherit",
 | |
|     "font-weight"                   : "normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit",
 | |
| 
 | |
|     //G
 | |
|     "grid-cell-stacking"            : "columns | rows | layer",
 | |
|     "grid-column"                   : 1,
 | |
|     "grid-columns"                  : 1,
 | |
|     "grid-column-align"             : "start | end | center | stretch",
 | |
|     "grid-column-sizing"            : 1,
 | |
|     "grid-column-span"              : "<integer>",
 | |
|     "grid-flow"                     : "none | rows | columns",
 | |
|     "grid-layer"                    : "<integer>",
 | |
|     "grid-row"                      : 1,
 | |
|     "grid-rows"                     : 1,
 | |
|     "grid-row-align"                : "start | end | center | stretch",
 | |
|     "grid-row-span"                 : "<integer>",
 | |
|     "grid-row-sizing"               : 1,
 | |
| 
 | |
|     //H
 | |
|     "hanging-punctuation"           : 1,
 | |
|     "height"                        : "<margin-width> | inherit",
 | |
|     "hyphenate-after"               : "<integer> | auto",
 | |
|     "hyphenate-before"              : "<integer> | auto",
 | |
|     "hyphenate-character"           : "<string> | auto",
 | |
|     "hyphenate-lines"               : "no-limit | <integer>",
 | |
|     "hyphenate-resource"            : 1,
 | |
|     "hyphens"                       : "none | manual | auto",
 | |
| 
 | |
|     //I
 | |
|     "icon"                          : 1,
 | |
|     "image-orientation"             : "angle | auto",
 | |
|     "image-rendering"               : 1,
 | |
|     "image-resolution"              : 1,
 | |
|     "inline-box-align"              : "initial | last | <integer>",
 | |
| 
 | |
|     //L
 | |
|     "left"                          : "<margin-width> | inherit",
 | |
|     "letter-spacing"                : "<length> | normal | inherit",
 | |
|     "line-height"                   : "<number> | <length> | <percentage> | normal | inherit",
 | |
|     "line-break"                    : "auto | loose | normal | strict",
 | |
|     "line-stacking"                 : 1,
 | |
|     "line-stacking-ruby"            : "exclude-ruby | include-ruby",
 | |
|     "line-stacking-shift"           : "consider-shifts | disregard-shifts",
 | |
|     "line-stacking-strategy"        : "inline-line-height | block-line-height | max-height | grid-height",
 | |
|     "list-style"                    : 1,
 | |
|     "list-style-image"              : "<uri> | none | inherit",
 | |
|     "list-style-position"           : "inside | outside | inherit",
 | |
|     "list-style-type"               : "disc | circle | square | decimal | decimal-leading-zero | lower-roman | upper-roman | lower-greek | lower-latin | upper-latin | armenian | georgian | lower-alpha | upper-alpha | none | inherit",
 | |
| 
 | |
|     //M
 | |
|     "margin"                        : { multi: "<margin-width> | inherit", max: 4 },
 | |
|     "margin-bottom"                 : "<margin-width> | inherit",
 | |
|     "margin-left"                   : "<margin-width> | inherit",
 | |
|     "margin-right"                  : "<margin-width> | inherit",
 | |
|     "margin-top"                    : "<margin-width> | inherit",
 | |
|     "mark"                          : 1,
 | |
|     "mark-after"                    : 1,
 | |
|     "mark-before"                   : 1,
 | |
|     "marks"                         : 1,
 | |
|     "marquee-direction"             : 1,
 | |
|     "marquee-play-count"            : 1,
 | |
|     "marquee-speed"                 : 1,
 | |
|     "marquee-style"                 : 1,
 | |
|     "max-height"                    : "<length> | <percentage> | none | inherit",
 | |
|     "max-width"                     : "<length> | <percentage> | none | inherit",
 | |
|     "min-height"                    : "<length> | <percentage> | inherit",
 | |
|     "min-width"                     : "<length> | <percentage> | inherit",
 | |
|     "move-to"                       : 1,
 | |
| 
 | |
|     //N
 | |
|     "nav-down"                      : 1,
 | |
|     "nav-index"                     : 1,
 | |
|     "nav-left"                      : 1,
 | |
|     "nav-right"                     : 1,
 | |
|     "nav-up"                        : 1,
 | |
| 
 | |
|     //O
 | |
|     "opacity"                       : "<number> | inherit",
 | |
|     "orphans"                       : "<integer> | inherit",
 | |
|     "outline"                       : 1,
 | |
|     "outline-color"                 : "<color> | invert | inherit",
 | |
|     "outline-offset"                : 1,
 | |
|     "outline-style"                 : "<border-style> | inherit",
 | |
|     "outline-width"                 : "<border-width> | inherit",
 | |
|     "overflow"                      : "visible | hidden | scroll | auto | inherit",
 | |
|     "overflow-style"                : 1,
 | |
|     "overflow-x"                    : 1,
 | |
|     "overflow-y"                    : 1,
 | |
| 
 | |
|     //P
 | |
|     "padding"                       : { multi: "<padding-width> | inherit", max: 4 },
 | |
|     "padding-bottom"                : "<padding-width> | inherit",
 | |
|     "padding-left"                  : "<padding-width> | inherit",
 | |
|     "padding-right"                 : "<padding-width> | inherit",
 | |
|     "padding-top"                   : "<padding-width> | inherit",
 | |
|     "page"                          : 1,
 | |
|     "page-break-after"              : "auto | always | avoid | left | right | inherit",
 | |
|     "page-break-before"             : "auto | always | avoid | left | right | inherit",
 | |
|     "page-break-inside"             : "auto | avoid | inherit",
 | |
|     "page-policy"                   : 1,
 | |
|     "pause"                         : 1,
 | |
|     "pause-after"                   : 1,
 | |
|     "pause-before"                  : 1,
 | |
|     "perspective"                   : 1,
 | |
|     "perspective-origin"            : 1,
 | |
|     "phonemes"                      : 1,
 | |
|     "pitch"                         : 1,
 | |
|     "pitch-range"                   : 1,
 | |
|     "play-during"                   : 1,
 | |
|     "position"                      : "static | relative | absolute | fixed | inherit",
 | |
|     "presentation-level"            : 1,
 | |
|     "punctuation-trim"              : 1,
 | |
| 
 | |
|     //Q
 | |
|     "quotes"                        : 1,
 | |
| 
 | |
|     //R
 | |
|     "rendering-intent"              : 1,
 | |
|     "resize"                        : 1,
 | |
|     "rest"                          : 1,
 | |
|     "rest-after"                    : 1,
 | |
|     "rest-before"                   : 1,
 | |
|     "richness"                      : 1,
 | |
|     "right"                         : "<margin-width> | inherit",
 | |
|     "rotation"                      : 1,
 | |
|     "rotation-point"                : 1,
 | |
|     "ruby-align"                    : 1,
 | |
|     "ruby-overhang"                 : 1,
 | |
|     "ruby-position"                 : 1,
 | |
|     "ruby-span"                     : 1,
 | |
| 
 | |
|     //S
 | |
|     "size"                          : 1,
 | |
|     "speak"                         : "normal | none | spell-out | inherit",
 | |
|     "speak-header"                  : "once | always | inherit",
 | |
|     "speak-numeral"                 : "digits | continuous | inherit",
 | |
|     "speak-punctuation"             : "code | none | inherit",
 | |
|     "speech-rate"                   : 1,
 | |
|     "src"                           : 1,
 | |
|     "stress"                        : 1,
 | |
|     "string-set"                    : 1,
 | |
| 
 | |
|     "table-layout"                  : "auto | fixed | inherit",
 | |
|     "tab-size"                      : "<integer> | <length>",
 | |
|     "target"                        : 1,
 | |
|     "target-name"                   : 1,
 | |
|     "target-new"                    : 1,
 | |
|     "target-position"               : 1,
 | |
|     "text-align"                    : "left | right | center | justify | inherit" ,
 | |
|     "text-align-last"               : 1,
 | |
|     "text-decoration"               : 1,
 | |
|     "text-emphasis"                 : 1,
 | |
|     "text-height"                   : 1,
 | |
|     "text-indent"                   : "<length> | <percentage> | inherit",
 | |
|     "text-justify"                  : "auto | none | inter-word | inter-ideograph | inter-cluster | distribute | kashida",
 | |
|     "text-outline"                  : 1,
 | |
|     "text-overflow"                 : 1,
 | |
|     "text-rendering"                : "auto | optimizeSpeed | optimizeLegibility | geometricPrecision | inherit",
 | |
|     "text-shadow"                   : 1,
 | |
|     "text-transform"                : "capitalize | uppercase | lowercase | none | inherit",
 | |
|     "text-wrap"                     : "normal | none | avoid",
 | |
|     "top"                           : "<margin-width> | inherit",
 | |
|     "transform"                     : 1,
 | |
|     "transform-origin"              : 1,
 | |
|     "transform-style"               : 1,
 | |
|     "transition"                    : 1,
 | |
|     "transition-delay"              : 1,
 | |
|     "transition-duration"           : 1,
 | |
|     "transition-property"           : 1,
 | |
|     "transition-timing-function"    : 1,
 | |
| 
 | |
|     //U
 | |
|     "unicode-bidi"                  : "normal | embed | bidi-override | inherit",
 | |
|     "user-modify"                   : "read-only | read-write | write-only | inherit",
 | |
|     "user-select"                   : "none | text | toggle | element | elements | all | inherit",
 | |
| 
 | |
|     //V
 | |
|     "vertical-align"                : "<percentage> | <length> | baseline | sub | super | top | text-top | middle | bottom | text-bottom | inherit",
 | |
|     "visibility"                    : "visible | hidden | collapse | inherit",
 | |
|     "voice-balance"                 : 1,
 | |
|     "voice-duration"                : 1,
 | |
|     "voice-family"                  : 1,
 | |
|     "voice-pitch"                   : 1,
 | |
|     "voice-pitch-range"             : 1,
 | |
|     "voice-rate"                    : 1,
 | |
|     "voice-stress"                  : 1,
 | |
|     "voice-volume"                  : 1,
 | |
|     "volume"                        : 1,
 | |
| 
 | |
|     //W
 | |
|     "white-space"                   : "normal | pre | nowrap | pre-wrap | pre-line | inherit",
 | |
|     "white-space-collapse"          : 1,
 | |
|     "widows"                        : "<integer> | inherit",
 | |
|     "width"                         : "<length> | <percentage> | auto | inherit" ,
 | |
|     "word-break"                    : "normal | keep-all | break-all",
 | |
|     "word-spacing"                  : "<length> | normal | inherit",
 | |
|     "word-wrap"                     : 1,
 | |
| 
 | |
|     //Z
 | |
|     "z-index"                       : "<integer> | auto | inherit",
 | |
|     "zoom"                          : "<number> | <percentage> | normal"
 | |
| };
 | |
| /*global SyntaxUnit, Parser*/
 | |
| /**
 | |
|  * Represents a selector combinator (whitespace, +, >).
 | |
|  * @namespace parserlib.css
 | |
|  * @class PropertyName
 | |
|  * @extends parserlib.util.SyntaxUnit
 | |
|  * @constructor
 | |
|  * @param {String} text The text representation of the unit.
 | |
|  * @param {String} hack The type of IE hack applied ("*", "_", or null).
 | |
|  * @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 PropertyName(text, hack, line, col){
 | |
| 
 | |
|     SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_NAME_TYPE);
 | |
| 
 | |
|     /**
 | |
|      * The type of IE hack applied ("*", "_", or null).
 | |
|      * @type String
 | |
|      * @property hack
 | |
|      */
 | |
|     this.hack = hack;
 | |
| 
 | |
| }
 | |
| 
 | |
| PropertyName.prototype = new SyntaxUnit();
 | |
| PropertyName.prototype.constructor = PropertyName;
 | |
| PropertyName.prototype.toString = function(){
 | |
|     return (this.hack ? this.hack : "") + this.text;
 | |
| };
 | |
| 
 | |
| /*global SyntaxUnit, Parser*/
 | |
| /**
 | |
|  * Represents a single part of a CSS property value, meaning that it represents
 | |
|  * just everything single part between ":" and ";". If there are multiple values
 | |
|  * separated by commas, this type represents just one of the values.
 | |
|  * @param {String[]} parts An array of value parts making up this value.
 | |
|  * @param {int} line The line of text on which the unit resides.
 | |
|  * @param {int} col The column of text on which the unit resides.
 | |
|  * @namespace parserlib.css
 | |
|  * @class PropertyValue
 | |
|  * @extends parserlib.util.SyntaxUnit
 | |
|  * @constructor
 | |
|  */
 | |
| function PropertyValue(parts, line, col){
 | |
| 
 | |
|     SyntaxUnit.call(this, parts.join(" "), line, col, Parser.PROPERTY_VALUE_TYPE);
 | |
| 
 | |
|     /**
 | |
|      * The parts that make up the selector.
 | |
|      * @type Array
 | |
|      * @property parts
 | |
|      */
 | |
|     this.parts = parts;
 | |
| 
 | |
| }
 | |
| 
 | |
| PropertyValue.prototype = new SyntaxUnit();
 | |
| PropertyValue.prototype.constructor = PropertyValue;
 | |
| 
 | |
| 
 | |
| /*global SyntaxUnit, Parser*/
 | |
| /**
 | |
|  * A utility class that allows for easy iteration over the various parts of a
 | |
|  * property value.
 | |
|  * @param {parserlib.css.PropertyValue} value The property value to iterate over.
 | |
|  * @namespace parserlib.css
 | |
|  * @class PropertyValueIterator
 | |
|  * @constructor
 | |
|  */
 | |
| function PropertyValueIterator(value){
 | |
| 
 | |
|     /**
 | |
|      * Iterator value
 | |
|      * @type int
 | |
|      * @property _i
 | |
|      * @private
 | |
|      */
 | |
|     this._i = 0;
 | |
| 
 | |
|     /**
 | |
|      * The parts that make up the value.
 | |
|      * @type Array
 | |
|      * @property _parts
 | |
|      * @private
 | |
|      */
 | |
|     this._parts = value.parts;
 | |
| 
 | |
|     /**
 | |
|      * Keeps track of bookmarks along the way.
 | |
|      * @type Array
 | |
|      * @property _marks
 | |
|      * @private
 | |
|      */
 | |
|     this._marks = [];
 | |
| 
 | |
|     /**
 | |
|      * Holds the original property value.
 | |
|      * @type parserlib.css.PropertyValue
 | |
|      * @property value
 | |
|      */
 | |
|     this.value = value;
 | |
| 
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns the total number of parts in the value.
 | |
|  * @return {int} The total number of parts in the value.
 | |
|  * @method count
 | |
|  */
 | |
| PropertyValueIterator.prototype.count = function(){
 | |
|     return this._parts.length;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Indicates if the iterator is positioned at the first item.
 | |
|  * @return {Boolean} True if positioned at first item, false if not.
 | |
|  * @method isFirst
 | |
|  */
 | |
| PropertyValueIterator.prototype.isFirst = function(){
 | |
|     return this._i === 0;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Indicates if there are more parts of the property value.
 | |
|  * @return {Boolean} True if there are more parts, false if not.
 | |
|  * @method hasNext
 | |
|  */
 | |
| PropertyValueIterator.prototype.hasNext = function(){
 | |
|     return (this._i < this._parts.length);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Marks the current spot in the iteration so it can be restored to
 | |
|  * later on.
 | |
|  * @return {void}
 | |
|  * @method mark
 | |
|  */
 | |
| PropertyValueIterator.prototype.mark = function(){
 | |
|     this._marks.push(this._i);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Returns the next part of the property value or null if there is no next
 | |
|  * part. Does not move the internal counter forward.
 | |
|  * @return {parserlib.css.PropertyValuePart} The next part of the property value or null if there is no next
 | |
|  * part.
 | |
|  * @method peek
 | |
|  */
 | |
| PropertyValueIterator.prototype.peek = function(count){
 | |
|     return this.hasNext() ? this._parts[this._i + (count || 0)] : null;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Returns the next part of the property value or null if there is no next
 | |
|  * part.
 | |
|  * @return {parserlib.css.PropertyValuePart} The next part of the property value or null if there is no next
 | |
|  * part.
 | |
|  * @method next
 | |
|  */
 | |
| PropertyValueIterator.prototype.next = function(){
 | |
|     return this.hasNext() ? this._parts[this._i++] : null;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Returns the previous part of the property value or null if there is no
 | |
|  * previous part.
 | |
|  * @return {parserlib.css.PropertyValuePart} The previous part of the
 | |
|  * property value or null if there is no next part.
 | |
|  * @method previous
 | |
|  */
 | |
| PropertyValueIterator.prototype.previous = function(){
 | |
|     return this._i > 0 ? this._parts[--this._i] : null;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Restores the last saved bookmark.
 | |
|  * @return {void}
 | |
|  * @method restore
 | |
|  */
 | |
| PropertyValueIterator.prototype.restore = function(){
 | |
|     if (this._marks.length){
 | |
|         this._i = this._marks.pop();
 | |
|     }
 | |
| };
 | |
| 
 | |
| 
 | |
| /*global SyntaxUnit, Parser, Colors*/
 | |
| /**
 | |
|  * Represents a single part of a CSS property value, meaning that it represents
 | |
|  * just one part of the data between ":" and ";".
 | |
|  * @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.
 | |
|  * @namespace parserlib.css
 | |
|  * @class PropertyValuePart
 | |
|  * @extends parserlib.util.SyntaxUnit
 | |
|  * @constructor
 | |
|  */
 | |
| function PropertyValuePart(text, line, col){
 | |
| 
 | |
|     SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_VALUE_PART_TYPE);
 | |
| 
 | |
|     /**
 | |
|      * Indicates the type of value unit.
 | |
|      * @type String
 | |
|      * @property type
 | |
|      */
 | |
|     this.type = "unknown";
 | |
| 
 | |
|     //figure out what type of data it is
 | |
| 
 | |
|     var temp;
 | |
| 
 | |
|     //it is a measurement?
 | |
|     if (/^([+\-]?[\d\.]+)([a-z]+)$/i.test(text)){  //dimension
 | |
|         this.type = "dimension";
 | |
|         this.value = +RegExp.$1;
 | |
|         this.units = RegExp.$2;
 | |
| 
 | |
|         //try to narrow down
 | |
|         switch(this.units.toLowerCase()){
 | |
| 
 | |
|             case "em":
 | |
|             case "rem":
 | |
|             case "ex":
 | |
|             case "px":
 | |
|             case "cm":
 | |
|             case "mm":
 | |
|             case "in":
 | |
|             case "pt":
 | |
|             case "pc":
 | |
|             case "ch":
 | |
|                 this.type = "length";
 | |
|                 break;
 | |
| 
 | |
|             case "deg":
 | |
|             case "rad":
 | |
|             case "grad":
 | |
|                 this.type = "angle";
 | |
|                 break;
 | |
| 
 | |
|             case "ms":
 | |
|             case "s":
 | |
|                 this.type = "time";
 | |
|                 break;
 | |
| 
 | |
|             case "hz":
 | |
|             case "khz":
 | |
|                 this.type = "frequency";
 | |
|                 break;
 | |
| 
 | |
|             case "dpi":
 | |
|             case "dpcm":
 | |
|                 this.type = "resolution";
 | |
|                 break;
 | |
| 
 | |
|             //default
 | |
| 
 | |
|         }
 | |
| 
 | |
|     } else if (/^([+\-]?[\d\.]+)%$/i.test(text)){  //percentage
 | |
|         this.type = "percentage";
 | |
|         this.value = +RegExp.$1;
 | |
|     } else if (/^([+\-]?[\d\.]+)%$/i.test(text)){  //percentage
 | |
|         this.type = "percentage";
 | |
|         this.value = +RegExp.$1;
 | |
|     } else if (/^([+\-]?\d+)$/i.test(text)){  //integer
 | |
|         this.type = "integer";
 | |
|         this.value = +RegExp.$1;
 | |
|     } else if (/^([+\-]?[\d\.]+)$/i.test(text)){  //number
 | |
|         this.type = "number";
 | |
|         this.value = +RegExp.$1;
 | |
| 
 | |
|     } else if (/^#([a-f0-9]{3,6})/i.test(text)){  //hexcolor
 | |
|         this.type = "color";
 | |
|         temp = RegExp.$1;
 | |
|         if (temp.length == 3){
 | |
|             this.red    = parseInt(temp.charAt(0)+temp.charAt(0),16);
 | |
|             this.green  = parseInt(temp.charAt(1)+temp.charAt(1),16);
 | |
|             this.blue   = parseInt(temp.charAt(2)+temp.charAt(2),16);
 | |
|         } else {
 | |
|             this.red    = parseInt(temp.substring(0,2),16);
 | |
|             this.green  = parseInt(temp.substring(2,4),16);
 | |
|             this.blue   = parseInt(temp.substring(4,6),16);
 | |
|         }
 | |
|     } else if (/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i.test(text)){ //rgb() color with absolute numbers
 | |
|         this.type   = "color";
 | |
|         this.red    = +RegExp.$1;
 | |
|         this.green  = +RegExp.$2;
 | |
|         this.blue   = +RegExp.$3;
 | |
|     } else if (/^rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)){ //rgb() color with percentages
 | |
|         this.type   = "color";
 | |
|         this.red    = +RegExp.$1 * 255 / 100;
 | |
|         this.green  = +RegExp.$2 * 255 / 100;
 | |
|         this.blue   = +RegExp.$3 * 255 / 100;
 | |
|     } else if (/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //rgba() color with absolute numbers
 | |
|         this.type   = "color";
 | |
|         this.red    = +RegExp.$1;
 | |
|         this.green  = +RegExp.$2;
 | |
|         this.blue   = +RegExp.$3;
 | |
|         this.alpha  = +RegExp.$4;
 | |
|     } else if (/^rgba\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //rgba() color with percentages
 | |
|         this.type   = "color";
 | |
|         this.red    = +RegExp.$1 * 255 / 100;
 | |
|         this.green  = +RegExp.$2 * 255 / 100;
 | |
|         this.blue   = +RegExp.$3 * 255 / 100;
 | |
|         this.alpha  = +RegExp.$4;
 | |
|     } else if (/^hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)){ //hsl()
 | |
|         this.type   = "color";
 | |
|         this.hue    = +RegExp.$1;
 | |
|         this.saturation = +RegExp.$2 / 100;
 | |
|         this.lightness  = +RegExp.$3 / 100;
 | |
|     } else if (/^hsla\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //hsla() color with percentages
 | |
|         this.type   = "color";
 | |
|         this.hue    = +RegExp.$1;
 | |
|         this.saturation = +RegExp.$2 / 100;
 | |
|         this.lightness  = +RegExp.$3 / 100;
 | |
|         this.alpha  = +RegExp.$4;
 | |
|     } else if (/^url\(["']?([^\)"']+)["']?\)/i.test(text)){ //URI
 | |
|         this.type   = "uri";
 | |
|         this.uri    = RegExp.$1;
 | |
|     } else if (/^([^\(]+)\(/i.test(text)){
 | |
|         this.type   = "function";
 | |
|         this.name   = RegExp.$1;
 | |
|         this.value  = text;
 | |
|     } else if (/^["'][^"']*["']/.test(text)){    //string
 | |
|         this.type   = "string";
 | |
|         this.value  = eval(text);
 | |
|     } else if (Colors[text.toLowerCase()]){  //named color
 | |
|         this.type   = "color";
 | |
|         temp        = Colors[text.toLowerCase()].substring(1);
 | |
|         this.red    = parseInt(temp.substring(0,2),16);
 | |
|         this.green  = parseInt(temp.substring(2,4),16);
 | |
|         this.blue   = parseInt(temp.substring(4,6),16);
 | |
|     } else if (/^[\,\/]$/.test(text)){
 | |
|         this.type   = "operator";
 | |
|         this.value  = text;
 | |
|     } else if (/^[a-z\-\u0080-\uFFFF][a-z0-9\-\u0080-\uFFFF]*$/i.test(text)){
 | |
|         this.type   = "identifier";
 | |
|         this.value  = text;
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| PropertyValuePart.prototype = new SyntaxUnit();
 | |
| PropertyValuePart.prototype.constructor = PropertyValuePart;
 | |
| 
 | |
| /**
 | |
|  * 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.css.PropertyValuePart} The object representing the token.
 | |
|  * @static
 | |
|  * @method fromToken
 | |
|  */
 | |
| PropertyValuePart.fromToken = function(token){
 | |
|     return new PropertyValuePart(token.value, token.startLine, token.startCol);
 | |
| };
 | |
| var Pseudos = {
 | |
|     ":first-letter": 1,
 | |
|     ":first-line":   1,
 | |
|     ":before":       1,
 | |
|     ":after":        1
 | |
| };
 | |
| 
 | |
| Pseudos.ELEMENT = 1;
 | |
| Pseudos.CLASS = 2;
 | |
| 
 | |
| Pseudos.isElement = function(pseudo){
 | |
|     return pseudo.indexOf("::") === 0 || Pseudos[pseudo.toLowerCase()] == Pseudos.ELEMENT;
 | |
| };
 | |
| /*global SyntaxUnit, Parser, Specificity*/
 | |
| /**
 | |
|  * Represents an entire single selector, including all parts but not
 | |
|  * including multiple selectors (those separated by commas).
 | |
|  * @namespace parserlib.css
 | |
|  * @class Selector
 | |
|  * @extends parserlib.util.SyntaxUnit
 | |
|  * @constructor
 | |
|  * @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 Selector(parts, line, col){
 | |
| 
 | |
|     SyntaxUnit.call(this, parts.join(" "), line, col, Parser.SELECTOR_TYPE);
 | |
| 
 | |
|     /**
 | |
|      * The parts that make up the selector.
 | |
|      * @type Array
 | |
|      * @property parts
 | |
|      */
 | |
|     this.parts = parts;
 | |
| 
 | |
|     /**
 | |
|      * The specificity of the selector.
 | |
|      * @type parserlib.css.Specificity
 | |
|      * @property specificity
 | |
|      */
 | |
|     this.specificity = Specificity.calculate(this);
 | |
| 
 | |
| }
 | |
| 
 | |
| Selector.prototype = new SyntaxUnit();
 | |
| Selector.prototype.constructor = Selector;
 | |
| 
 | |
| 
 | |
| /*global SyntaxUnit, Parser*/
 | |
| /**
 | |
|  * Represents a single part of a selector string, meaning a single set of
 | |
|  * element name and modifiers. This does not include combinators such as
 | |
|  * spaces, +, >, etc.
 | |
|  * @namespace parserlib.css
 | |
|  * @class SelectorPart
 | |
|  * @extends parserlib.util.SyntaxUnit
 | |
|  * @constructor
 | |
|  * @param {String} elementName The element name in the selector or null
 | |
|  *      if there is no element name.
 | |
|  * @param {Array} modifiers Array of individual modifiers for the element.
 | |
|  *      May be empty if there are none.
 | |
|  * @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 SelectorPart(elementName, modifiers, text, line, col){
 | |
| 
 | |
|     SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_PART_TYPE);
 | |
| 
 | |
|     /**
 | |
|      * The tag name of the element to which this part
 | |
|      * of the selector affects.
 | |
|      * @type String
 | |
|      * @property elementName
 | |
|      */
 | |
|     this.elementName = elementName;
 | |
| 
 | |
|     /**
 | |
|      * The parts that come after the element name, such as class names, IDs,
 | |
|      * pseudo classes/elements, etc.
 | |
|      * @type Array
 | |
|      * @property modifiers
 | |
|      */
 | |
|     this.modifiers = modifiers;
 | |
| 
 | |
| }
 | |
| 
 | |
| SelectorPart.prototype = new SyntaxUnit();
 | |
| SelectorPart.prototype.constructor = SelectorPart;
 | |
| 
 | |
| 
 | |
| /*global SyntaxUnit, Parser*/
 | |
| /**
 | |
|  * Represents a selector modifier string, meaning a class name, element name,
 | |
|  * element ID, pseudo rule, etc.
 | |
|  * @namespace parserlib.css
 | |
|  * @class SelectorSubPart
 | |
|  * @extends parserlib.util.SyntaxUnit
 | |
|  * @constructor
 | |
|  * @param {String} text The text representation of the unit.
 | |
|  * @param {String} type The type of selector modifier.
 | |
|  * @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 SelectorSubPart(text, type, line, col){
 | |
| 
 | |
|     SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_SUB_PART_TYPE);
 | |
| 
 | |
|     /**
 | |
|      * The type of modifier.
 | |
|      * @type String
 | |
|      * @property type
 | |
|      */
 | |
|     this.type = type;
 | |
| 
 | |
|     /**
 | |
|      * Some subparts have arguments, this represents them.
 | |
|      * @type Array
 | |
|      * @property args
 | |
|      */
 | |
|     this.args = [];
 | |
| 
 | |
| }
 | |
| 
 | |
| SelectorSubPart.prototype = new SyntaxUnit();
 | |
| SelectorSubPart.prototype.constructor = SelectorSubPart;
 | |
| 
 | |
| 
 | |
| /*global Pseudos, SelectorPart*/
 | |
| /**
 | |
|  * Represents a selector's specificity.
 | |
|  * @namespace parserlib.css
 | |
|  * @class Specificity
 | |
|  * @constructor
 | |
|  * @param {int} a Should be 1 for inline styles, zero for stylesheet styles
 | |
|  * @param {int} b Number of ID selectors
 | |
|  * @param {int} c Number of classes and pseudo classes
 | |
|  * @param {int} d Number of element names and pseudo elements
 | |
|  */
 | |
| function Specificity(a, b, c, d){
 | |
|     this.a = a;
 | |
|     this.b = b;
 | |
|     this.c = c;
 | |
|     this.d = d;
 | |
| }
 | |
| 
 | |
| Specificity.prototype = {
 | |
|     constructor: Specificity,
 | |
| 
 | |
|     /**
 | |
|      * Compare this specificity to another.
 | |
|      * @param {Specificity} other The other specificity to compare to.
 | |
|      * @return {int} -1 if the other specificity is larger, 1 if smaller, 0 if equal.
 | |
|      * @method compare
 | |
|      */
 | |
|     compare: function(other){
 | |
|         var comps = ["a", "b", "c", "d"],
 | |
|             i, len;
 | |
| 
 | |
|         for (i=0, len=comps.length; i < len; i++){
 | |
|             if (this[comps[i]] < other[comps[i]]){
 | |
|                 return -1;
 | |
|             } else if (this[comps[i]] > other[comps[i]]){
 | |
|                 return 1;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return 0;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Creates a numeric value for the specificity.
 | |
|      * @return {int} The numeric value for the specificity.
 | |
|      * @method valueOf
 | |
|      */
 | |
|     valueOf: function(){
 | |
|         return (this.a * 1000) + (this.b * 100) + (this.c * 10) + this.d;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Returns a string representation for specificity.
 | |
|      * @return {String} The string representation of specificity.
 | |
|      * @method toString
 | |
|      */
 | |
|     toString: function(){
 | |
|         return this.a + "," + this.b + "," + this.c + "," + this.d;
 | |
|     }
 | |
| 
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Calculates the specificity of the given selector.
 | |
|  * @param {parserlib.css.Selector} The selector to calculate specificity for.
 | |
|  * @return {parserlib.css.Specificity} The specificity of the selector.
 | |
|  * @static
 | |
|  * @method calculate
 | |
|  */
 | |
| Specificity.calculate = function(selector){
 | |
| 
 | |
|     var i, len,
 | |
|         part,
 | |
|         b=0, c=0, d=0;
 | |
| 
 | |
|     function updateValues(part){
 | |
| 
 | |
|         var i, j, len, num,
 | |
|             elementName = part.elementName ? part.elementName.text : "",
 | |
|             modifier;
 | |
| 
 | |
|         if (elementName && elementName.charAt(elementName.length-1) != "*") {
 | |
|             d++;
 | |
|         }
 | |
| 
 | |
|         for (i=0, len=part.modifiers.length; i < len; i++){
 | |
|             modifier = part.modifiers[i];
 | |
|             switch(modifier.type){
 | |
|                 case "class":
 | |
|                 case "attribute":
 | |
|                     c++;
 | |
|                     break;
 | |
| 
 | |
|                 case "id":
 | |
|                     b++;
 | |
|                     break;
 | |
| 
 | |
|                 case "pseudo":
 | |
|                     if (Pseudos.isElement(modifier.text)){
 | |
|                         d++;
 | |
|                     } else {
 | |
|                         c++;
 | |
|                     }
 | |
|                     break;
 | |
| 
 | |
|                 case "not":
 | |
|                     for (j=0, num=modifier.args.length; j < num; j++){
 | |
|                         updateValues(modifier.args[j]);
 | |
|                     }
 | |
|             }
 | |
|          }
 | |
|     }
 | |
| 
 | |
|     for (i=0, len=selector.parts.length; i < len; i++){
 | |
|         part = selector.parts[i];
 | |
| 
 | |
|         if (part instanceof SelectorPart){
 | |
|             updateValues(part);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return new Specificity(0, b, c, d);
 | |
| };
 | |
| 
 | |
| /*global Tokens, TokenStreamBase*/
 | |
| 
 | |
| var h = /^[0-9a-fA-F]$/,
 | |
|     nonascii = /^[\u0080-\uFFFF]$/,
 | |
|     nl = /\n|\r\n|\r|\f/;
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // Helper functions
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| 
 | |
| function isHexDigit(c){
 | |
|     return c !== null && h.test(c);
 | |
| }
 | |
| 
 | |
| function isDigit(c){
 | |
|     return c !== null && /\d/.test(c);
 | |
| }
 | |
| 
 | |
| function isWhitespace(c){
 | |
|     return c !== null && /\s/.test(c);
 | |
| }
 | |
| 
 | |
| function isNewLine(c){
 | |
|     return c !== null && nl.test(c);
 | |
| }
 | |
| 
 | |
| function isNameStart(c){
 | |
|     return c !== null && (/[a-z_\u0080-\uFFFF\\]/i.test(c));
 | |
| }
 | |
| 
 | |
| function isNameChar(c){
 | |
|     return c !== null && (isNameStart(c) || /[0-9\-\\]/.test(c));
 | |
| }
 | |
| 
 | |
| function isIdentStart(c){
 | |
|     return c !== null && (isNameStart(c) || /\-\\/.test(c));
 | |
| }
 | |
| 
 | |
| function mix(receiver, supplier){
 | |
|     for (var prop in supplier){
 | |
|         if (supplier.hasOwnProperty(prop)){
 | |
|             receiver[prop] = supplier[prop];
 | |
|         }
 | |
|     }
 | |
|     return receiver;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // CSS Token Stream
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * A token stream that produces CSS tokens.
 | |
|  * @param {String|Reader} input The source of text to tokenize.
 | |
|  * @constructor
 | |
|  * @class TokenStream
 | |
|  * @namespace parserlib.css
 | |
|  */
 | |
| function TokenStream(input){
 | |
|     TokenStreamBase.call(this, input, Tokens);
 | |
| }
 | |
| 
 | |
| TokenStream.prototype = mix(new TokenStreamBase(), {
 | |
| 
 | |
|     /**
 | |
|      * Overrides the TokenStreamBase method of the same name
 | |
|      * to produce CSS tokens.
 | |
|      * @param {variant} channel The name of the channel to use
 | |
|      *      for the next token.
 | |
|      * @return {Object} A token object representing the next token.
 | |
|      * @method _getToken
 | |
|      * @private
 | |
|      */
 | |
|     _getToken: function(channel){
 | |
| 
 | |
|         var c,
 | |
|             reader = this._reader,
 | |
|             token   = null,
 | |
|             startLine   = reader.getLine(),
 | |
|             startCol    = reader.getCol();
 | |
| 
 | |
|         c = reader.read();
 | |
| 
 | |
| 
 | |
|         while(c){
 | |
|             switch(c){
 | |
| 
 | |
|                 /*
 | |
|                  * Potential tokens:
 | |
|                  * - COMMENT
 | |
|                  * - SLASH
 | |
|                  * - CHAR
 | |
|                  */
 | |
|                 case "/":
 | |
| 
 | |
|                     if(reader.peek() == "*"){
 | |
|                         token = this.commentToken(c, startLine, startCol);
 | |
|                     } else {
 | |
|                         token = this.charToken(c, startLine, startCol);
 | |
|                     }
 | |
|                     break;
 | |
| 
 | |
|                 /*
 | |
|                  * Potential tokens:
 | |
|                  * - DASHMATCH
 | |
|                  * - INCLUDES
 | |
|                  * - PREFIXMATCH
 | |
|                  * - SUFFIXMATCH
 | |
|                  * - SUBSTRINGMATCH
 | |
|                  * - CHAR
 | |
|                  */
 | |
|                 case "|":
 | |
|                 case "~":
 | |
|                 case "^":
 | |
|                 case "$":
 | |
|                 case "*":
 | |
|                     if(reader.peek() == "="){
 | |
|                         token = this.comparisonToken(c, startLine, startCol);
 | |
|                     } else {
 | |
|                         token = this.charToken(c, startLine, startCol);
 | |
|                     }
 | |
|                     break;
 | |
| 
 | |
|                 /*
 | |
|                  * Potential tokens:
 | |
|                  * - STRING
 | |
|                  * - INVALID
 | |
|                  */
 | |
|                 case "\"":
 | |
|                 case "'":
 | |
|                     token = this.stringToken(c, startLine, startCol);
 | |
|                     break;
 | |
| 
 | |
|                 /*
 | |
|                  * Potential tokens:
 | |
|                  * - HASH
 | |
|                  * - CHAR
 | |
|                  */
 | |
|                 case "#":
 | |
|                     if (isNameChar(reader.peek())){
 | |
|                         token = this.hashToken(c, startLine, startCol);
 | |
|                     } else {
 | |
|                         token = this.charToken(c, startLine, startCol);
 | |
|                     }
 | |
|                     break;
 | |
| 
 | |
|                 /*
 | |
|                  * Potential tokens:
 | |
|                  * - DOT
 | |
|                  * - NUMBER
 | |
|                  * - DIMENSION
 | |
|                  * - PERCENTAGE
 | |
|                  */
 | |
|                 case ".":
 | |
|                     if (isDigit(reader.peek())){
 | |
|                         token = this.numberToken(c, startLine, startCol);
 | |
|                     } else {
 | |
|                         token = this.charToken(c, startLine, startCol);
 | |
|                     }
 | |
|                     break;
 | |
| 
 | |
|                 /*
 | |
|                  * Potential tokens:
 | |
|                  * - CDC
 | |
|                  * - MINUS
 | |
|                  * - NUMBER
 | |
|                  * - DIMENSION
 | |
|                  * - PERCENTAGE
 | |
|                  */
 | |
|                 case "-":
 | |
|                     if (reader.peek() == "-"){  //could be closing HTML-style comment
 | |
|                         token = this.htmlCommentEndToken(c, startLine, startCol);
 | |
|                     } else if (isNameStart(reader.peek())){
 | |
|                         token = this.identOrFunctionToken(c, startLine, startCol);
 | |
|                     } else {
 | |
|                         token = this.charToken(c, startLine, startCol);
 | |
|                     }
 | |
|                     break;
 | |
| 
 | |
|                 /*
 | |
|                  * Potential tokens:
 | |
|                  * - IMPORTANT_SYM
 | |
|                  * - CHAR
 | |
|                  */
 | |
|                 case "!":
 | |
|                     token = this.importantToken(c, startLine, startCol);
 | |
|                     break;
 | |
| 
 | |
|                 /*
 | |
|                  * Any at-keyword or CHAR
 | |
|                  */
 | |
|                 case "@":
 | |
|                     token = this.atRuleToken(c, startLine, startCol);
 | |
|                     break;
 | |
| 
 | |
|                 /*
 | |
|                  * Potential tokens:
 | |
|                  * - NOT
 | |
|                  * - CHAR
 | |
|                  */
 | |
|                 case ":":
 | |
|                     token = this.notToken(c, startLine, startCol);
 | |
|                     break;
 | |
| 
 | |
|                 /*
 | |
|                  * Potential tokens:
 | |
|                  * - CDO
 | |
|                  * - CHAR
 | |
|                  */
 | |
|                 case "<":
 | |
|                     token = this.htmlCommentStartToken(c, startLine, startCol);
 | |
|                     break;
 | |
| 
 | |
|                 /*
 | |
|                  * Potential tokens:
 | |
|                  * - UNICODE_RANGE
 | |
|                  * - URL
 | |
|                  * - CHAR
 | |
|                  */
 | |
|                 case "U":
 | |
|                 case "u":
 | |
|                     if (reader.peek() == "+"){
 | |
|                         token = this.unicodeRangeToken(c, startLine, startCol);
 | |
|                         break;
 | |
|                     }
 | |
|                     /* falls through */
 | |
|                 default:
 | |
| 
 | |
|                     /*
 | |
|                      * Potential tokens:
 | |
|                      * - NUMBER
 | |
|                      * - DIMENSION
 | |
|                      * - LENGTH
 | |
|                      * - FREQ
 | |
|                      * - TIME
 | |
|                      * - EMS
 | |
|                      * - EXS
 | |
|                      * - ANGLE
 | |
|                      */
 | |
|                     if (isDigit(c)){
 | |
|                         token = this.numberToken(c, startLine, startCol);
 | |
|                     } else
 | |
| 
 | |
|                     /*
 | |
|                      * Potential tokens:
 | |
|                      * - S
 | |
|                      */
 | |
|                     if (isWhitespace(c)){
 | |
|                         token = this.whitespaceToken(c, startLine, startCol);
 | |
|                     } else
 | |
| 
 | |
|                     /*
 | |
|                      * Potential tokens:
 | |
|                      * - IDENT
 | |
|                      */
 | |
|                     if (isIdentStart(c)){
 | |
|                         token = this.identOrFunctionToken(c, startLine, startCol);
 | |
|                     } else
 | |
| 
 | |
|                     /*
 | |
|                      * Potential tokens:
 | |
|                      * - CHAR
 | |
|                      * - PLUS
 | |
|                      */
 | |
|                     {
 | |
|                         token = this.charToken(c, startLine, startCol);
 | |
|                     }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
|             }
 | |
| 
 | |
|             //make sure this token is wanted
 | |
|             //TODO: check channel
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         if (!token && c === null){
 | |
|             token = this.createToken(Tokens.EOF,null,startLine,startCol);
 | |
|         }
 | |
| 
 | |
|         return token;
 | |
|     },
 | |
| 
 | |
|     //-------------------------------------------------------------------------
 | |
|     // Methods to create tokens
 | |
|     //-------------------------------------------------------------------------
 | |
| 
 | |
|     /**
 | |
|      * Produces a token based on available data and the current
 | |
|      * reader position information. This method is called by other
 | |
|      * private methods to create tokens and is never called directly.
 | |
|      * @param {int} tt The token type.
 | |
|      * @param {String} value The text value of the token.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @param {Object} options (Optional) Specifies a channel property
 | |
|      *      to indicate that a different channel should be scanned
 | |
|      *      and/or a hide property indicating that the token should
 | |
|      *      be hidden.
 | |
|      * @return {Object} A token object.
 | |
|      * @method createToken
 | |
|      */
 | |
|     createToken: function(tt, value, startLine, startCol, options){
 | |
|         var reader = this._reader;
 | |
|         options = options || {};
 | |
| 
 | |
|         return {
 | |
|             value:      value,
 | |
|             type:       tt,
 | |
|             channel:    options.channel,
 | |
|             hide:       options.hide || false,
 | |
|             startLine:  startLine,
 | |
|             startCol:   startCol,
 | |
|             endLine:    reader.getLine(),
 | |
|             endCol:     reader.getCol()
 | |
|         };
 | |
|     },
 | |
| 
 | |
|     //-------------------------------------------------------------------------
 | |
|     // Methods to create specific tokens
 | |
|     //-------------------------------------------------------------------------
 | |
| 
 | |
|     /**
 | |
|      * Produces a token for any at-rule. If the at-rule is unknown, then
 | |
|      * the token is for a single "@" character.
 | |
|      * @param {String} first The first character for the token.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @return {Object} A token object.
 | |
|      * @method atRuleToken
 | |
|      */
 | |
|     atRuleToken: function(first, startLine, startCol){
 | |
|         var rule    = first,
 | |
|             reader  = this._reader,
 | |
|             tt      = Tokens.CHAR,
 | |
|             valid   = false,
 | |
|             ident,
 | |
|             c;
 | |
| 
 | |
|         /*
 | |
|          * First, mark where we are. There are only four @ rules,
 | |
|          * so anything else is really just an invalid token.
 | |
|          * Basically, if this doesn't match one of the known @
 | |
|          * rules, just return '@' as an unknown token and allow
 | |
|          * parsing to continue after that point.
 | |
|          */
 | |
|         reader.mark();
 | |
| 
 | |
|         //try to find the at-keyword
 | |
|         ident = this.readName();
 | |
|         rule = first + ident;
 | |
|         tt = Tokens.type(rule.toLowerCase());
 | |
| 
 | |
|         //if it's not valid, use the first character only and reset the reader
 | |
|         if (tt == Tokens.CHAR || tt == Tokens.UNKNOWN){
 | |
|             if (rule.length > 1){
 | |
|                 tt = Tokens.UNKNOWN_SYM;
 | |
|             } else {
 | |
|                 tt = Tokens.CHAR;
 | |
|                 rule = first;
 | |
|                 reader.reset();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return this.createToken(tt, rule, startLine, startCol);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Produces a character token based on the given character
 | |
|      * and location in the stream. If there's a special (non-standard)
 | |
|      * token name, this is used; otherwise CHAR is used.
 | |
|      * @param {String} c The character for the token.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @return {Object} A token object.
 | |
|      * @method charToken
 | |
|      */
 | |
|     charToken: function(c, startLine, startCol){
 | |
|         var tt = Tokens.type(c);
 | |
| 
 | |
|         if (tt == -1){
 | |
|             tt = Tokens.CHAR;
 | |
|         }
 | |
| 
 | |
|         return this.createToken(tt, c, startLine, startCol);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Produces a character token based on the given character
 | |
|      * and location in the stream. If there's a special (non-standard)
 | |
|      * token name, this is used; otherwise CHAR is used.
 | |
|      * @param {String} first The first character for the token.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @return {Object} A token object.
 | |
|      * @method commentToken
 | |
|      */
 | |
|     commentToken: function(first, startLine, startCol){
 | |
|         var reader  = this._reader,
 | |
|             comment = this.readComment(first);
 | |
| 
 | |
|         return this.createToken(Tokens.COMMENT, comment, startLine, startCol);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Produces a comparison token based on the given character
 | |
|      * and location in the stream. The next character must be
 | |
|      * read and is already known to be an equals sign.
 | |
|      * @param {String} c The character for the token.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @return {Object} A token object.
 | |
|      * @method comparisonToken
 | |
|      */
 | |
|     comparisonToken: function(c, startLine, startCol){
 | |
|         var reader  = this._reader,
 | |
|             comparison  = c + reader.read(),
 | |
|             tt      = Tokens.type(comparison) || Tokens.CHAR;
 | |
| 
 | |
|         return this.createToken(tt, comparison, startLine, startCol);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Produces a hash token based on the specified information. The
 | |
|      * first character provided is the pound sign (#) and then this
 | |
|      * method reads a name afterward.
 | |
|      * @param {String} first The first character (#) in the hash name.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @return {Object} A token object.
 | |
|      * @method hashToken
 | |
|      */
 | |
|     hashToken: function(first, startLine, startCol){
 | |
|         var reader  = this._reader,
 | |
|             name    = this.readName(first);
 | |
| 
 | |
|         return this.createToken(Tokens.HASH, name, startLine, startCol);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Produces a CDO or CHAR token based on the specified information. The
 | |
|      * first character is provided and the rest is read by the function to determine
 | |
|      * the correct token to create.
 | |
|      * @param {String} first The first character in the token.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @return {Object} A token object.
 | |
|      * @method htmlCommentStartToken
 | |
|      */
 | |
|     htmlCommentStartToken: function(first, startLine, startCol){
 | |
|         var reader      = this._reader,
 | |
|             text        = first;
 | |
| 
 | |
|         reader.mark();
 | |
|         text += reader.readCount(3);
 | |
| 
 | |
|         if (text == "<!--"){
 | |
|             return this.createToken(Tokens.CDO, text, startLine, startCol);
 | |
|         } else {
 | |
|             reader.reset();
 | |
|             return this.charToken(first, startLine, startCol);
 | |
|         }
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Produces a CDC or CHAR token based on the specified information. The
 | |
|      * first character is provided and the rest is read by the function to determine
 | |
|      * the correct token to create.
 | |
|      * @param {String} first The first character in the token.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @return {Object} A token object.
 | |
|      * @method htmlCommentEndToken
 | |
|      */
 | |
|     htmlCommentEndToken: function(first, startLine, startCol){
 | |
|         var reader      = this._reader,
 | |
|             text        = first;
 | |
| 
 | |
|         reader.mark();
 | |
|         text += reader.readCount(2);
 | |
| 
 | |
|         if (text == "-->"){
 | |
|             return this.createToken(Tokens.CDC, text, startLine, startCol);
 | |
|         } else {
 | |
|             reader.reset();
 | |
|             return this.charToken(first, startLine, startCol);
 | |
|         }
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Produces an IDENT or FUNCTION token based on the specified information. The
 | |
|      * first character is provided and the rest is read by the function to determine
 | |
|      * the correct token to create.
 | |
|      * @param {String} first The first character in the identifier.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @return {Object} A token object.
 | |
|      * @method identOrFunctionToken
 | |
|      */
 | |
|     identOrFunctionToken: function(first, startLine, startCol){
 | |
|         var reader  = this._reader,
 | |
|             ident   = this.readName(first),
 | |
|             tt      = Tokens.IDENT;
 | |
| 
 | |
|         //if there's a left paren immediately after, it's a URI or function
 | |
|         if (reader.peek() == "("){
 | |
|             ident += reader.read();
 | |
|             if (ident.toLowerCase() == "url("){
 | |
|                 tt = Tokens.URI;
 | |
|                 ident = this.readURI(ident);
 | |
| 
 | |
|                 //didn't find a valid URL or there's no closing paren
 | |
|                 if (ident.toLowerCase() == "url("){
 | |
|                     tt = Tokens.FUNCTION;
 | |
|                 }
 | |
|             } else {
 | |
|                 tt = Tokens.FUNCTION;
 | |
|             }
 | |
|         } else if (reader.peek() == ":"){  //might be an IE function
 | |
| 
 | |
|             //IE-specific functions always being with progid:
 | |
|             if (ident.toLowerCase() == "progid"){
 | |
|                 ident += reader.readTo("(");
 | |
|                 tt = Tokens.IE_FUNCTION;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return this.createToken(tt, ident, startLine, startCol);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Produces an IMPORTANT_SYM or CHAR token based on the specified information. The
 | |
|      * first character is provided and the rest is read by the function to determine
 | |
|      * the correct token to create.
 | |
|      * @param {String} first The first character in the token.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @return {Object} A token object.
 | |
|      * @method importantToken
 | |
|      */
 | |
|     importantToken: function(first, startLine, startCol){
 | |
|         var reader      = this._reader,
 | |
|             important   = first,
 | |
|             tt          = Tokens.CHAR,
 | |
|             temp,
 | |
|             c;
 | |
| 
 | |
|         reader.mark();
 | |
|         c = reader.read();
 | |
| 
 | |
|         while(c){
 | |
| 
 | |
|             //there can be a comment in here
 | |
|             if (c == "/"){
 | |
| 
 | |
|                 //if the next character isn't a star, then this isn't a valid !important token
 | |
|                 if (reader.peek() != "*"){
 | |
|                     break;
 | |
|                 } else {
 | |
|                     temp = this.readComment(c);
 | |
|                     if (temp === ""){    //broken!
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|             } else if (isWhitespace(c)){
 | |
|                 important += c + this.readWhitespace();
 | |
|             } else if (/i/i.test(c)){
 | |
|                 temp = reader.readCount(8);
 | |
|                 if (/mportant/i.test(temp)){
 | |
|                     important += c + temp;
 | |
|                     tt = Tokens.IMPORTANT_SYM;
 | |
| 
 | |
|                 }
 | |
|                 break;  //we're done
 | |
|             } else {
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             c = reader.read();
 | |
|         }
 | |
| 
 | |
|         if (tt == Tokens.CHAR){
 | |
|             reader.reset();
 | |
|             return this.charToken(first, startLine, startCol);
 | |
|         } else {
 | |
|             return this.createToken(tt, important, startLine, startCol);
 | |
|         }
 | |
| 
 | |
| 
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Produces a NOT or CHAR token based on the specified information. The
 | |
|      * first character is provided and the rest is read by the function to determine
 | |
|      * the correct token to create.
 | |
|      * @param {String} first The first character in the token.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @return {Object} A token object.
 | |
|      * @method notToken
 | |
|      */
 | |
|     notToken: function(first, startLine, startCol){
 | |
|         var reader      = this._reader,
 | |
|             text        = first;
 | |
| 
 | |
|         reader.mark();
 | |
|         text += reader.readCount(4);
 | |
| 
 | |
|         if (text.toLowerCase() == ":not("){
 | |
|             return this.createToken(Tokens.NOT, text, startLine, startCol);
 | |
|         } else {
 | |
|             reader.reset();
 | |
|             return this.charToken(first, startLine, startCol);
 | |
|         }
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Produces a number token based on the given character
 | |
|      * and location in the stream. This may return a token of
 | |
|      * NUMBER, EMS, EXS, LENGTH, ANGLE, TIME, FREQ, DIMENSION,
 | |
|      * or PERCENTAGE.
 | |
|      * @param {String} first The first character for the token.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @return {Object} A token object.
 | |
|      * @method numberToken
 | |
|      */
 | |
|     numberToken: function(first, startLine, startCol){
 | |
|         var reader  = this._reader,
 | |
|             value   = this.readNumber(first),
 | |
|             ident,
 | |
|             tt      = Tokens.NUMBER,
 | |
|             c       = reader.peek();
 | |
| 
 | |
|         if (isIdentStart(c)){
 | |
|             ident = this.readName(reader.read());
 | |
|             value += ident;
 | |
| 
 | |
|             if (/^em$|^ex$|^px$|^gd$|^rem$|^vw$|^vh$|^vm$|^ch$|^cm$|^mm$|^in$|^pt$|^pc$/i.test(ident)){
 | |
|                 tt = Tokens.LENGTH;
 | |
|             } else if (/^deg|^rad$|^grad$/i.test(ident)){
 | |
|                 tt = Tokens.ANGLE;
 | |
|             } else if (/^ms$|^s$/i.test(ident)){
 | |
|                 tt = Tokens.TIME;
 | |
|             } else if (/^hz$|^khz$/i.test(ident)){
 | |
|                 tt = Tokens.FREQ;
 | |
|             } else if (/^dpi$|^dpcm$/i.test(ident)){
 | |
|                 tt = Tokens.RESOLUTION;
 | |
|             } else {
 | |
|                 tt = Tokens.DIMENSION;
 | |
|             }
 | |
| 
 | |
|         } else if (c == "%"){
 | |
|             value += reader.read();
 | |
|             tt = Tokens.PERCENTAGE;
 | |
|         }
 | |
| 
 | |
|         return this.createToken(tt, value, startLine, startCol);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Produces a string token based on the given character
 | |
|      * and location in the stream. Since strings may be indicated
 | |
|      * by single or double quotes, a failure to match starting
 | |
|      * and ending quotes results in an INVALID token being generated.
 | |
|      * The first character in the string is passed in and then
 | |
|      * the rest are read up to and including the final quotation mark.
 | |
|      * @param {String} first The first character in the string.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @return {Object} A token object.
 | |
|      * @method stringToken
 | |
|      */
 | |
|     stringToken: function(first, startLine, startCol){
 | |
|         var delim   = first,
 | |
|             string  = first,
 | |
|             reader  = this._reader,
 | |
|             prev    = first,
 | |
|             tt      = Tokens.STRING,
 | |
|             c       = reader.read();
 | |
| 
 | |
|         while(c){
 | |
|             string += c;
 | |
| 
 | |
|             //if the delimiter is found with an escapement, we're done.
 | |
|             if (c == delim && prev != "\\"){
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             //if there's a newline without an escapement, it's an invalid string
 | |
|             if (isNewLine(reader.peek()) && c != "\\"){
 | |
|                 tt = Tokens.INVALID;
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             //save previous and get next
 | |
|             prev = c;
 | |
|             c = reader.read();
 | |
|         }
 | |
| 
 | |
|         //if c is null, that means we're out of input and the string was never closed
 | |
|         if (c === null){
 | |
|             tt = Tokens.INVALID;
 | |
|         }
 | |
| 
 | |
|         return this.createToken(tt, string, startLine, startCol);
 | |
|     },
 | |
| 
 | |
|     unicodeRangeToken: function(first, startLine, startCol){
 | |
|         var reader  = this._reader,
 | |
|             value   = first,
 | |
|             temp,
 | |
|             tt      = Tokens.CHAR;
 | |
| 
 | |
|         //then it should be a unicode range
 | |
|         if (reader.peek() == "+"){
 | |
|             reader.mark();
 | |
|             value += reader.read();
 | |
|             value += this.readUnicodeRangePart(true);
 | |
| 
 | |
|             //ensure there's an actual unicode range here
 | |
|             if (value.length == 2){
 | |
|                 reader.reset();
 | |
|             } else {
 | |
| 
 | |
|                 tt = Tokens.UNICODE_RANGE;
 | |
| 
 | |
|                 //if there's a ? in the first part, there can't be a second part
 | |
|                 if (value.indexOf("?") == -1){
 | |
| 
 | |
|                     if (reader.peek() == "-"){
 | |
|                         reader.mark();
 | |
|                         temp = reader.read();
 | |
|                         temp += this.readUnicodeRangePart(false);
 | |
| 
 | |
|                         //if there's not another value, back up and just take the first
 | |
|                         if (temp.length == 1){
 | |
|                             reader.reset();
 | |
|                         } else {
 | |
|                             value += temp;
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return this.createToken(tt, value, startLine, startCol);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Produces a S token based on the specified information. Since whitespace
 | |
|      * may have multiple characters, this consumes all whitespace characters
 | |
|      * into a single token.
 | |
|      * @param {String} first The first character in the token.
 | |
|      * @param {int} startLine The beginning line for the character.
 | |
|      * @param {int} startCol The beginning column for the character.
 | |
|      * @return {Object} A token object.
 | |
|      * @method whitespaceToken
 | |
|      */
 | |
|     whitespaceToken: function(first, startLine, startCol){
 | |
|         var reader  = this._reader,
 | |
|             value   = first + this.readWhitespace();
 | |
|         return this.createToken(Tokens.S, value, startLine, startCol);
 | |
|     },
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
|     //-------------------------------------------------------------------------
 | |
|     // Methods to read values from the string stream
 | |
|     //-------------------------------------------------------------------------
 | |
| 
 | |
|     readUnicodeRangePart: function(allowQuestionMark){
 | |
|         var reader  = this._reader,
 | |
|             part = "",
 | |
|             c       = reader.peek();
 | |
| 
 | |
|         //first read hex digits
 | |
|         while(isHexDigit(c) && part.length < 6){
 | |
|             reader.read();
 | |
|             part += c;
 | |
|             c = reader.peek();
 | |
|         }
 | |
| 
 | |
|         //then read question marks if allowed
 | |
|         if (allowQuestionMark){
 | |
|             while(c == "?" && part.length < 6){
 | |
|                 reader.read();
 | |
|                 part += c;
 | |
|                 c = reader.peek();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         //there can't be any other characters after this point
 | |
| 
 | |
|         return part;
 | |
|     },
 | |
| 
 | |
|     readWhitespace: function(){
 | |
|         var reader  = this._reader,
 | |
|             whitespace = "",
 | |
|             c       = reader.peek();
 | |
| 
 | |
|         while(isWhitespace(c)){
 | |
|             reader.read();
 | |
|             whitespace += c;
 | |
|             c = reader.peek();
 | |
|         }
 | |
| 
 | |
|         return whitespace;
 | |
|     },
 | |
|     readNumber: function(first){
 | |
|         var reader  = this._reader,
 | |
|             number  = first,
 | |
|             hasDot  = (first == "."),
 | |
|             c       = reader.peek();
 | |
| 
 | |
| 
 | |
|         while(c){
 | |
|             if (isDigit(c)){
 | |
|                 number += reader.read();
 | |
|             } else if (c == "."){
 | |
|                 if (hasDot){
 | |
|                     break;
 | |
|                 } else {
 | |
|                     hasDot = true;
 | |
|                     number += reader.read();
 | |
|                 }
 | |
|             } else {
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             c = reader.peek();
 | |
|         }
 | |
| 
 | |
|         return number;
 | |
|     },
 | |
|     readString: function(){
 | |
|         var reader  = this._reader,
 | |
|             delim   = reader.read(),
 | |
|             string  = delim,
 | |
|             prev    = delim,
 | |
|             c       = reader.peek();
 | |
| 
 | |
|         while(c){
 | |
|             c = reader.read();
 | |
|             string += c;
 | |
| 
 | |
|             //if the delimiter is found with an escapement, we're done.
 | |
|             if (c == delim && prev != "\\"){
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             //if there's a newline without an escapement, it's an invalid string
 | |
|             if (isNewLine(reader.peek()) && c != "\\"){
 | |
|                 string = "";
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             //save previous and get next
 | |
|             prev = c;
 | |
|             c = reader.peek();
 | |
|         }
 | |
| 
 | |
|         //if c is null, that means we're out of input and the string was never closed
 | |
|         if (c === null){
 | |
|             string = "";
 | |
|         }
 | |
| 
 | |
|         return string;
 | |
|     },
 | |
|     readURI: function(first){
 | |
|         var reader  = this._reader,
 | |
|             uri     = first,
 | |
|             inner   = "",
 | |
|             c       = reader.peek();
 | |
| 
 | |
|         reader.mark();
 | |
| 
 | |
|         //skip whitespace before
 | |
|         while(c && isWhitespace(c)){
 | |
|             reader.read();
 | |
|             c = reader.peek();
 | |
|         }
 | |
| 
 | |
|         //it's a string
 | |
|         if (c == "'" || c == "\""){
 | |
|             inner = this.readString();
 | |
|         } else {
 | |
|             inner = this.readURL();
 | |
|         }
 | |
| 
 | |
|         c = reader.peek();
 | |
| 
 | |
|         //skip whitespace after
 | |
|         while(c && isWhitespace(c)){
 | |
|             reader.read();
 | |
|             c = reader.peek();
 | |
|         }
 | |
| 
 | |
|         //if there was no inner value or the next character isn't closing paren, it's not a URI
 | |
|         if (inner === "" || c != ")"){
 | |
|             uri = first;
 | |
|             reader.reset();
 | |
|         } else {
 | |
|             uri += inner + reader.read();
 | |
|         }
 | |
| 
 | |
|         return uri;
 | |
|     },
 | |
|     readURL: function(){
 | |
|         var reader  = this._reader,
 | |
|             url     = "",
 | |
|             c       = reader.peek();
 | |
| 
 | |
|         //TODO: Check for escape and nonascii
 | |
|         while (/^[!#$%&\\*-~]$/.test(c)){
 | |
|             url += reader.read();
 | |
|             c = reader.peek();
 | |
|         }
 | |
| 
 | |
|         return url;
 | |
| 
 | |
|     },
 | |
|     readName: function(first){
 | |
|         var reader  = this._reader,
 | |
|             ident   = first || "",
 | |
|             c       = reader.peek();
 | |
| 
 | |
|         while(true){
 | |
|             if (c == "\\"){
 | |
|                 ident += this.readEscape(reader.read());
 | |
|                 c = reader.peek();
 | |
|             } else if(c && isNameChar(c)){
 | |
|                 ident += reader.read();
 | |
|                 c = reader.peek();
 | |
|             } else {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return ident;
 | |
|     },
 | |
| 
 | |
|     readEscape: function(first){
 | |
|         var reader  = this._reader,
 | |
|             cssEscape = first || "",
 | |
|             i       = 0,
 | |
|             c       = reader.peek();
 | |
| 
 | |
|         if (isHexDigit(c)){
 | |
|             do {
 | |
|                 cssEscape += reader.read();
 | |
|                 c = reader.peek();
 | |
|             } while(c && isHexDigit(c) && ++i < 6);
 | |
|         }
 | |
| 
 | |
|         if (cssEscape.length == 3 && /\s/.test(c) ||
 | |
|             cssEscape.length == 7 || cssEscape.length == 1){
 | |
|                 reader.read();
 | |
|         } else {
 | |
|             c = "";
 | |
|         }
 | |
| 
 | |
|         return cssEscape + c;
 | |
|     },
 | |
| 
 | |
|     readComment: function(first){
 | |
|         var reader  = this._reader,
 | |
|             comment = first || "",
 | |
|             c       = reader.read();
 | |
| 
 | |
|         if (c == "*"){
 | |
|             while(c){
 | |
|                 comment += c;
 | |
| 
 | |
|                 //look for end of comment
 | |
|                 if (comment.length > 2 && c == "*" && reader.peek() == "/"){
 | |
|                     comment += reader.read();
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
|                 c = reader.read();
 | |
|             }
 | |
| 
 | |
|             return comment;
 | |
|         } else {
 | |
|             return "";
 | |
|         }
 | |
| 
 | |
|     }
 | |
| });
 | |
| 
 | |
| 
 | |
| var Tokens  = [
 | |
| 
 | |
|     /*
 | |
|      * The following token names are defined in CSS3 Grammar: http://www.w3.org/TR/css3-syntax/#lexical
 | |
|      */
 | |
| 
 | |
|     //HTML-style comments
 | |
|     { name: "CDO"},
 | |
|     { name: "CDC"},
 | |
| 
 | |
|     //ignorables
 | |
|     { name: "S", whitespace: true/*, channel: "ws"*/},
 | |
|     { name: "COMMENT", comment: true, hide: true, channel: "comment" },
 | |
| 
 | |
|     //attribute equality
 | |
|     { name: "INCLUDES", text: "~="},
 | |
|     { name: "DASHMATCH", text: "|="},
 | |
|     { name: "PREFIXMATCH", text: "^="},
 | |
|     { name: "SUFFIXMATCH", text: "$="},
 | |
|     { name: "SUBSTRINGMATCH", text: "*="},
 | |
| 
 | |
|     //identifier types
 | |
|     { name: "STRING"},
 | |
|     { name: "IDENT"},
 | |
|     { name: "HASH"},
 | |
| 
 | |
|     //at-keywords
 | |
|     { name: "IMPORT_SYM", text: "@import"},
 | |
|     { name: "PAGE_SYM", text: "@page"},
 | |
|     { name: "MEDIA_SYM", text: "@media"},
 | |
|     { name: "FONT_FACE_SYM", text: "@font-face"},
 | |
|     { name: "CHARSET_SYM", text: "@charset"},
 | |
|     { name: "NAMESPACE_SYM", text: "@namespace"},
 | |
|     { name: "UNKNOWN_SYM" },
 | |
|     //{ name: "ATKEYWORD"},
 | |
| 
 | |
|     //CSS3 animations
 | |
|     { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-ms-keyframes" ] },
 | |
| 
 | |
|     //important symbol
 | |
|     { name: "IMPORTANT_SYM"},
 | |
| 
 | |
|     //measurements
 | |
|     { name: "LENGTH"},
 | |
|     { name: "ANGLE"},
 | |
|     { name: "TIME"},
 | |
|     { name: "FREQ"},
 | |
|     { name: "DIMENSION"},
 | |
|     { name: "PERCENTAGE"},
 | |
|     { name: "NUMBER"},
 | |
| 
 | |
|     //functions
 | |
|     { name: "URI"},
 | |
|     { name: "FUNCTION"},
 | |
| 
 | |
|     //Unicode ranges
 | |
|     { name: "UNICODE_RANGE"},
 | |
| 
 | |
|     /*
 | |
|      * The following token names are defined in CSS3 Selectors: http://www.w3.org/TR/css3-selectors/#selector-syntax
 | |
|      */
 | |
| 
 | |
|     //invalid string
 | |
|     { name: "INVALID"},
 | |
| 
 | |
|     //combinators
 | |
|     { name: "PLUS", text: "+" },
 | |
|     { name: "GREATER", text: ">"},
 | |
|     { name: "COMMA", text: ","},
 | |
|     { name: "TILDE", text: "~"},
 | |
| 
 | |
|     //modifier
 | |
|     { name: "NOT"},
 | |
| 
 | |
|     /*
 | |
|      * Defined in CSS3 Paged Media
 | |
|      */
 | |
|     { name: "TOPLEFTCORNER_SYM", text: "@top-left-corner"},
 | |
|     { name: "TOPLEFT_SYM", text: "@top-left"},
 | |
|     { name: "TOPCENTER_SYM", text: "@top-center"},
 | |
|     { name: "TOPRIGHT_SYM", text: "@top-right"},
 | |
|     { name: "TOPRIGHTCORNER_SYM", text: "@top-right-corner"},
 | |
|     { name: "BOTTOMLEFTCORNER_SYM", text: "@bottom-left-corner"},
 | |
|     { name: "BOTTOMLEFT_SYM", text: "@bottom-left"},
 | |
|     { name: "BOTTOMCENTER_SYM", text: "@bottom-center"},
 | |
|     { name: "BOTTOMRIGHT_SYM", text: "@bottom-right"},
 | |
|     { name: "BOTTOMRIGHTCORNER_SYM", text: "@bottom-right-corner"},
 | |
|     { name: "LEFTTOP_SYM", text: "@left-top"},
 | |
|     { name: "LEFTMIDDLE_SYM", text: "@left-middle"},
 | |
|     { name: "LEFTBOTTOM_SYM", text: "@left-bottom"},
 | |
|     { name: "RIGHTTOP_SYM", text: "@right-top"},
 | |
|     { name: "RIGHTMIDDLE_SYM", text: "@right-middle"},
 | |
|     { name: "RIGHTBOTTOM_SYM", text: "@right-bottom"},
 | |
| 
 | |
|     /*
 | |
|      * The following token names are defined in CSS3 Media Queries: http://www.w3.org/TR/css3-mediaqueries/#syntax
 | |
|      */
 | |
|     /*{ name: "MEDIA_ONLY", state: "media"},
 | |
|     { name: "MEDIA_NOT", state: "media"},
 | |
|     { name: "MEDIA_AND", state: "media"},*/
 | |
|     { name: "RESOLUTION", state: "media"},
 | |
| 
 | |
|     /*
 | |
|      * The following token names are not defined in any CSS specification but are used by the lexer.
 | |
|      */
 | |
| 
 | |
|     //not a real token, but useful for stupid IE filters
 | |
|     { name: "IE_FUNCTION" },
 | |
| 
 | |
|     //part of CSS3 grammar but not the Flex code
 | |
|     { name: "CHAR" },
 | |
| 
 | |
|     //TODO: Needed?
 | |
|     //Not defined as tokens, but might as well be
 | |
|     {
 | |
|         name: "PIPE",
 | |
|         text: "|"
 | |
|     },
 | |
|     {
 | |
|         name: "SLASH",
 | |
|         text: "/"
 | |
|     },
 | |
|     {
 | |
|         name: "MINUS",
 | |
|         text: "-"
 | |
|     },
 | |
|     {
 | |
|         name: "STAR",
 | |
|         text: "*"
 | |
|     },
 | |
| 
 | |
|     {
 | |
|         name: "LBRACE",
 | |
|         text: "{"
 | |
|     },
 | |
|     {
 | |
|         name: "RBRACE",
 | |
|         text: "}"
 | |
|     },
 | |
|     {
 | |
|         name: "LBRACKET",
 | |
|         text: "["
 | |
|     },
 | |
|     {
 | |
|         name: "RBRACKET",
 | |
|         text: "]"
 | |
|     },
 | |
|     {
 | |
|         name: "EQUALS",
 | |
|         text: "="
 | |
|     },
 | |
|     {
 | |
|         name: "COLON",
 | |
|         text: ":"
 | |
|     },
 | |
|     {
 | |
|         name: "SEMICOLON",
 | |
|         text: ";"
 | |
|     },
 | |
| 
 | |
|     {
 | |
|         name: "LPAREN",
 | |
|         text: "("
 | |
|     },
 | |
|     {
 | |
|         name: "RPAREN",
 | |
|         text: ")"
 | |
|     },
 | |
|     {
 | |
|         name: "DOT",
 | |
|         text: "."
 | |
|     }
 | |
| ];
 | |
| 
 | |
| (function(){
 | |
| 
 | |
|     var nameMap = [],
 | |
|         typeMap = {};
 | |
| 
 | |
|     Tokens.UNKNOWN = -1;
 | |
|     Tokens.unshift({name:"EOF"});
 | |
|     for (var i=0, len = Tokens.length; i < len; i++){
 | |
|         nameMap.push(Tokens[i].name);
 | |
|         Tokens[Tokens[i].name] = i;
 | |
|         if (Tokens[i].text){
 | |
|             if (Tokens[i].text instanceof Array){
 | |
|                 for (var j=0; j < Tokens[i].text.length; j++){
 | |
|                     typeMap[Tokens[i].text[j]] = i;
 | |
|                 }
 | |
|             } else {
 | |
|                 typeMap[Tokens[i].text] = i;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     Tokens.name = function(tt){
 | |
|         return nameMap[tt];
 | |
|     };
 | |
| 
 | |
|     Tokens.type = function(c){
 | |
|         return typeMap[c] || -1;
 | |
|     };
 | |
| 
 | |
| })();
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| //This file will likely change a lot! Very experimental!
 | |
| /*global Properties, ValidationTypes, ValidationError, PropertyValueIterator */
 | |
| var Validation = {
 | |
| 
 | |
|     validate: function(property, value){
 | |
| 
 | |
|         //normalize name
 | |
|         var name        = property.toString().toLowerCase(),
 | |
|             parts       = value.parts,
 | |
|             expression  = new PropertyValueIterator(value),
 | |
|             spec        = Properties[name],
 | |
|             part,
 | |
|             valid,
 | |
|             j, count,
 | |
|             msg,
 | |
|             types,
 | |
|             last,
 | |
|             literals,
 | |
|             max, multi, group;
 | |
| 
 | |
|         if (!spec) {
 | |
|             if (name.indexOf("-") !== 0){    //vendor prefixed are ok
 | |
|                 throw new ValidationError("Unknown property '" + property + "'.", property.line, property.col);
 | |
|             }
 | |
|         } else if (typeof spec != "number"){
 | |
| 
 | |
|             //initialization
 | |
|             if (typeof spec == "string"){
 | |
|                 if (spec.indexOf("||") > -1) {
 | |
|                     this.groupProperty(spec, expression);
 | |
|                 } else {
 | |
|                     this.singleProperty(spec, expression, 1);
 | |
|                 }
 | |
| 
 | |
|             } else if (spec.multi) {
 | |
|                 this.multiProperty(spec.multi, expression, spec.comma, spec.max || Infinity);
 | |
|             } else if (typeof spec == "function") {
 | |
|                 spec(expression);
 | |
|             }
 | |
| 
 | |
|         }
 | |
| 
 | |
|     },
 | |
| 
 | |
|     singleProperty: function(types, expression, max, partial) {
 | |
| 
 | |
|         var result      = false,
 | |
|             value       = expression.value,
 | |
|             count       = 0,
 | |
|             part;
 | |
| 
 | |
|         while (expression.hasNext() && count < max) {
 | |
|             result = ValidationTypes.isAny(expression, types);
 | |
|             if (!result) {
 | |
|                 break;
 | |
|             }
 | |
|             count++;
 | |
|         }
 | |
| 
 | |
|         if (!result) {
 | |
|             if (expression.hasNext() && !expression.isFirst()) {
 | |
|                 part = expression.peek();
 | |
|                 throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
 | |
|             } else {
 | |
|                  throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col);
 | |
|             }
 | |
|         } else if (expression.hasNext()) {
 | |
|             part = expression.next();
 | |
|             throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
 | |
|         }
 | |
| 
 | |
|     },
 | |
| 
 | |
|     multiProperty: function (types, expression, comma, max) {
 | |
| 
 | |
|         var result      = false,
 | |
|             value       = expression.value,
 | |
|             count       = 0,
 | |
|             sep         = false,
 | |
|             part;
 | |
| 
 | |
|         while(expression.hasNext() && !result && count < max) {
 | |
|             if (ValidationTypes.isAny(expression, types)) {
 | |
|                 count++;
 | |
|                 if (!expression.hasNext()) {
 | |
|                     result = true;
 | |
| 
 | |
|                 } else if (comma) {
 | |
|                     if (expression.peek() == ",") {
 | |
|                         part = expression.next();
 | |
|                     } else {
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|             } else {
 | |
|                 break;
 | |
| 
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (!result) {
 | |
|             if (expression.hasNext() && !expression.isFirst()) {
 | |
|                 part = expression.peek();
 | |
|                 throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
 | |
|             } else {
 | |
|                 part = expression.previous();
 | |
|                 if (comma && part == ",") {
 | |
|                     throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
 | |
|                 } else {
 | |
|                     throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|         } else if (expression.hasNext()) {
 | |
|             part = expression.next();
 | |
|             throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
 | |
|         }
 | |
| 
 | |
|     },
 | |
| 
 | |
|     groupProperty: function (types, expression, comma) {
 | |
| 
 | |
|         var result      = false,
 | |
|             value       = expression.value,
 | |
|             typeCount   = types.split("||").length,
 | |
|             groups      = { count: 0 },
 | |
|             partial     = false,
 | |
|             name,
 | |
|             part;
 | |
| 
 | |
|         while(expression.hasNext() && !result) {
 | |
|             name = ValidationTypes.isAnyOfGroup(expression, types);
 | |
|             if (name) {
 | |
| 
 | |
|                 //no dupes
 | |
|                 if (groups[name]) {
 | |
|                     break;
 | |
|                 } else {
 | |
|                     groups[name] = 1;
 | |
|                     groups.count++;
 | |
|                     partial = true;
 | |
| 
 | |
|                     if (groups.count == typeCount || !expression.hasNext()) {
 | |
|                         result = true;
 | |
|                     }
 | |
|                 }
 | |
|             } else {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (!result) {
 | |
|             if (partial && expression.hasNext()) {
 | |
|                     part = expression.peek();
 | |
|                     throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
 | |
|             } else {
 | |
|                 throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col);
 | |
|             }
 | |
|         } else if (expression.hasNext()) {
 | |
|             part = expression.next();
 | |
|             throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
| 
 | |
| };
 | |
| /**
 | |
|  * Type to use when a validation error occurs.
 | |
|  * @class ValidationError
 | |
|  * @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 ValidationError(message, line, col){
 | |
| 
 | |
|     /**
 | |
|      * The column at which the error occurred.
 | |
|      * @type int
 | |
|      * @property col
 | |
|      */
 | |
|     this.col = col;
 | |
| 
 | |
|     /**
 | |
|      * The line at which the error occurred.
 | |
|      * @type int
 | |
|      * @property line
 | |
|      */
 | |
|     this.line = line;
 | |
| 
 | |
|     /**
 | |
|      * The text representation of the unit.
 | |
|      * @type String
 | |
|      * @property text
 | |
|      */
 | |
|     this.message = message;
 | |
| 
 | |
| }
 | |
| 
 | |
| //inherit from Error
 | |
| ValidationError.prototype = new Error();
 | |
| //This file will likely change a lot! Very experimental!
 | |
| /*global Properties, Validation, ValidationError, PropertyValueIterator, console*/
 | |
| var ValidationTypes = {
 | |
| 
 | |
|     isLiteral: function (part, literals) {
 | |
|         var text = part.text.toString().toLowerCase(),
 | |
|             args = literals.split(" | "),
 | |
|             i, len, found = false;
 | |
| 
 | |
|         for (i=0,len=args.length; i < len && !found; i++){
 | |
|             if (text == args[i].toLowerCase()){
 | |
|                 found = true;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return found;
 | |
|     },
 | |
| 
 | |
|     isSimple: function(type) {
 | |
|         return !!this.simple[type];
 | |
|     },
 | |
| 
 | |
|     isComplex: function(type) {
 | |
|         return !!this.complex[type];
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Determines if the next part(s) of the given expression
 | |
|      * are any of the given types.
 | |
|      */
 | |
|     isAny: function (expression, types) {
 | |
|         var args = types.split(" | "),
 | |
|             i, len, found = false;
 | |
| 
 | |
|         for (i=0,len=args.length; i < len && !found && expression.hasNext(); i++){
 | |
|             found = this.isType(expression, args[i]);
 | |
|         }
 | |
| 
 | |
|         return found;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Determines if the next part(s) of the given expresion
 | |
|      * are one of a group.
 | |
|      */
 | |
|     isAnyOfGroup: function(expression, types) {
 | |
|         var args = types.split(" || "),
 | |
|             i, len, found = false;
 | |
| 
 | |
|         for (i=0,len=args.length; i < len && !found; i++){
 | |
|             found = this.isType(expression, args[i]);
 | |
|         }
 | |
| 
 | |
|         return found ? args[i-1] : false;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Determines if the next part(s) of the given expression
 | |
|      * are of a given type.
 | |
|      */
 | |
|     isType: function (expression, type) {
 | |
|         var part = expression.peek(),
 | |
|             result = false;
 | |
| 
 | |
|         if (type.charAt(0) != "<") {
 | |
|             result = this.isLiteral(part, type);
 | |
|             if (result) {
 | |
|                 expression.next();
 | |
|             }
 | |
|         } else if (this.simple[type]) {
 | |
|             result = this.simple[type](part);
 | |
|             if (result) {
 | |
|                 expression.next();
 | |
|             }
 | |
|         } else {
 | |
|             result = this.complex[type](expression);
 | |
|         }
 | |
| 
 | |
|         return result;
 | |
|     },
 | |
| 
 | |
| 
 | |
| 
 | |
|     simple: {
 | |
| 
 | |
|         "<absolute-size>": function(part){
 | |
|             return ValidationTypes.isLiteral(part, "xx-small | x-small | small | medium | large | x-large | xx-large");
 | |
|         },
 | |
| 
 | |
|         "<attachment>": function(part){
 | |
|             return ValidationTypes.isLiteral(part, "scroll | fixed | local");
 | |
|         },
 | |
| 
 | |
|         "<attr>": function(part){
 | |
|             return part.type == "function" && part.name == "attr";
 | |
|         },
 | |
| 
 | |
|         "<bg-image>": function(part){
 | |
|             return this["<image>"](part) || this["<gradient>"](part) ||  part == "none";
 | |
|         },
 | |
| 
 | |
|         "<gradient>": function(part) {
 | |
|             return part.type == "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?(?:repeating\-)?(?:radial\-|linear\-)?gradient/i.test(part);
 | |
|         },
 | |
| 
 | |
|         "<box>": function(part){
 | |
|             return ValidationTypes.isLiteral(part, "padding-box | border-box | content-box");
 | |
|         },
 | |
| 
 | |
|         "<content>": function(part){
 | |
|             return part.type == "function" && part.name == "content";
 | |
|         },
 | |
| 
 | |
|         "<relative-size>": function(part){
 | |
|             return ValidationTypes.isLiteral(part, "smaller | larger");
 | |
|         },
 | |
| 
 | |
|         //any identifier
 | |
|         "<ident>": function(part){
 | |
|             return part.type == "identifier";
 | |
|         },
 | |
| 
 | |
|         "<length>": function(part){
 | |
|             return part.type == "length" || part.type == "number" || part.type == "integer" || part == "0";
 | |
|         },
 | |
| 
 | |
|         "<color>": function(part){
 | |
|             return part.type == "color" || part == "transparent";
 | |
|         },
 | |
| 
 | |
|         "<number>": function(part){
 | |
|             return part.type == "number" || this["<integer>"](part);
 | |
|         },
 | |
| 
 | |
|         "<integer>": function(part){
 | |
|             return part.type == "integer";
 | |
|         },
 | |
| 
 | |
|         "<line>": function(part){
 | |
|             return part.type == "integer";
 | |
|         },
 | |
| 
 | |
|         "<angle>": function(part){
 | |
|             return part.type == "angle";
 | |
|         },
 | |
| 
 | |
|         "<uri>": function(part){
 | |
|             return part.type == "uri";
 | |
|         },
 | |
| 
 | |
|         "<image>": function(part){
 | |
|             return this["<uri>"](part);
 | |
|         },
 | |
| 
 | |
|         "<percentage>": function(part){
 | |
|             return part.type == "percentage" || part == "0";
 | |
|         },
 | |
| 
 | |
|         "<border-width>": function(part){
 | |
|             return this["<length>"](part) || ValidationTypes.isLiteral(part, "thin | medium | thick");
 | |
|         },
 | |
| 
 | |
|         "<border-style>": function(part){
 | |
|             return ValidationTypes.isLiteral(part, "none | hidden | dotted | dashed | solid | double | groove | ridge | inset | outset");
 | |
|         },
 | |
| 
 | |
|         "<margin-width>": function(part){
 | |
|             return this["<length>"](part) || this["<percentage>"](part) || ValidationTypes.isLiteral(part, "auto");
 | |
|         },
 | |
| 
 | |
|         "<padding-width>": function(part){
 | |
|             return this["<length>"](part) || this["<percentage>"](part);
 | |
|         },
 | |
| 
 | |
|         "<shape>": function(part){
 | |
|             return part.type == "function" && (part.name == "rect" || part.name == "inset-rect");
 | |
|         },
 | |
| 
 | |
|         "<time>": function(part) {
 | |
|             return part.type == "time";
 | |
|         }
 | |
|     },
 | |
| 
 | |
|     complex: {
 | |
| 
 | |
|         "<bg-position>": function(expression){
 | |
|             var types   = this,
 | |
|                 result  = false,
 | |
|                 numeric = "<percentage> | <length>",
 | |
|                 xDir    = "left | center | right",
 | |
|                 yDir    = "top | center | bottom",
 | |
|                 part,
 | |
|                 i, len;
 | |
| 
 | |
| /*
 | |
| <position> = [
 | |
|   [ left | center | right | top | bottom | <percentage> | <length> ]
 | |
| |
 | |
|   [ left | center | right | <percentage> | <length> ]
 | |
|   [ top | center | bottom | <percentage> | <length> ]
 | |
| |
 | |
|   [ center | [ left | right ] [ <percentage> | <length> ]? ] &&
 | |
|   [ center | [ top | bottom ] [ <percentage> | <length> ]? ]
 | |
| ]
 | |
| 
 | |
| */
 | |
| 
 | |
|             if (ValidationTypes.isAny(expression, "top | bottom")) {
 | |
|                 result = true;
 | |
|             } else {
 | |
| 
 | |
|                 //must be two-part
 | |
|                 if (ValidationTypes.isAny(expression, numeric)){
 | |
|                     if (expression.hasNext()){
 | |
|                         result = ValidationTypes.isAny(expression, numeric + " | " + yDir);
 | |
|                     }
 | |
|                 } else if (ValidationTypes.isAny(expression, xDir)){
 | |
|                     if (expression.hasNext()){
 | |
| 
 | |
|                         //two- or three-part
 | |
|                         if (ValidationTypes.isAny(expression, yDir)){
 | |
|                             result = true;
 | |
| 
 | |
|                             ValidationTypes.isAny(expression, numeric);
 | |
| 
 | |
|                         } else if (ValidationTypes.isAny(expression, numeric)){
 | |
| 
 | |
|                             //could also be two-part, so check the next part
 | |
|                             if (ValidationTypes.isAny(expression, yDir)){
 | |
|                                 ValidationTypes.isAny(expression, numeric);
 | |
|                             }
 | |
| 
 | |
|                             result = true;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
| 
 | |
|             return result;
 | |
|         },
 | |
| 
 | |
|         "<bg-size>": function(expression){
 | |
|             //<bg-size> = [ <length> | <percentage> | auto ]{1,2} | cover | contain
 | |
|             var types   = this,
 | |
|                 result  = false,
 | |
|                 numeric = "<percentage> | <length> | auto",
 | |
|                 part,
 | |
|                 i, len;
 | |
| 
 | |
|             if (ValidationTypes.isAny(expression, "cover | contain")) {
 | |
|                 result = true;
 | |
|             } else if (ValidationTypes.isAny(expression, numeric)) {
 | |
|                 result = true;
 | |
|                 ValidationTypes.isAny(expression, numeric);
 | |
|             }
 | |
| 
 | |
|             return result;
 | |
|         },
 | |
| 
 | |
|         "<repeat-style>": function(expression){
 | |
|             //repeat-x | repeat-y | [repeat | space | round | no-repeat]{1,2}
 | |
|             var result  = false,
 | |
|                 values  = "repeat | space | round | no-repeat",
 | |
|                 part;
 | |
| 
 | |
|             if (expression.hasNext()){
 | |
|                 part = expression.next();
 | |
| 
 | |
|                 if (ValidationTypes.isLiteral(part, "repeat-x | repeat-y")) {
 | |
|                     result = true;
 | |
|                 } else if (ValidationTypes.isLiteral(part, values)) {
 | |
|                     result = true;
 | |
| 
 | |
|                     if (expression.hasNext() && ValidationTypes.isLiteral(expression.peek(), values)) {
 | |
|                         expression.next();
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return result;
 | |
| 
 | |
|         },
 | |
| 
 | |
|         "<shadow>": function(expression) {
 | |
|             //inset? && [ <length>{2,4} && <color>? ]
 | |
|             var result  = false,
 | |
|                 count   = 0,
 | |
|                 inset   = false,
 | |
|                 color   = false,
 | |
|                 part;
 | |
| 
 | |
|             if (expression.hasNext()) {
 | |
| 
 | |
|                 if (ValidationTypes.isAny(expression, "inset")){
 | |
|                     inset = true;
 | |
|                 }
 | |
| 
 | |
|                 if (ValidationTypes.isAny(expression, "<color>")) {
 | |
|                     color = true;
 | |
|                 }
 | |
| 
 | |
|                 while (ValidationTypes.isAny(expression, "<length>") && count < 4) {
 | |
|                     count++;
 | |
|                 }
 | |
| 
 | |
| 
 | |
|                 if (expression.hasNext()) {
 | |
|                     if (!color) {
 | |
|                         ValidationTypes.isAny(expression, "<color>");
 | |
|                     }
 | |
| 
 | |
|                     if (!inset) {
 | |
|                         ValidationTypes.isAny(expression, "inset");
 | |
|                     }
 | |
| 
 | |
|                 }
 | |
| 
 | |
|                 result = (count >= 2 && count <= 4);
 | |
| 
 | |
|             }
 | |
| 
 | |
|             return result;
 | |
|         },
 | |
| 
 | |
|         "<x-one-radius>": function(expression) {
 | |
|             //[ <length> | <percentage> ] [ <length> | <percentage> ]?
 | |
|             var result  = false,
 | |
|                 count   = 0,
 | |
|                 numeric = "<length> | <percentage>",
 | |
|                 part;
 | |
| 
 | |
|             if (ValidationTypes.isAny(expression, numeric)){
 | |
|                 result = true;
 | |
| 
 | |
|                 ValidationTypes.isAny(expression, numeric);
 | |
|             }
 | |
| 
 | |
|             return result;
 | |
|         }
 | |
|     }
 | |
| };
 | |
| 
 | |
| 
 | |
| parserlib.css = {
 | |
| Colors              :Colors,
 | |
| Combinator          :Combinator,
 | |
| Parser              :Parser,
 | |
| PropertyName        :PropertyName,
 | |
| PropertyValue       :PropertyValue,
 | |
| PropertyValuePart   :PropertyValuePart,
 | |
| MediaFeature        :MediaFeature,
 | |
| MediaQuery          :MediaQuery,
 | |
| Selector            :Selector,
 | |
| SelectorPart        :SelectorPart,
 | |
| SelectorSubPart     :SelectorSubPart,
 | |
| Specificity         :Specificity,
 | |
| TokenStream         :TokenStream,
 | |
| Tokens              :Tokens,
 | |
| ValidationError     :ValidationError
 | |
| };
 | |
| })();
 | |
| 
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Main CSSLint object.
 | |
|  * @class CSSLint
 | |
|  * @static
 | |
|  * @extends parserlib.util.EventTarget
 | |
|  */
 | |
| /*global parserlib, Reporter*/
 | |
| var CSSLint = (function(){
 | |
| 
 | |
|     var rules      = [],
 | |
|         formatters = [],
 | |
|         api        = new parserlib.util.EventTarget();
 | |
| 
 | |
|     api.version = "0.9.8";
 | |
| 
 | |
|     //-------------------------------------------------------------------------
 | |
|     // Rule Management
 | |
|     //-------------------------------------------------------------------------
 | |
| 
 | |
|     /**
 | |
|      * Adds a new rule to the engine.
 | |
|      * @param {Object} rule The rule to add.
 | |
|      * @method addRule
 | |
|      */
 | |
|     api.addRule = function(rule){
 | |
|         rules.push(rule);
 | |
|         rules[rule.id] = rule;
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Clears all rule from the engine.
 | |
|      * @method clearRules
 | |
|      */
 | |
|     api.clearRules = function(){
 | |
|         rules = [];
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Returns the rule objects.
 | |
|      * @return An array of rule objects.
 | |
|      * @method getRules
 | |
|      */
 | |
|     api.getRules = function(){
 | |
|         return [].concat(rules).sort(function(a,b){
 | |
|             return a.id > b.id ? 1 : 0;
 | |
|         });
 | |
|     };
 | |
| 
 | |
|     //-------------------------------------------------------------------------
 | |
|     // Formatters
 | |
|     //-------------------------------------------------------------------------
 | |
| 
 | |
|     /**
 | |
|      * Adds a new formatter to the engine.
 | |
|      * @param {Object} formatter The formatter to add.
 | |
|      * @method addFormatter
 | |
|      */
 | |
|     api.addFormatter = function(formatter) {
 | |
|         // formatters.push(formatter);
 | |
|         formatters[formatter.id] = formatter;
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Retrieves a formatter for use.
 | |
|      * @param {String} formatId The name of the format to retrieve.
 | |
|      * @return {Object} The formatter or undefined.
 | |
|      * @method getFormatter
 | |
|      */
 | |
|     api.getFormatter = function(formatId){
 | |
|         return formatters[formatId];
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Formats the results in a particular format for a single file.
 | |
|      * @param {Object} result The results returned from CSSLint.verify().
 | |
|      * @param {String} filename The filename for which the results apply.
 | |
|      * @param {String} formatId The name of the formatter to use.
 | |
|      * @param {Object} options (Optional) for special output handling.
 | |
|      * @return {String} A formatted string for the results.
 | |
|      * @method format
 | |
|      */
 | |
|     api.format = function(results, filename, formatId, options) {
 | |
|         var formatter = this.getFormatter(formatId),
 | |
|             result = null;
 | |
| 
 | |
|         if (formatter){
 | |
|             result = formatter.startFormat();
 | |
|             result += formatter.formatResults(results, filename, options || {});
 | |
|             result += formatter.endFormat();
 | |
|         }
 | |
| 
 | |
|         return result;
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Indicates if the given format is supported.
 | |
|      * @param {String} formatId The ID of the format to check.
 | |
|      * @return {Boolean} True if the format exists, false if not.
 | |
|      * @method hasFormat
 | |
|      */
 | |
|     api.hasFormat = function(formatId){
 | |
|         return formatters.hasOwnProperty(formatId);
 | |
|     };
 | |
| 
 | |
|     //-------------------------------------------------------------------------
 | |
|     // Verification
 | |
|     //-------------------------------------------------------------------------
 | |
| 
 | |
|     /**
 | |
|      * Starts the verification process for the given CSS text.
 | |
|      * @param {String} text The CSS text to verify.
 | |
|      * @param {Object} ruleset (Optional) List of rules to apply. If null, then
 | |
|      *      all rules are used. If a rule has a value of 1 then it's a warning,
 | |
|      *      a value of 2 means it's an error.
 | |
|      * @return {Object} Results of the verification.
 | |
|      * @method verify
 | |
|      */
 | |
|     api.verify = function(text, ruleset){
 | |
| 
 | |
|         var i       = 0,
 | |
|             len     = rules.length,
 | |
|             reporter,
 | |
|             lines,
 | |
|             report,
 | |
|             parser = new parserlib.css.Parser({ starHack: true, ieFilters: true,
 | |
|                                                 underscoreHack: true, strict: false });
 | |
| 
 | |
|         lines = text.replace(/\n\r?/g, "$split$").split('$split$');
 | |
| 
 | |
|         if (!ruleset){
 | |
|             ruleset = {};
 | |
|             while (i < len){
 | |
|                 ruleset[rules[i++].id] = 1;    //by default, everything is a warning
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         reporter = new Reporter(lines, ruleset);
 | |
| 
 | |
|         ruleset.errors = 2;       //always report parsing errors as errors
 | |
|         for (i in ruleset){
 | |
|             if(ruleset.hasOwnProperty(i)){
 | |
|                 if (rules[i]){
 | |
|                     rules[i].init(parser, reporter);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         //capture most horrible error type
 | |
|         try {
 | |
|             parser.parse(text);
 | |
|         } catch (ex) {
 | |
|             reporter.error("Fatal error, cannot continue: " + ex.message, ex.line, ex.col, {});
 | |
|         }
 | |
| 
 | |
|         report = {
 | |
|             messages    : reporter.messages,
 | |
|             stats       : reporter.stats
 | |
|         };
 | |
| 
 | |
|         //sort by line numbers, rollups at the bottom
 | |
|         report.messages.sort(function (a, b){
 | |
|             if (a.rollup && !b.rollup){
 | |
|                 return 1;
 | |
|             } else if (!a.rollup && b.rollup){
 | |
|                 return -1;
 | |
|             } else {
 | |
|                 return a.line - b.line;
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         return report;
 | |
|     };
 | |
| 
 | |
|     //-------------------------------------------------------------------------
 | |
|     // Publish the API
 | |
|     //-------------------------------------------------------------------------
 | |
| 
 | |
|     return api;
 | |
| 
 | |
| })();
 | |
| 
 | |
| /*global CSSLint*/
 | |
| /**
 | |
|  * An instance of Report is used to report results of the
 | |
|  * verification back to the main API.
 | |
|  * @class Reporter
 | |
|  * @constructor
 | |
|  * @param {String[]} lines The text lines of the source.
 | |
|  * @param {Object} ruleset The set of rules to work with, including if
 | |
|  *      they are errors or warnings.
 | |
|  */
 | |
| function Reporter(lines, ruleset){
 | |
| 
 | |
|     /**
 | |
|      * List of messages being reported.
 | |
|      * @property messages
 | |
|      * @type String[]
 | |
|      */
 | |
|     this.messages = [];
 | |
| 
 | |
|     /**
 | |
|      * List of statistics being reported.
 | |
|      * @property stats
 | |
|      * @type String[]
 | |
|      */
 | |
|     this.stats = [];
 | |
| 
 | |
|     /**
 | |
|      * Lines of code being reported on. Used to provide contextual information
 | |
|      * for messages.
 | |
|      * @property lines
 | |
|      * @type String[]
 | |
|      */
 | |
|     this.lines = lines;
 | |
| 
 | |
|     /**
 | |
|      * Information about the rules. Used to determine whether an issue is an
 | |
|      * error or warning.
 | |
|      * @property ruleset
 | |
|      * @type Object
 | |
|      */
 | |
|     this.ruleset = ruleset;
 | |
| }
 | |
| 
 | |
| Reporter.prototype = {
 | |
| 
 | |
|     //restore constructor
 | |
|     constructor: Reporter,
 | |
| 
 | |
|     /**
 | |
|      * Report an error.
 | |
|      * @param {String} message The message to store.
 | |
|      * @param {int} line The line number.
 | |
|      * @param {int} col The column number.
 | |
|      * @param {Object} rule The rule this message relates to.
 | |
|      * @method error
 | |
|      */
 | |
|     error: function(message, line, col, rule){
 | |
|         this.messages.push({
 | |
|             type    : "error",
 | |
|             line    : line,
 | |
|             col     : col,
 | |
|             message : message,
 | |
|             evidence: this.lines[line-1],
 | |
|             rule    : rule || {}
 | |
|         });
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Report an warning.
 | |
|      * @param {String} message The message to store.
 | |
|      * @param {int} line The line number.
 | |
|      * @param {int} col The column number.
 | |
|      * @param {Object} rule The rule this message relates to.
 | |
|      * @method warn
 | |
|      * @deprecated Use report instead.
 | |
|      */
 | |
|     warn: function(message, line, col, rule){
 | |
|         this.report(message, line, col, rule);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Report an issue.
 | |
|      * @param {String} message The message to store.
 | |
|      * @param {int} line The line number.
 | |
|      * @param {int} col The column number.
 | |
|      * @param {Object} rule The rule this message relates to.
 | |
|      * @method report
 | |
|      */
 | |
|     report: function(message, line, col, rule){
 | |
|         this.messages.push({
 | |
|             type    : this.ruleset[rule.id] == 2 ? "error" : "warning",
 | |
|             line    : line,
 | |
|             col     : col,
 | |
|             message : message,
 | |
|             evidence: this.lines[line-1],
 | |
|             rule    : rule
 | |
|         });
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Report some informational text.
 | |
|      * @param {String} message The message to store.
 | |
|      * @param {int} line The line number.
 | |
|      * @param {int} col The column number.
 | |
|      * @param {Object} rule The rule this message relates to.
 | |
|      * @method info
 | |
|      */
 | |
|     info: function(message, line, col, rule){
 | |
|         this.messages.push({
 | |
|             type    : "info",
 | |
|             line    : line,
 | |
|             col     : col,
 | |
|             message : message,
 | |
|             evidence: this.lines[line-1],
 | |
|             rule    : rule
 | |
|         });
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Report some rollup error information.
 | |
|      * @param {String} message The message to store.
 | |
|      * @param {Object} rule The rule this message relates to.
 | |
|      * @method rollupError
 | |
|      */
 | |
|     rollupError: function(message, rule){
 | |
|         this.messages.push({
 | |
|             type    : "error",
 | |
|             rollup  : true,
 | |
|             message : message,
 | |
|             rule    : rule
 | |
|         });
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Report some rollup warning information.
 | |
|      * @param {String} message The message to store.
 | |
|      * @param {Object} rule The rule this message relates to.
 | |
|      * @method rollupWarn
 | |
|      */
 | |
|     rollupWarn: function(message, rule){
 | |
|         this.messages.push({
 | |
|             type    : "warning",
 | |
|             rollup  : true,
 | |
|             message : message,
 | |
|             rule    : rule
 | |
|         });
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Report a statistic.
 | |
|      * @param {String} name The name of the stat to store.
 | |
|      * @param {Variant} value The value of the stat.
 | |
|      * @method stat
 | |
|      */
 | |
|     stat: function(name, value){
 | |
|         this.stats[name] = value;
 | |
|     }
 | |
| };
 | |
| 
 | |
| //expose for testing purposes
 | |
| CSSLint._Reporter = Reporter;
 | |
| 
 | |
| /*global CSSLint*/
 | |
| 
 | |
| /*
 | |
|  * Utility functions that make life easier.
 | |
|  */
 | |
| CSSLint.Util = {
 | |
|     /*
 | |
|      * Adds all properties from supplier onto receiver,
 | |
|      * overwriting if the same name already exists on
 | |
|      * reciever.
 | |
|      * @param {Object} The object to receive the properties.
 | |
|      * @param {Object} The object to provide the properties.
 | |
|      * @return {Object} The receiver
 | |
|      */
 | |
|     mix: function(receiver, supplier){
 | |
|         var prop;
 | |
| 
 | |
|         for (prop in supplier){
 | |
|             if (supplier.hasOwnProperty(prop)){
 | |
|                 receiver[prop] = supplier[prop];
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return prop;
 | |
|     },
 | |
| 
 | |
|     /*
 | |
|      * Polyfill for array indexOf() method.
 | |
|      * @param {Array} values The array to search.
 | |
|      * @param {Variant} value The value to search for.
 | |
|      * @return {int} The index of the value if found, -1 if not.
 | |
|      */
 | |
|     indexOf: function(values, value){
 | |
|         if (values.indexOf){
 | |
|             return values.indexOf(value);
 | |
|         } else {
 | |
|             for (var i=0, len=values.length; i < len; i++){
 | |
|                 if (values[i] === value){
 | |
|                     return i;
 | |
|                 }
 | |
|             }
 | |
|             return -1;
 | |
|         }
 | |
|     },
 | |
| 
 | |
|     /*
 | |
|      * Polyfill for array forEach() method.
 | |
|      * @param {Array} values The array to operate on.
 | |
|      * @param {Function} func The function to call on each item.
 | |
|      * @return {void}
 | |
|      */
 | |
|     forEach: function(values, func) {
 | |
|         if (values.forEach){
 | |
|             return values.forEach(func);
 | |
|         } else {
 | |
|             for (var i=0, len=values.length; i < len; i++){
 | |
|                 func(values[i], i, values);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| };
 | |
| /*global CSSLint*/
 | |
| /*
 | |
|  * Rule: Don't use adjoining classes (.foo.bar).
 | |
|  */
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "adjoining-classes",
 | |
|     name: "Disallow adjoining classes",
 | |
|     desc: "Don't use adjoining classes.",
 | |
|     browsers: "IE6",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
|         parser.addListener("startrule", function(event){
 | |
|             var selectors = event.selectors,
 | |
|                 selector,
 | |
|                 part,
 | |
|                 modifier,
 | |
|                 classCount,
 | |
|                 i, j, k;
 | |
| 
 | |
|             for (i=0; i < selectors.length; i++){
 | |
|                 selector = selectors[i];
 | |
|                 for (j=0; j < selector.parts.length; j++){
 | |
|                     part = selector.parts[j];
 | |
|                     if (part.type == parser.SELECTOR_PART_TYPE){
 | |
|                         classCount = 0;
 | |
|                         for (k=0; k < part.modifiers.length; k++){
 | |
|                             modifier = part.modifiers[k];
 | |
|                             if (modifier.type == "class"){
 | |
|                                 classCount++;
 | |
|                             }
 | |
|                             if (classCount > 1){
 | |
|                                 reporter.report("Don't use adjoining classes.", part.line, part.col, rule);
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*global CSSLint*/
 | |
| 
 | |
| /*
 | |
|  * Rule: Don't use width or height when using padding or border.
 | |
|  */
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "box-model",
 | |
|     name: "Beware of broken box size",
 | |
|     desc: "Don't use width or height when using padding or border.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             widthProperties = {
 | |
|                 border: 1,
 | |
|                 "border-left": 1,
 | |
|                 "border-right": 1,
 | |
|                 padding: 1,
 | |
|                 "padding-left": 1,
 | |
|                 "padding-right": 1
 | |
|             },
 | |
|             heightProperties = {
 | |
|                 border: 1,
 | |
|                 "border-bottom": 1,
 | |
|                 "border-top": 1,
 | |
|                 padding: 1,
 | |
|                 "padding-bottom": 1,
 | |
|                 "padding-top": 1
 | |
|             },
 | |
|             properties;
 | |
| 
 | |
|         function startRule(){
 | |
|             properties = {};
 | |
|         }
 | |
| 
 | |
|         function endRule(){
 | |
|             var prop;
 | |
|             if (properties.height){
 | |
|                 for (prop in heightProperties){
 | |
|                     if (heightProperties.hasOwnProperty(prop) && properties[prop]){
 | |
| 
 | |
|                         //special case for padding
 | |
|                         if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[0].value === 0)){
 | |
|                             reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (properties.width){
 | |
|                 for (prop in widthProperties){
 | |
|                     if (widthProperties.hasOwnProperty(prop) && properties[prop]){
 | |
| 
 | |
|                         if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[1].value === 0)){
 | |
|                             reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         parser.addListener("startrule", startRule);
 | |
|         parser.addListener("startfontface", startRule);
 | |
|         parser.addListener("startpage", startRule);
 | |
|         parser.addListener("startpagemargin", startRule);
 | |
|         parser.addListener("startkeyframerule", startRule);
 | |
| 
 | |
|         parser.addListener("property", function(event){
 | |
|             var name = event.property.text.toLowerCase();
 | |
| 
 | |
|             if (heightProperties[name] || widthProperties[name]){
 | |
|                 if (!/^0\S*$/.test(event.value) && !(name == "border" && event.value == "none")){
 | |
|                     properties[name] = { line: event.property.line, col: event.property.col, value: event.value };
 | |
|                 }
 | |
|             } else {
 | |
|                 if (name == "width" || name == "height"){
 | |
|                     properties[name] = 1;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", endRule);
 | |
|         parser.addListener("endfontface", endRule);
 | |
|         parser.addListener("endpage", endRule);
 | |
|         parser.addListener("endpagemargin", endRule);
 | |
|         parser.addListener("endkeyframerule", endRule);
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*global CSSLint*/
 | |
| 
 | |
| /*
 | |
|  * Rule: box-sizing doesn't work in IE6 and IE7.
 | |
|  */
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "box-sizing",
 | |
|     name: "Disallow use of box-sizing",
 | |
|     desc: "The box-sizing properties isn't supported in IE6 and IE7.",
 | |
|     browsers: "IE6, IE7",
 | |
|     tags: ["Compatibility"],
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
| 
 | |
|         parser.addListener("property", function(event){
 | |
|             var name = event.property.text.toLowerCase();
 | |
| 
 | |
|             if (name == "box-sizing"){
 | |
|                 reporter.report("The box-sizing property isn't supported in IE6 and IE7.", event.line, event.col, rule);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Include all compatible vendor prefixes to reach a wider
 | |
|  * range of users.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "compatible-vendor-prefixes",
 | |
|     name: "Require compatible vendor prefixes",
 | |
|     desc: "Include all compatible vendor prefixes to reach a wider range of users.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function (parser, reporter) {
 | |
|         var rule = this,
 | |
|             compatiblePrefixes,
 | |
|             properties,
 | |
|             prop,
 | |
|             variations,
 | |
|             prefixed,
 | |
|             i,
 | |
|             len,
 | |
|             arrayPush = Array.prototype.push,
 | |
|             applyTo = [];
 | |
| 
 | |
|         // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details
 | |
|         compatiblePrefixes = {
 | |
|             "animation"                  : "webkit moz ms",
 | |
|             "animation-delay"            : "webkit moz ms",
 | |
|             "animation-direction"        : "webkit moz ms",
 | |
|             "animation-duration"         : "webkit moz ms",
 | |
|             "animation-fill-mode"        : "webkit moz ms",
 | |
|             "animation-iteration-count"  : "webkit moz ms",
 | |
|             "animation-name"             : "webkit moz ms",
 | |
|             "animation-play-state"       : "webkit moz ms",
 | |
|             "animation-timing-function"  : "webkit moz ms",
 | |
|             "appearance"                 : "webkit moz",
 | |
|             "border-end"                 : "webkit moz",
 | |
|             "border-end-color"           : "webkit moz",
 | |
|             "border-end-style"           : "webkit moz",
 | |
|             "border-end-width"           : "webkit moz",
 | |
|             "border-image"               : "webkit moz o",
 | |
|             "border-radius"              : "webkit moz",
 | |
|             "border-start"               : "webkit moz",
 | |
|             "border-start-color"         : "webkit moz",
 | |
|             "border-start-style"         : "webkit moz",
 | |
|             "border-start-width"         : "webkit moz",
 | |
|             "box-align"                  : "webkit moz ms",
 | |
|             "box-direction"              : "webkit moz ms",
 | |
|             "box-flex"                   : "webkit moz ms",
 | |
|             "box-lines"                  : "webkit ms",
 | |
|             "box-ordinal-group"          : "webkit moz ms",
 | |
|             "box-orient"                 : "webkit moz ms",
 | |
|             "box-pack"                   : "webkit moz ms",
 | |
|             "box-sizing"                 : "webkit moz",
 | |
|             "box-shadow"                 : "webkit moz",
 | |
|             "column-count"               : "webkit moz ms",
 | |
|             "column-gap"                 : "webkit moz ms",
 | |
|             "column-rule"                : "webkit moz ms",
 | |
|             "column-rule-color"          : "webkit moz ms",
 | |
|             "column-rule-style"          : "webkit moz ms",
 | |
|             "column-rule-width"          : "webkit moz ms",
 | |
|             "column-width"               : "webkit moz ms",
 | |
|             "hyphens"                    : "epub moz",
 | |
|             "line-break"                 : "webkit ms",
 | |
|             "margin-end"                 : "webkit moz",
 | |
|             "margin-start"               : "webkit moz",
 | |
|             "marquee-speed"              : "webkit wap",
 | |
|             "marquee-style"              : "webkit wap",
 | |
|             "padding-end"                : "webkit moz",
 | |
|             "padding-start"              : "webkit moz",
 | |
|             "tab-size"                   : "moz o",
 | |
|             "text-size-adjust"           : "webkit ms",
 | |
|             "transform"                  : "webkit moz ms o",
 | |
|             "transform-origin"           : "webkit moz ms o",
 | |
|             "transition"                 : "webkit moz o ms",
 | |
|             "transition-delay"           : "webkit moz o ms",
 | |
|             "transition-duration"        : "webkit moz o ms",
 | |
|             "transition-property"        : "webkit moz o ms",
 | |
|             "transition-timing-function" : "webkit moz o ms",
 | |
|             "user-modify"                : "webkit moz",
 | |
|             "user-select"                : "webkit moz ms",
 | |
|             "word-break"                 : "epub ms",
 | |
|             "writing-mode"               : "epub ms"
 | |
|         };
 | |
| 
 | |
| 
 | |
|         for (prop in compatiblePrefixes) {
 | |
|             if (compatiblePrefixes.hasOwnProperty(prop)) {
 | |
|                 variations = [];
 | |
|                 prefixed = compatiblePrefixes[prop].split(' ');
 | |
|                 for (i = 0, len = prefixed.length; i < len; i++) {
 | |
|                     variations.push('-' + prefixed[i] + '-' + prop);
 | |
|                 }
 | |
|                 compatiblePrefixes[prop] = variations;
 | |
|                 arrayPush.apply(applyTo, variations);
 | |
|             }
 | |
|         }
 | |
|         parser.addListener("startrule", function () {
 | |
|             properties = [];
 | |
|         });
 | |
| 
 | |
|         parser.addListener("property", function (event) {
 | |
|             var name = event.property;
 | |
|             if (CSSLint.Util.indexOf(applyTo, name.text) > -1) {
 | |
|                 properties.push(name);
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", function (event) {
 | |
|             if (!properties.length) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             var propertyGroups = {},
 | |
|                 i,
 | |
|                 len,
 | |
|                 name,
 | |
|                 prop,
 | |
|                 variations,
 | |
|                 value,
 | |
|                 full,
 | |
|                 actual,
 | |
|                 item,
 | |
|                 propertiesSpecified;
 | |
| 
 | |
|             for (i = 0, len = properties.length; i < len; i++) {
 | |
|                 name = properties[i];
 | |
| 
 | |
|                 for (prop in compatiblePrefixes) {
 | |
|                     if (compatiblePrefixes.hasOwnProperty(prop)) {
 | |
|                         variations = compatiblePrefixes[prop];
 | |
|                         if (CSSLint.Util.indexOf(variations, name.text) > -1) {
 | |
|                             if (!propertyGroups[prop]) {
 | |
|                                 propertyGroups[prop] = {
 | |
|                                     full : variations.slice(0),
 | |
|                                     actual : [],
 | |
|                                     actualNodes: []
 | |
|                                 };
 | |
|                             }
 | |
|                             if (CSSLint.Util.indexOf(propertyGroups[prop].actual, name.text) === -1) {
 | |
|                                 propertyGroups[prop].actual.push(name.text);
 | |
|                                 propertyGroups[prop].actualNodes.push(name);
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             for (prop in propertyGroups) {
 | |
|                 if (propertyGroups.hasOwnProperty(prop)) {
 | |
|                     value = propertyGroups[prop];
 | |
|                     full = value.full;
 | |
|                     actual = value.actual;
 | |
| 
 | |
|                     if (full.length > actual.length) {
 | |
|                         for (i = 0, len = full.length; i < len; i++) {
 | |
|                             item = full[i];
 | |
|                             if (CSSLint.Util.indexOf(actual, item) === -1) {
 | |
|                                 propertiesSpecified = (actual.length === 1) ? actual[0] : (actual.length == 2) ? actual.join(" and ") : actual.join(", ");
 | |
|                                 reporter.report("The property " + item + " is compatible with " + propertiesSpecified + " and should be included as well.", value.actualNodes[0].line, value.actualNodes[0].col, rule);
 | |
|                             }
 | |
|                         }
 | |
| 
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| });
 | |
| /*
 | |
|  * Rule: Certain properties don't play well with certain display values.
 | |
|  * - float should not be used with inline-block
 | |
|  * - height, width, margin-top, margin-bottom, float should not be used with inline
 | |
|  * - vertical-align should not be used with block
 | |
|  * - margin, float should not be used with table-*
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "display-property-grouping",
 | |
|     name: "Require properties appropriate for display",
 | |
|     desc: "Certain properties shouldn't be used with certain display property values.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
| 
 | |
|         var propertiesToCheck = {
 | |
|                 display: 1,
 | |
|                 "float": "none",
 | |
|                 height: 1,
 | |
|                 width: 1,
 | |
|                 margin: 1,
 | |
|                 "margin-left": 1,
 | |
|                 "margin-right": 1,
 | |
|                 "margin-bottom": 1,
 | |
|                 "margin-top": 1,
 | |
|                 padding: 1,
 | |
|                 "padding-left": 1,
 | |
|                 "padding-right": 1,
 | |
|                 "padding-bottom": 1,
 | |
|                 "padding-top": 1,
 | |
|                 "vertical-align": 1
 | |
|             },
 | |
|             properties;
 | |
| 
 | |
|         function reportProperty(name, display, msg){
 | |
|             if (properties[name]){
 | |
|                 if (typeof propertiesToCheck[name] != "string" || properties[name].value.toLowerCase() != propertiesToCheck[name]){
 | |
|                     reporter.report(msg || name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         function startRule(){
 | |
|             properties = {};
 | |
|         }
 | |
| 
 | |
|         function endRule(){
 | |
| 
 | |
|             var display = properties.display ? properties.display.value : null;
 | |
|             if (display){
 | |
|                 switch(display){
 | |
| 
 | |
|                     case "inline":
 | |
|                         //height, width, margin-top, margin-bottom, float should not be used with inline
 | |
|                         reportProperty("height", display);
 | |
|                         reportProperty("width", display);
 | |
|                         reportProperty("margin", display);
 | |
|                         reportProperty("margin-top", display);
 | |
|                         reportProperty("margin-bottom", display);
 | |
|                         reportProperty("float", display, "display:inline has no effect on floated elements (but may be used to fix the IE6 double-margin bug).");
 | |
|                         break;
 | |
| 
 | |
|                     case "block":
 | |
|                         //vertical-align should not be used with block
 | |
|                         reportProperty("vertical-align", display);
 | |
|                         break;
 | |
| 
 | |
|                     case "inline-block":
 | |
|                         //float should not be used with inline-block
 | |
|                         reportProperty("float", display);
 | |
|                         break;
 | |
| 
 | |
|                     default:
 | |
|                         //margin, float should not be used with table
 | |
|                         if (display.indexOf("table-") === 0){
 | |
|                             reportProperty("margin", display);
 | |
|                             reportProperty("margin-left", display);
 | |
|                             reportProperty("margin-right", display);
 | |
|                             reportProperty("margin-top", display);
 | |
|                             reportProperty("margin-bottom", display);
 | |
|                             reportProperty("float", display);
 | |
|                         }
 | |
| 
 | |
|                         //otherwise do nothing
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|         }
 | |
| 
 | |
|         parser.addListener("startrule", startRule);
 | |
|         parser.addListener("startfontface", startRule);
 | |
|         parser.addListener("startkeyframerule", startRule);
 | |
|         parser.addListener("startpagemargin", startRule);
 | |
|         parser.addListener("startpage", startRule);
 | |
| 
 | |
|         parser.addListener("property", function(event){
 | |
|             var name = event.property.text.toLowerCase();
 | |
| 
 | |
|             if (propertiesToCheck[name]){
 | |
|                 properties[name] = { value: event.value.text, line: event.property.line, col: event.property.col };
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", endRule);
 | |
|         parser.addListener("endfontface", endRule);
 | |
|         parser.addListener("endkeyframerule", endRule);
 | |
|         parser.addListener("endpagemargin", endRule);
 | |
|         parser.addListener("endpage", endRule);
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Disallow duplicate background-images (using url).
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "duplicate-background-images",
 | |
|     name: "Disallow duplicate background images",
 | |
|     desc: "Every background-image should be unique. Use a common class for e.g. sprites.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             stack = {};
 | |
| 
 | |
|         parser.addListener("property", function(event){
 | |
|             var name = event.property.text,
 | |
|                 value = event.value,
 | |
|                 i, len;
 | |
| 
 | |
|             if (name.match(/background/i)) {
 | |
|                 for (i=0, len=value.parts.length; i < len; i++) {
 | |
|                     if (value.parts[i].type == 'uri') {
 | |
|                         if (typeof stack[value.parts[i].uri] === 'undefined') {
 | |
|                             stack[value.parts[i].uri] = event;
 | |
|                         }
 | |
|                         else {
 | |
|                             reporter.report("Background image '" + value.parts[i].uri + "' was used multiple times, first declared at line " + stack[value.parts[i].uri].line + ", col " + stack[value.parts[i].uri].col + ".", event.line, event.col, rule);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| });
 | |
| /*
 | |
|  * Rule: Duplicate properties must appear one after the other. If an already-defined
 | |
|  * property appears somewhere else in the rule, then it's likely an error.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "duplicate-properties",
 | |
|     name: "Disallow duplicate properties",
 | |
|     desc: "Duplicate properties must appear one after the other.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             properties,
 | |
|             lastProperty;
 | |
| 
 | |
|         function startRule(event){
 | |
|             properties = {};
 | |
|         }
 | |
| 
 | |
|         parser.addListener("startrule", startRule);
 | |
|         parser.addListener("startfontface", startRule);
 | |
|         parser.addListener("startpage", startRule);
 | |
|         parser.addListener("startpagemargin", startRule);
 | |
|         parser.addListener("startkeyframerule", startRule);
 | |
| 
 | |
|         parser.addListener("property", function(event){
 | |
|             var property = event.property,
 | |
|                 name = property.text.toLowerCase();
 | |
| 
 | |
|             if (properties[name] && (lastProperty != name || properties[name] == event.value.text)){
 | |
|                 reporter.report("Duplicate property '" + event.property + "' found.", event.line, event.col, rule);
 | |
|             }
 | |
| 
 | |
|             properties[name] = event.value.text;
 | |
|             lastProperty = name;
 | |
| 
 | |
|         });
 | |
| 
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Style rules without any properties defined should be removed.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "empty-rules",
 | |
|     name: "Disallow empty rules",
 | |
|     desc: "Rules without any properties specified should be removed.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             count = 0;
 | |
| 
 | |
|         parser.addListener("startrule", function(){
 | |
|             count=0;
 | |
|         });
 | |
| 
 | |
|         parser.addListener("property", function(){
 | |
|             count++;
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", function(event){
 | |
|             var selectors = event.selectors;
 | |
|             if (count === 0){
 | |
|                 reporter.report("Rule is empty.", selectors[0].line, selectors[0].col, rule);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: There should be no syntax errors. (Duh.)
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "errors",
 | |
|     name: "Parsing Errors",
 | |
|     desc: "This rule looks for recoverable syntax errors.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
| 
 | |
|         parser.addListener("error", function(event){
 | |
|             reporter.error(event.message, event.line, event.col, rule);
 | |
|         });
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "fallback-colors",
 | |
|     name: "Require fallback colors",
 | |
|     desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.",
 | |
|     browsers: "IE6,IE7,IE8",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             lastProperty,
 | |
|             propertiesToCheck = {
 | |
|                 color: 1,
 | |
|                 background: 1,
 | |
|                 "background-color": 1
 | |
|             },
 | |
|             properties;
 | |
| 
 | |
|         function startRule(event){
 | |
|             properties = {};
 | |
|             lastProperty = null;
 | |
|         }
 | |
| 
 | |
|         parser.addListener("startrule", startRule);
 | |
|         parser.addListener("startfontface", startRule);
 | |
|         parser.addListener("startpage", startRule);
 | |
|         parser.addListener("startpagemargin", startRule);
 | |
|         parser.addListener("startkeyframerule", startRule);
 | |
| 
 | |
|         parser.addListener("property", function(event){
 | |
|             var property = event.property,
 | |
|                 name = property.text.toLowerCase(),
 | |
|                 parts = event.value.parts,
 | |
|                 i = 0,
 | |
|                 colorType = "",
 | |
|                 len = parts.length;
 | |
| 
 | |
|             if(propertiesToCheck[name]){
 | |
|                 while(i < len){
 | |
|                     if (parts[i].type == "color"){
 | |
|                         if ("alpha" in parts[i] || "hue" in parts[i]){
 | |
| 
 | |
|                             if (/([^\)]+)\(/.test(parts[i])){
 | |
|                                 colorType = RegExp.$1.toUpperCase();
 | |
|                             }
 | |
| 
 | |
|                             if (!lastProperty || (lastProperty.property.text.toLowerCase() != name || lastProperty.colorType != "compat")){
 | |
|                                 reporter.report("Fallback " + name + " (hex or RGB) should precede " + colorType + " " + name + ".", event.line, event.col, rule);
 | |
|                             }
 | |
|                         } else {
 | |
|                             event.colorType = "compat";
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     i++;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             lastProperty = event;
 | |
|         });
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: You shouldn't use more than 10 floats. If you do, there's probably
 | |
|  * room for some abstraction.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "floats",
 | |
|     name: "Disallow too many floats",
 | |
|     desc: "This rule tests if the float property is used too many times",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
|         var count = 0;
 | |
| 
 | |
|         //count how many times "float" is used
 | |
|         parser.addListener("property", function(event){
 | |
|             if (event.property.text.toLowerCase() == "float" &&
 | |
|                     event.value.text.toLowerCase() != "none"){
 | |
|                 count++;
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         //report the results
 | |
|         parser.addListener("endstylesheet", function(){
 | |
|             reporter.stat("floats", count);
 | |
|             if (count >= 10){
 | |
|                 reporter.rollupWarn("Too many floats (" + count + "), you're probably using them for layout. Consider using a grid system instead.", rule);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Avoid too many @font-face declarations in the same stylesheet.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "font-faces",
 | |
|     name: "Don't use too many web fonts",
 | |
|     desc: "Too many different web fonts in the same stylesheet.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             count = 0;
 | |
| 
 | |
| 
 | |
|         parser.addListener("startfontface", function(){
 | |
|             count++;
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endstylesheet", function(){
 | |
|             if (count > 5){
 | |
|                 reporter.rollupWarn("Too many @font-face declarations (" + count + ").", rule);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: You shouldn't need more than 9 font-size declarations.
 | |
|  */
 | |
| 
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "font-sizes",
 | |
|     name: "Disallow too many font sizes",
 | |
|     desc: "Checks the number of font-size declarations.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             count = 0;
 | |
| 
 | |
|         //check for use of "font-size"
 | |
|         parser.addListener("property", function(event){
 | |
|             if (event.property == "font-size"){
 | |
|                 count++;
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         //report the results
 | |
|         parser.addListener("endstylesheet", function(){
 | |
|             reporter.stat("font-sizes", count);
 | |
|             if (count >= 10){
 | |
|                 reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: When using a vendor-prefixed gradient, make sure to use them all.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "gradients",
 | |
|     name: "Require all gradient definitions",
 | |
|     desc: "When using a vendor-prefixed gradient, make sure to use them all.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             gradients;
 | |
| 
 | |
|         parser.addListener("startrule", function(){
 | |
|             gradients = {
 | |
|                 moz: 0,
 | |
|                 webkit: 0,
 | |
|                 oldWebkit: 0,
 | |
|                 ms: 0,
 | |
|                 o: 0
 | |
|             };
 | |
|         });
 | |
| 
 | |
|         parser.addListener("property", function(event){
 | |
| 
 | |
|             if (/\-(moz|ms|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)){
 | |
|                 gradients[RegExp.$1] = 1;
 | |
|             } else if (/\-webkit\-gradient/i.test(event.value)){
 | |
|                 gradients.oldWebkit = 1;
 | |
|             }
 | |
| 
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", function(event){
 | |
|             var missing = [];
 | |
| 
 | |
|             if (!gradients.moz){
 | |
|                 missing.push("Firefox 3.6+");
 | |
|             }
 | |
| 
 | |
|             if (!gradients.webkit){
 | |
|                 missing.push("Webkit (Safari 5+, Chrome)");
 | |
|             }
 | |
| 
 | |
|             if (!gradients.oldWebkit){
 | |
|                 missing.push("Old Webkit (Safari 4+, Chrome)");
 | |
|             }
 | |
| 
 | |
|             if (!gradients.ms){
 | |
|                 missing.push("Internet Explorer 10+");
 | |
|             }
 | |
| 
 | |
|             if (!gradients.o){
 | |
|                 missing.push("Opera 11.1+");
 | |
|             }
 | |
| 
 | |
|             if (missing.length && missing.length < 5){
 | |
|                 reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule);
 | |
|             }
 | |
| 
 | |
|         });
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Don't use IDs for selectors.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "ids",
 | |
|     name: "Disallow IDs in selectors",
 | |
|     desc: "Selectors should not contain IDs.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
|         parser.addListener("startrule", function(event){
 | |
|             var selectors = event.selectors,
 | |
|                 selector,
 | |
|                 part,
 | |
|                 modifier,
 | |
|                 idCount,
 | |
|                 i, j, k;
 | |
| 
 | |
|             for (i=0; i < selectors.length; i++){
 | |
|                 selector = selectors[i];
 | |
|                 idCount = 0;
 | |
| 
 | |
|                 for (j=0; j < selector.parts.length; j++){
 | |
|                     part = selector.parts[j];
 | |
|                     if (part.type == parser.SELECTOR_PART_TYPE){
 | |
|                         for (k=0; k < part.modifiers.length; k++){
 | |
|                             modifier = part.modifiers[k];
 | |
|                             if (modifier.type == "id"){
 | |
|                                 idCount++;
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (idCount == 1){
 | |
|                     reporter.report("Don't use IDs in selectors.", selector.line, selector.col, rule);
 | |
|                 } else if (idCount > 1){
 | |
|                     reporter.report(idCount + " IDs in the selector, really?", selector.line, selector.col, rule);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Don't use @import, use <link> instead.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "import",
 | |
|     name: "Disallow @import",
 | |
|     desc: "Don't use @import, use <link> instead.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
| 
 | |
|         parser.addListener("import", function(event){
 | |
|             reporter.report("@import prevents parallel downloads, use <link> instead.", event.line, event.col, rule);
 | |
|         });
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Make sure !important is not overused, this could lead to specificity
 | |
|  * war. Display a warning on !important declarations, an error if it's
 | |
|  * used more at least 10 times.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "important",
 | |
|     name: "Disallow !important",
 | |
|     desc: "Be careful when using !important declaration",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             count = 0;
 | |
| 
 | |
|         //warn that important is used and increment the declaration counter
 | |
|         parser.addListener("property", function(event){
 | |
|             if (event.important === true){
 | |
|                 count++;
 | |
|                 reporter.report("Use of !important", event.line, event.col, rule);
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         //if there are more than 10, show an error
 | |
|         parser.addListener("endstylesheet", function(){
 | |
|             reporter.stat("important", count);
 | |
|             if (count >= 10){
 | |
|                 reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specificity issues.", rule);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Properties should be known (listed in CSS3 specification) or
 | |
|  * be a vendor-prefixed property.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "known-properties",
 | |
|     name: "Require use of known properties",
 | |
|     desc: "Properties should be known (listed in CSS specification) or be a vendor-prefixed property.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             properties = {
 | |
| 
 | |
|                 "alignment-adjust": 1,
 | |
|                 "alignment-baseline": 1,
 | |
|                 "animation": 1,
 | |
|                 "animation-delay": 1,
 | |
|                 "animation-direction": 1,
 | |
|                 "animation-duration": 1,
 | |
|                 "animation-fill-mode": 1,
 | |
|                 "animation-iteration-count": 1,
 | |
|                 "animation-name": 1,
 | |
|                 "animation-play-state": 1,
 | |
|                 "animation-timing-function": 1,
 | |
|                 "appearance": 1,
 | |
|                 "azimuth": 1,
 | |
|                 "backface-visibility": 1,
 | |
|                 "background": 1,
 | |
|                 "background-attachment": 1,
 | |
|                 "background-break": 1,
 | |
|                 "background-clip": 1,
 | |
|                 "background-color": 1,
 | |
|                 "background-image": 1,
 | |
|                 "background-origin": 1,
 | |
|                 "background-position": 1,
 | |
|                 "background-repeat": 1,
 | |
|                 "background-size": 1,
 | |
|                 "baseline-shift": 1,
 | |
|                 "binding": 1,
 | |
|                 "bleed": 1,
 | |
|                 "bookmark-label": 1,
 | |
|                 "bookmark-level": 1,
 | |
|                 "bookmark-state": 1,
 | |
|                 "bookmark-target": 1,
 | |
|                 "border": 1,
 | |
|                 "border-bottom": 1,
 | |
|                 "border-bottom-color": 1,
 | |
|                 "border-bottom-left-radius": 1,
 | |
|                 "border-bottom-right-radius": 1,
 | |
|                 "border-bottom-style": 1,
 | |
|                 "border-bottom-width": 1,
 | |
|                 "border-collapse": 1,
 | |
|                 "border-color": 1,
 | |
|                 "border-image": 1,
 | |
|                 "border-image-outset": 1,
 | |
|                 "border-image-repeat": 1,
 | |
|                 "border-image-slice": 1,
 | |
|                 "border-image-source": 1,
 | |
|                 "border-image-width": 1,
 | |
|                 "border-left": 1,
 | |
|                 "border-left-color": 1,
 | |
|                 "border-left-style": 1,
 | |
|                 "border-left-width": 1,
 | |
|                 "border-radius": 1,
 | |
|                 "border-right": 1,
 | |
|                 "border-right-color": 1,
 | |
|                 "border-right-style": 1,
 | |
|                 "border-right-width": 1,
 | |
|                 "border-spacing": 1,
 | |
|                 "border-style": 1,
 | |
|                 "border-top": 1,
 | |
|                 "border-top-color": 1,
 | |
|                 "border-top-left-radius": 1,
 | |
|                 "border-top-right-radius": 1,
 | |
|                 "border-top-style": 1,
 | |
|                 "border-top-width": 1,
 | |
|                 "border-width": 1,
 | |
|                 "bottom": 1,
 | |
|                 "box-align": 1,
 | |
|                 "box-decoration-break": 1,
 | |
|                 "box-direction": 1,
 | |
|                 "box-flex": 1,
 | |
|                 "box-flex-group": 1,
 | |
|                 "box-lines": 1,
 | |
|                 "box-ordinal-group": 1,
 | |
|                 "box-orient": 1,
 | |
|                 "box-pack": 1,
 | |
|                 "box-shadow": 1,
 | |
|                 "box-sizing": 1,
 | |
|                 "break-after": 1,
 | |
|                 "break-before": 1,
 | |
|                 "break-inside": 1,
 | |
|                 "caption-side": 1,
 | |
|                 "clear": 1,
 | |
|                 "clip": 1,
 | |
|                 "color": 1,
 | |
|                 "color-profile": 1,
 | |
|                 "column-count": 1,
 | |
|                 "column-fill": 1,
 | |
|                 "column-gap": 1,
 | |
|                 "column-rule": 1,
 | |
|                 "column-rule-color": 1,
 | |
|                 "column-rule-style": 1,
 | |
|                 "column-rule-width": 1,
 | |
|                 "column-span": 1,
 | |
|                 "column-width": 1,
 | |
|                 "columns": 1,
 | |
|                 "content": 1,
 | |
|                 "counter-increment": 1,
 | |
|                 "counter-reset": 1,
 | |
|                 "crop": 1,
 | |
|                 "cue": 1,
 | |
|                 "cue-after": 1,
 | |
|                 "cue-before": 1,
 | |
|                 "cursor": 1,
 | |
|                 "direction": 1,
 | |
|                 "display": 1,
 | |
|                 "dominant-baseline": 1,
 | |
|                 "drop-initial-after-adjust": 1,
 | |
|                 "drop-initial-after-align": 1,
 | |
|                 "drop-initial-before-adjust": 1,
 | |
|                 "drop-initial-before-align": 1,
 | |
|                 "drop-initial-size": 1,
 | |
|                 "drop-initial-value": 1,
 | |
|                 "elevation": 1,
 | |
|                 "empty-cells": 1,
 | |
|                 "fit": 1,
 | |
|                 "fit-position": 1,
 | |
|                 "float": 1,
 | |
|                 "float-offset": 1,
 | |
|                 "font": 1,
 | |
|                 "font-family": 1,
 | |
|                 "font-size": 1,
 | |
|                 "font-size-adjust": 1,
 | |
|                 "font-stretch": 1,
 | |
|                 "font-style": 1,
 | |
|                 "font-variant": 1,
 | |
|                 "font-weight": 1,
 | |
|                 "grid-columns": 1,
 | |
|                 "grid-rows": 1,
 | |
|                 "hanging-punctuation": 1,
 | |
|                 "height": 1,
 | |
|                 "hyphenate-after": 1,
 | |
|                 "hyphenate-before": 1,
 | |
|                 "hyphenate-character": 1,
 | |
|                 "hyphenate-lines": 1,
 | |
|                 "hyphenate-resource": 1,
 | |
|                 "hyphens": 1,
 | |
|                 "icon": 1,
 | |
|                 "image-orientation": 1,
 | |
|                 "image-rendering": 1,
 | |
|                 "image-resolution": 1,
 | |
|                 "inline-box-align": 1,
 | |
|                 "left": 1,
 | |
|                 "letter-spacing": 1,
 | |
|                 "line-height": 1,
 | |
|                 "line-stacking": 1,
 | |
|                 "line-stacking-ruby": 1,
 | |
|                 "line-stacking-shift": 1,
 | |
|                 "line-stacking-strategy": 1,
 | |
|                 "list-style": 1,
 | |
|                 "list-style-image": 1,
 | |
|                 "list-style-position": 1,
 | |
|                 "list-style-type": 1,
 | |
|                 "margin": 1,
 | |
|                 "margin-bottom": 1,
 | |
|                 "margin-left": 1,
 | |
|                 "margin-right": 1,
 | |
|                 "margin-top": 1,
 | |
|                 "mark": 1,
 | |
|                 "mark-after": 1,
 | |
|                 "mark-before": 1,
 | |
|                 "marks": 1,
 | |
|                 "marquee-direction": 1,
 | |
|                 "marquee-play-count": 1,
 | |
|                 "marquee-speed": 1,
 | |
|                 "marquee-style": 1,
 | |
|                 "max-height": 1,
 | |
|                 "max-width": 1,
 | |
|                 "min-height": 1,
 | |
|                 "min-width": 1,
 | |
|                 "move-to": 1,
 | |
|                 "nav-down": 1,
 | |
|                 "nav-index": 1,
 | |
|                 "nav-left": 1,
 | |
|                 "nav-right": 1,
 | |
|                 "nav-up": 1,
 | |
|                 "opacity": 1,
 | |
|                 "orphans": 1,
 | |
|                 "outline": 1,
 | |
|                 "outline-color": 1,
 | |
|                 "outline-offset": 1,
 | |
|                 "outline-style": 1,
 | |
|                 "outline-width": 1,
 | |
|                 "overflow": 1,
 | |
|                 "overflow-style": 1,
 | |
|                 "overflow-x": 1,
 | |
|                 "overflow-y": 1,
 | |
|                 "padding": 1,
 | |
|                 "padding-bottom": 1,
 | |
|                 "padding-left": 1,
 | |
|                 "padding-right": 1,
 | |
|                 "padding-top": 1,
 | |
|                 "page": 1,
 | |
|                 "page-break-after": 1,
 | |
|                 "page-break-before": 1,
 | |
|                 "page-break-inside": 1,
 | |
|                 "page-policy": 1,
 | |
|                 "pause": 1,
 | |
|                 "pause-after": 1,
 | |
|                 "pause-before": 1,
 | |
|                 "perspective": 1,
 | |
|                 "perspective-origin": 1,
 | |
|                 "phonemes": 1,
 | |
|                 "pitch": 1,
 | |
|                 "pitch-range": 1,
 | |
|                 "play-during": 1,
 | |
|                 "position": 1,
 | |
|                 "presentation-level": 1,
 | |
|                 "punctuation-trim": 1,
 | |
|                 "quotes": 1,
 | |
|                 "rendering-intent": 1,
 | |
|                 "resize": 1,
 | |
|                 "rest": 1,
 | |
|                 "rest-after": 1,
 | |
|                 "rest-before": 1,
 | |
|                 "richness": 1,
 | |
|                 "right": 1,
 | |
|                 "rotation": 1,
 | |
|                 "rotation-point": 1,
 | |
|                 "ruby-align": 1,
 | |
|                 "ruby-overhang": 1,
 | |
|                 "ruby-position": 1,
 | |
|                 "ruby-span": 1,
 | |
|                 "size": 1,
 | |
|                 "speak": 1,
 | |
|                 "speak-header": 1,
 | |
|                 "speak-numeral": 1,
 | |
|                 "speak-punctuation": 1,
 | |
|                 "speech-rate": 1,
 | |
|                 "stress": 1,
 | |
|                 "string-set": 1,
 | |
|                 "table-layout": 1,
 | |
|                 "target": 1,
 | |
|                 "target-name": 1,
 | |
|                 "target-new": 1,
 | |
|                 "target-position": 1,
 | |
|                 "text-align": 1,
 | |
|                 "text-align-last": 1,
 | |
|                 "text-decoration": 1,
 | |
|                 "text-emphasis": 1,
 | |
|                 "text-height": 1,
 | |
|                 "text-indent": 1,
 | |
|                 "text-justify": 1,
 | |
|                 "text-outline": 1,
 | |
|                 "text-shadow": 1,
 | |
|                 "text-transform": 1,
 | |
|                 "text-wrap": 1,
 | |
|                 "top": 1,
 | |
|                 "transform": 1,
 | |
|                 "transform-origin": 1,
 | |
|                 "transform-style": 1,
 | |
|                 "transition": 1,
 | |
|                 "transition-delay": 1,
 | |
|                 "transition-duration": 1,
 | |
|                 "transition-property": 1,
 | |
|                 "transition-timing-function": 1,
 | |
|                 "unicode-bidi": 1,
 | |
|                 "user-modify": 1,
 | |
|                 "user-select": 1,
 | |
|                 "vertical-align": 1,
 | |
|                 "visibility": 1,
 | |
|                 "voice-balance": 1,
 | |
|                 "voice-duration": 1,
 | |
|                 "voice-family": 1,
 | |
|                 "voice-pitch": 1,
 | |
|                 "voice-pitch-range": 1,
 | |
|                 "voice-rate": 1,
 | |
|                 "voice-stress": 1,
 | |
|                 "voice-volume": 1,
 | |
|                 "volume": 1,
 | |
|                 "white-space": 1,
 | |
|                 "white-space-collapse": 1,
 | |
|                 "widows": 1,
 | |
|                 "width": 1,
 | |
|                 "word-break": 1,
 | |
|                 "word-spacing": 1,
 | |
|                 "word-wrap": 1,
 | |
|                 "z-index": 1,
 | |
| 
 | |
|                 //IE
 | |
|                 "filter": 1,
 | |
|                 "zoom": 1,
 | |
| 
 | |
|                 //@font-face
 | |
|                 "src": 1
 | |
|             };
 | |
| 
 | |
|         parser.addListener("property", function(event){
 | |
|             var name = event.property.text.toLowerCase();
 | |
| 
 | |
|             if (event.invalid) {
 | |
|                 reporter.report(event.invalid.message, event.line, event.col, rule);
 | |
|             }
 | |
|             //if (!properties[name] && name.charAt(0) != "-"){
 | |
|             //    reporter.error("Unknown property '" + event.property + "'.", event.line, event.col, rule);
 | |
|             //}
 | |
| 
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: outline: none or outline: 0 should only be used in a :focus rule
 | |
|  *       and only if there are other properties in the same rule.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "outline-none",
 | |
|     name: "Disallow outline: none",
 | |
|     desc: "Use of outline: none or outline: 0 should be limited to :focus rules.",
 | |
|     browsers: "All",
 | |
|     tags: ["Accessibility"],
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             lastRule;
 | |
| 
 | |
|         function startRule(event){
 | |
|             if (event.selectors){
 | |
|                 lastRule = {
 | |
|                     line: event.line,
 | |
|                     col: event.col,
 | |
|                     selectors: event.selectors,
 | |
|                     propCount: 0,
 | |
|                     outline: false
 | |
|                 };
 | |
|             } else {
 | |
|                 lastRule = null;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         function endRule(event){
 | |
|             if (lastRule){
 | |
|                 if (lastRule.outline){
 | |
|                     if (lastRule.selectors.toString().toLowerCase().indexOf(":focus") == -1){
 | |
|                         reporter.report("Outlines should only be modified using :focus.", lastRule.line, lastRule.col, rule);
 | |
|                     } else if (lastRule.propCount == 1) {
 | |
|                         reporter.report("Outlines shouldn't be hidden unless other visual changes are made.", lastRule.line, lastRule.col, rule);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         parser.addListener("startrule", startRule);
 | |
|         parser.addListener("startfontface", startRule);
 | |
|         parser.addListener("startpage", startRule);
 | |
|         parser.addListener("startpagemargin", startRule);
 | |
|         parser.addListener("startkeyframerule", startRule);
 | |
| 
 | |
|         parser.addListener("property", function(event){
 | |
|             var name = event.property.text.toLowerCase(),
 | |
|                 value = event.value;
 | |
| 
 | |
|             if (lastRule){
 | |
|                 lastRule.propCount++;
 | |
|                 if (name == "outline" && (value == "none" || value == "0")){
 | |
|                     lastRule.outline = true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", endRule);
 | |
|         parser.addListener("endfontface", endRule);
 | |
|         parser.addListener("endpage", endRule);
 | |
|         parser.addListener("endpagemargin", endRule);
 | |
|         parser.addListener("endkeyframerule", endRule);
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Don't use classes or IDs with elements (a.foo or a#foo).
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "overqualified-elements",
 | |
|     name: "Disallow overqualified elements",
 | |
|     desc: "Don't use classes or IDs with elements (a.foo or a#foo).",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             classes = {};
 | |
| 
 | |
|         parser.addListener("startrule", function(event){
 | |
|             var selectors = event.selectors,
 | |
|                 selector,
 | |
|                 part,
 | |
|                 modifier,
 | |
|                 i, j, k;
 | |
| 
 | |
|             for (i=0; i < selectors.length; i++){
 | |
|                 selector = selectors[i];
 | |
| 
 | |
|                 for (j=0; j < selector.parts.length; j++){
 | |
|                     part = selector.parts[j];
 | |
|                     if (part.type == parser.SELECTOR_PART_TYPE){
 | |
|                         for (k=0; k < part.modifiers.length; k++){
 | |
|                             modifier = part.modifiers[k];
 | |
|                             if (part.elementName && modifier.type == "id"){
 | |
|                                 reporter.report("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule);
 | |
|                             } else if (modifier.type == "class"){
 | |
| 
 | |
|                                 if (!classes[modifier]){
 | |
|                                     classes[modifier] = [];
 | |
|                                 }
 | |
|                                 classes[modifier].push({ modifier: modifier, part: part });
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endstylesheet", function(){
 | |
| 
 | |
|             var prop;
 | |
|             for (prop in classes){
 | |
|                 if (classes.hasOwnProperty(prop)){
 | |
| 
 | |
|                     //one use means that this is overqualified
 | |
|                     if (classes[prop].length == 1 && classes[prop][0].part.elementName){
 | |
|                         reporter.report("Element (" + classes[prop][0].part + ") is overqualified, just use " + classes[prop][0].modifier + " without element name.", classes[prop][0].part.line, classes[prop][0].part.col, rule);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Headings (h1-h6) should not be qualified (namespaced).
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "qualified-headings",
 | |
|     name: "Disallow qualified headings",
 | |
|     desc: "Headings should not be qualified (namespaced).",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
| 
 | |
|         parser.addListener("startrule", function(event){
 | |
|             var selectors = event.selectors,
 | |
|                 selector,
 | |
|                 part,
 | |
|                 i, j;
 | |
| 
 | |
|             for (i=0; i < selectors.length; i++){
 | |
|                 selector = selectors[i];
 | |
| 
 | |
|                 for (j=0; j < selector.parts.length; j++){
 | |
|                     part = selector.parts[j];
 | |
|                     if (part.type == parser.SELECTOR_PART_TYPE){
 | |
|                         if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0){
 | |
|                             reporter.report("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Selectors that look like regular expressions are slow and should be avoided.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "regex-selectors",
 | |
|     name: "Disallow selectors that look like regexs",
 | |
|     desc: "Selectors that look like regular expressions are slow and should be avoided.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
| 
 | |
|         parser.addListener("startrule", function(event){
 | |
|             var selectors = event.selectors,
 | |
|                 selector,
 | |
|                 part,
 | |
|                 modifier,
 | |
|                 i, j, k;
 | |
| 
 | |
|             for (i=0; i < selectors.length; i++){
 | |
|                 selector = selectors[i];
 | |
|                 for (j=0; j < selector.parts.length; j++){
 | |
|                     part = selector.parts[j];
 | |
|                     if (part.type == parser.SELECTOR_PART_TYPE){
 | |
|                         for (k=0; k < part.modifiers.length; k++){
 | |
|                             modifier = part.modifiers[k];
 | |
|                             if (modifier.type == "attribute"){
 | |
|                                 if (/([\~\|\^\$\*]=)/.test(modifier)){
 | |
|                                     reporter.report("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule);
 | |
|                                 }
 | |
|                             }
 | |
| 
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Total number of rules should not exceed x.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "rules-count",
 | |
|     name: "Rules Count",
 | |
|     desc: "Track how many rules there are.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             count = 0;
 | |
| 
 | |
|         //count each rule
 | |
|         parser.addListener("startrule", function(){
 | |
|             count++;
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endstylesheet", function(){
 | |
|             reporter.stat("rule-count", count);
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Use shorthand properties where possible.
 | |
|  *
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "shorthand",
 | |
|     name: "Require shorthand properties",
 | |
|     desc: "Use shorthand properties where possible.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             prop, i, len,
 | |
|             propertiesToCheck = {},
 | |
|             properties,
 | |
|             mapping = {
 | |
|                 "margin": [
 | |
|                     "margin-top",
 | |
|                     "margin-bottom",
 | |
|                     "margin-left",
 | |
|                     "margin-right"
 | |
|                 ],
 | |
|                 "padding": [
 | |
|                     "padding-top",
 | |
|                     "padding-bottom",
 | |
|                     "padding-left",
 | |
|                     "padding-right"
 | |
|                 ]
 | |
|             };
 | |
| 
 | |
|         //initialize propertiesToCheck
 | |
|         for (prop in mapping){
 | |
|             if (mapping.hasOwnProperty(prop)){
 | |
|                 for (i=0, len=mapping[prop].length; i < len; i++){
 | |
|                     propertiesToCheck[mapping[prop][i]] = prop;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         function startRule(event){
 | |
|             properties = {};
 | |
|         }
 | |
| 
 | |
|         //event handler for end of rules
 | |
|         function endRule(event){
 | |
| 
 | |
|             var prop, i, len, total;
 | |
| 
 | |
|             //check which properties this rule has
 | |
|             for (prop in mapping){
 | |
|                 if (mapping.hasOwnProperty(prop)){
 | |
|                     total=0;
 | |
| 
 | |
|                     for (i=0, len=mapping[prop].length; i < len; i++){
 | |
|                         total += properties[mapping[prop][i]] ? 1 : 0;
 | |
|                     }
 | |
| 
 | |
|                     if (total == mapping[prop].length){
 | |
|                         reporter.report("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         parser.addListener("startrule", startRule);
 | |
|         parser.addListener("startfontface", startRule);
 | |
| 
 | |
|         //check for use of "font-size"
 | |
|         parser.addListener("property", function(event){
 | |
|             var name = event.property.toString().toLowerCase(),
 | |
|                 value = event.value.parts[0].value;
 | |
| 
 | |
|             if (propertiesToCheck[name]){
 | |
|                 properties[name] = 1;
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", endRule);
 | |
|         parser.addListener("endfontface", endRule);
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Don't use properties with a star prefix.
 | |
|  *
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "star-property-hack",
 | |
|     name: "Disallow properties with a star prefix",
 | |
|     desc: "Checks for the star property hack (targets IE6/7)",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
| 
 | |
|         //check if property name starts with "*"
 | |
|         parser.addListener("property", function(event){
 | |
|             var property = event.property;
 | |
| 
 | |
|             if (property.hack == "*") {
 | |
|                 reporter.report("Property with star prefix found.", event.property.line, event.property.col, rule);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| });
 | |
| /*
 | |
|  * Rule: Don't use text-indent for image replacement if you need to support rtl.
 | |
|  *
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "text-indent",
 | |
|     name: "Disallow negative text-indent",
 | |
|     desc: "Checks for text indent less than -99px",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             textIndent,
 | |
|             direction;
 | |
| 
 | |
| 
 | |
|         function startRule(event){
 | |
|             textIndent = false;
 | |
|             direction = "inherit";
 | |
|         }
 | |
| 
 | |
|         //event handler for end of rules
 | |
|         function endRule(event){
 | |
|             if (textIndent && direction != "ltr"){
 | |
|                 reporter.report("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set direction for that item to ltr.", textIndent.line, textIndent.col, rule);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         parser.addListener("startrule", startRule);
 | |
|         parser.addListener("startfontface", startRule);
 | |
| 
 | |
|         //check for use of "font-size"
 | |
|         parser.addListener("property", function(event){
 | |
|             var name = event.property.toString().toLowerCase(),
 | |
|                 value = event.value;
 | |
| 
 | |
|             if (name == "text-indent" && value.parts[0].value < -99){
 | |
|                 textIndent = event.property;
 | |
|             } else if (name == "direction" && value == "ltr"){
 | |
|                 direction = "ltr";
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", endRule);
 | |
|         parser.addListener("endfontface", endRule);
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Don't use properties with a underscore prefix.
 | |
|  *
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "underscore-property-hack",
 | |
|     name: "Disallow properties with an underscore prefix",
 | |
|     desc: "Checks for the underscore property hack (targets IE6)",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
| 
 | |
|         //check if property name starts with "_"
 | |
|         parser.addListener("property", function(event){
 | |
|             var property = event.property;
 | |
| 
 | |
|             if (property.hack == "_") {
 | |
|                 reporter.report("Property with underscore prefix found.", event.property.line, event.property.col, rule);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| });
 | |
| /*
 | |
|  * Rule: Headings (h1-h6) should be defined only once.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "unique-headings",
 | |
|     name: "Headings should only be defined once",
 | |
|     desc: "Headings should be defined only once.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
| 
 | |
|         var headings =  {
 | |
|                 h1: 0,
 | |
|                 h2: 0,
 | |
|                 h3: 0,
 | |
|                 h4: 0,
 | |
|                 h5: 0,
 | |
|                 h6: 0
 | |
|             };
 | |
| 
 | |
|         parser.addListener("startrule", function(event){
 | |
|             var selectors = event.selectors,
 | |
|                 selector,
 | |
|                 part,
 | |
|                 pseudo,
 | |
|                 i, j;
 | |
| 
 | |
|             for (i=0; i < selectors.length; i++){
 | |
|                 selector = selectors[i];
 | |
|                 part = selector.parts[selector.parts.length-1];
 | |
| 
 | |
|                 if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())){
 | |
| 
 | |
|                     for (j=0; j < part.modifiers.length; j++){
 | |
|                         if (part.modifiers[j].type == "pseudo"){
 | |
|                             pseudo = true;
 | |
|                             break;
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     if (!pseudo){
 | |
|                         headings[RegExp.$1]++;
 | |
|                         if (headings[RegExp.$1] > 1) {
 | |
|                             reporter.report("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endstylesheet", function(event){
 | |
|             var prop,
 | |
|                 messages = [];
 | |
| 
 | |
|             for (prop in headings){
 | |
|                 if (headings.hasOwnProperty(prop)){
 | |
|                     if (headings[prop] > 1){
 | |
|                         messages.push(headings[prop] + " " + prop + "s");
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (messages.length){
 | |
|                 reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Don't use universal selector because it's slow.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "universal-selector",
 | |
|     name: "Disallow universal selector",
 | |
|     desc: "The universal selector (*) is known to be slow.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
| 
 | |
|         parser.addListener("startrule", function(event){
 | |
|             var selectors = event.selectors,
 | |
|                 selector,
 | |
|                 part,
 | |
|                 modifier,
 | |
|                 i, j, k;
 | |
| 
 | |
|             for (i=0; i < selectors.length; i++){
 | |
|                 selector = selectors[i];
 | |
| 
 | |
|                 part = selector.parts[selector.parts.length-1];
 | |
|                 if (part.elementName == "*"){
 | |
|                     reporter.report(rule.desc, part.line, part.col, rule);
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: Don't use unqualified attribute selectors because they're just like universal selectors.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "unqualified-attributes",
 | |
|     name: "Disallow unqualified attribute selectors",
 | |
|     desc: "Unqualified attribute selectors are known to be slow.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
| 
 | |
|         parser.addListener("startrule", function(event){
 | |
| 
 | |
|             var selectors = event.selectors,
 | |
|                 selector,
 | |
|                 part,
 | |
|                 modifier,
 | |
|                 i, j, k;
 | |
| 
 | |
|             for (i=0; i < selectors.length; i++){
 | |
|                 selector = selectors[i];
 | |
| 
 | |
|                 part = selector.parts[selector.parts.length-1];
 | |
|                 if (part.type == parser.SELECTOR_PART_TYPE){
 | |
|                     for (k=0; k < part.modifiers.length; k++){
 | |
|                         modifier = part.modifiers[k];
 | |
|                         if (modifier.type == "attribute" && (!part.elementName || part.elementName == "*")){
 | |
|                             reporter.report(rule.desc, part.line, part.col, rule);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: When using a vendor-prefixed property, make sure to
 | |
|  * include the standard one.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "vendor-prefix",
 | |
|     name: "Require standard property with vendor prefix",
 | |
|     desc: "When using a vendor-prefixed property, make sure to include the standard one.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this,
 | |
|             properties,
 | |
|             num,
 | |
|             propertiesToCheck = {
 | |
|                 "-webkit-border-radius": "border-radius",
 | |
|                 "-webkit-border-top-left-radius": "border-top-left-radius",
 | |
|                 "-webkit-border-top-right-radius": "border-top-right-radius",
 | |
|                 "-webkit-border-bottom-left-radius": "border-bottom-left-radius",
 | |
|                 "-webkit-border-bottom-right-radius": "border-bottom-right-radius",
 | |
| 
 | |
|                 "-o-border-radius": "border-radius",
 | |
|                 "-o-border-top-left-radius": "border-top-left-radius",
 | |
|                 "-o-border-top-right-radius": "border-top-right-radius",
 | |
|                 "-o-border-bottom-left-radius": "border-bottom-left-radius",
 | |
|                 "-o-border-bottom-right-radius": "border-bottom-right-radius",
 | |
| 
 | |
|                 "-moz-border-radius": "border-radius",
 | |
|                 "-moz-border-radius-topleft": "border-top-left-radius",
 | |
|                 "-moz-border-radius-topright": "border-top-right-radius",
 | |
|                 "-moz-border-radius-bottomleft": "border-bottom-left-radius",
 | |
|                 "-moz-border-radius-bottomright": "border-bottom-right-radius",
 | |
| 
 | |
|                 "-moz-column-count": "column-count",
 | |
|                 "-webkit-column-count": "column-count",
 | |
| 
 | |
|                 "-moz-column-gap": "column-gap",
 | |
|                 "-webkit-column-gap": "column-gap",
 | |
| 
 | |
|                 "-moz-column-rule": "column-rule",
 | |
|                 "-webkit-column-rule": "column-rule",
 | |
| 
 | |
|                 "-moz-column-rule-style": "column-rule-style",
 | |
|                 "-webkit-column-rule-style": "column-rule-style",
 | |
| 
 | |
|                 "-moz-column-rule-color": "column-rule-color",
 | |
|                 "-webkit-column-rule-color": "column-rule-color",
 | |
| 
 | |
|                 "-moz-column-rule-width": "column-rule-width",
 | |
|                 "-webkit-column-rule-width": "column-rule-width",
 | |
| 
 | |
|                 "-moz-column-width": "column-width",
 | |
|                 "-webkit-column-width": "column-width",
 | |
| 
 | |
|                 "-webkit-column-span": "column-span",
 | |
|                 "-webkit-columns": "columns",
 | |
| 
 | |
|                 "-moz-box-shadow": "box-shadow",
 | |
|                 "-webkit-box-shadow": "box-shadow",
 | |
| 
 | |
|                 "-moz-transform" : "transform",
 | |
|                 "-webkit-transform" : "transform",
 | |
|                 "-o-transform" : "transform",
 | |
|                 "-ms-transform" : "transform",
 | |
| 
 | |
|                 "-moz-transform-origin" : "transform-origin",
 | |
|                 "-webkit-transform-origin" : "transform-origin",
 | |
|                 "-o-transform-origin" : "transform-origin",
 | |
|                 "-ms-transform-origin" : "transform-origin",
 | |
| 
 | |
|                 "-moz-box-sizing" : "box-sizing",
 | |
|                 "-webkit-box-sizing" : "box-sizing",
 | |
| 
 | |
|                 "-moz-user-select" : "user-select",
 | |
|                 "-khtml-user-select" : "user-select",
 | |
|                 "-webkit-user-select" : "user-select"
 | |
|             };
 | |
| 
 | |
|         //event handler for beginning of rules
 | |
|         function startRule(){
 | |
|             properties = {};
 | |
|             num=1;
 | |
|         }
 | |
| 
 | |
|         //event handler for end of rules
 | |
|         function endRule(event){
 | |
|             var prop,
 | |
|                 i, len,
 | |
|                 standard,
 | |
|                 needed,
 | |
|                 actual,
 | |
|                 needsStandard = [];
 | |
| 
 | |
|             for (prop in properties){
 | |
|                 if (propertiesToCheck[prop]){
 | |
|                     needsStandard.push({ actual: prop, needed: propertiesToCheck[prop]});
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             for (i=0, len=needsStandard.length; i < len; i++){
 | |
|                 needed = needsStandard[i].needed;
 | |
|                 actual = needsStandard[i].actual;
 | |
| 
 | |
|                 if (!properties[needed]){
 | |
|                     reporter.report("Missing standard property '" + needed + "' to go along with '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
 | |
|                 } else {
 | |
|                     //make sure standard property is last
 | |
|                     if (properties[needed][0].pos < properties[actual][0].pos){
 | |
|                         reporter.report("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|         }
 | |
| 
 | |
|         parser.addListener("startrule", startRule);
 | |
|         parser.addListener("startfontface", startRule);
 | |
|         parser.addListener("startpage", startRule);
 | |
|         parser.addListener("startpagemargin", startRule);
 | |
|         parser.addListener("startkeyframerule", startRule);
 | |
| 
 | |
|         parser.addListener("property", function(event){
 | |
|             var name = event.property.text.toLowerCase();
 | |
| 
 | |
|             if (!properties[name]){
 | |
|                 properties[name] = [];
 | |
|             }
 | |
| 
 | |
|             properties[name].push({ name: event.property, value : event.value, pos:num++ });
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", endRule);
 | |
|         parser.addListener("endfontface", endRule);
 | |
|         parser.addListener("endpage", endRule);
 | |
|         parser.addListener("endpagemargin", endRule);
 | |
|         parser.addListener("endkeyframerule", endRule);
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*
 | |
|  * Rule: You don't need to specify units when a value is 0.
 | |
|  */
 | |
| /*global CSSLint*/
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     //rule information
 | |
|     id: "zero-units",
 | |
|     name: "Disallow units for 0 values",
 | |
|     desc: "You don't need to specify units when a value is 0.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     //initialization
 | |
|     init: function(parser, reporter){
 | |
|         var rule = this;
 | |
| 
 | |
|         //count how many times "float" is used
 | |
|         parser.addListener("property", function(event){
 | |
|             var parts = event.value.parts,
 | |
|                 i = 0,
 | |
|                 len = parts.length;
 | |
| 
 | |
|             while(i < len){
 | |
|                 if ((parts[i].units || parts[i].type == "percentage") && parts[i].value === 0 && parts[i].type != "time"){
 | |
|                     reporter.report("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule);
 | |
|                 }
 | |
|                 i++;
 | |
|             }
 | |
| 
 | |
|         });
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| /*global CSSLint*/
 | |
| (function() {
 | |
| 
 | |
|     /**
 | |
|      * Replace special characters before write to output.
 | |
|      *
 | |
|      * Rules:
 | |
|      *  - single quotes is the escape sequence for double-quotes
 | |
|      *  - & is the escape sequence for &
 | |
|      *  - < is the escape sequence for <
 | |
|      *  - > is the escape sequence for >
 | |
|      *
 | |
|      * @param {String} message to escape
 | |
|      * @return escaped message as {String}
 | |
|      */
 | |
|     var xmlEscape = function(str) {
 | |
|         if (!str || str.constructor !== String) {
 | |
|             return "";
 | |
|         }
 | |
| 
 | |
|         return str.replace(/[\"&><]/g, function(match) {
 | |
|             switch (match) {
 | |
|                 case "\"":
 | |
|                     return """;
 | |
|                 case "&":
 | |
|                     return "&";
 | |
|                 case "<":
 | |
|                     return "<";
 | |
|                 case ">":
 | |
|                     return ">";
 | |
|             }
 | |
|         });
 | |
|     };
 | |
| 
 | |
|     CSSLint.addFormatter({
 | |
|         //format information
 | |
|         id: "checkstyle-xml",
 | |
|         name: "Checkstyle XML format",
 | |
| 
 | |
|         /**
 | |
|          * Return opening root XML tag.
 | |
|          * @return {String} to prepend before all results
 | |
|          */
 | |
|         startFormat: function(){
 | |
|             return "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle>";
 | |
|         },
 | |
| 
 | |
|         /**
 | |
|          * Return closing root XML tag.
 | |
|          * @return {String} to append after all results
 | |
|          */
 | |
|         endFormat: function(){
 | |
|             return "</checkstyle>";
 | |
|         },
 | |
| 
 | |
|         /**
 | |
|          * Returns message when there is a file read error.
 | |
|          * @param {String} filename The name of the file that caused the error.
 | |
|          * @param {String} message The error message
 | |
|          * @return {String} The error message.
 | |
|          */
 | |
|         readError: function(filename, message) {
 | |
|             return "<file name=\"" + xmlEscape(filename) + "\"><error line=\"0\" column=\"0\" severty=\"error\" message=\"" + xmlEscape(message) + "\"></error></file>";
 | |
|         },
 | |
| 
 | |
|         /**
 | |
|          * Given CSS Lint results for a file, return output for this format.
 | |
|          * @param results {Object} with error and warning messages
 | |
|          * @param filename {String} relative file path
 | |
|          * @param options {Object} (UNUSED for now) specifies special handling of output
 | |
|          * @return {String} output for results
 | |
|          */
 | |
|         formatResults: function(results, filename, options) {
 | |
|             var messages = results.messages,
 | |
|                 output = [];
 | |
| 
 | |
|             /**
 | |
|              * Generate a source string for a rule.
 | |
|              * Checkstyle source strings usually resemble Java class names e.g
 | |
|              * net.csslint.SomeRuleName
 | |
|              * @param {Object} rule
 | |
|              * @return rule source as {String}
 | |
|              */
 | |
|             var generateSource = function(rule) {
 | |
|                 if (!rule || !('name' in rule)) {
 | |
|                     return "";
 | |
|                 }
 | |
|                 return 'net.csslint.' + rule.name.replace(/\s/g,'');
 | |
|             };
 | |
| 
 | |
| 
 | |
| 
 | |
|             if (messages.length > 0) {
 | |
|                 output.push("<file name=\""+filename+"\">");
 | |
|                 CSSLint.Util.forEach(messages, function (message, i) {
 | |
|                     //ignore rollups for now
 | |
|                     if (!message.rollup) {
 | |
|                       output.push("<error line=\"" + message.line + "\" column=\"" + message.col + "\" severity=\"" + message.type + "\"" +
 | |
|                           " message=\"" + xmlEscape(message.message) + "\" source=\"" + generateSource(message.rule) +"\"/>");
 | |
|                     }
 | |
|                 });
 | |
|                 output.push("</file>");
 | |
|             }
 | |
| 
 | |
|             return output.join("");
 | |
|         }
 | |
|     });
 | |
| 
 | |
| }());
 | |
| /*global CSSLint*/
 | |
| CSSLint.addFormatter({
 | |
|     //format information
 | |
|     id: "compact",
 | |
|     name: "Compact, 'porcelain' format",
 | |
| 
 | |
|     /**
 | |
|      * Return content to be printed before all file results.
 | |
|      * @return {String} to prepend before all results
 | |
|      */
 | |
|     startFormat: function() {
 | |
|         return "";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Return content to be printed after all file results.
 | |
|      * @return {String} to append after all results
 | |
|      */
 | |
|     endFormat: function() {
 | |
|         return "";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Given CSS Lint results for a file, return output for this format.
 | |
|      * @param results {Object} with error and warning messages
 | |
|      * @param filename {String} relative file path
 | |
|      * @param options {Object} (Optional) specifies special handling of output
 | |
|      * @return {String} output for results
 | |
|      */
 | |
|     formatResults: function(results, filename, options) {
 | |
|         var messages = results.messages,
 | |
|             output = "";
 | |
|         options = options || {};
 | |
| 
 | |
|         /**
 | |
|          * Capitalize and return given string.
 | |
|          * @param str {String} to capitalize
 | |
|          * @return {String} capitalized
 | |
|          */
 | |
|         var capitalize = function(str) {
 | |
|             return str.charAt(0).toUpperCase() + str.slice(1);
 | |
|         };
 | |
| 
 | |
|         if (messages.length === 0) {
 | |
|             return options.quiet ? "" : filename + ": Lint Free!";
 | |
|         }
 | |
| 
 | |
|         CSSLint.Util.forEach(messages, function(message, i) {
 | |
|             if (message.rollup) {
 | |
|                 output += filename + ": " + capitalize(message.type) + " - " + message.message + "\n";
 | |
|             } else {
 | |
|                 output += filename + ": " + "line " + message.line +
 | |
|                     ", col " + message.col + ", " + capitalize(message.type) + " - " + message.message + "\n";
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         return output;
 | |
|     }
 | |
| });
 | |
| /*global CSSLint*/
 | |
| CSSLint.addFormatter({
 | |
|     //format information
 | |
|     id: "csslint-xml",
 | |
|     name: "CSSLint XML format",
 | |
| 
 | |
|     /**
 | |
|      * Return opening root XML tag.
 | |
|      * @return {String} to prepend before all results
 | |
|      */
 | |
|     startFormat: function(){
 | |
|         return "<?xml version=\"1.0\" encoding=\"utf-8\"?><csslint>";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Return closing root XML tag.
 | |
|      * @return {String} to append after all results
 | |
|      */
 | |
|     endFormat: function(){
 | |
|         return "</csslint>";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Given CSS Lint results for a file, return output for this format.
 | |
|      * @param results {Object} with error and warning messages
 | |
|      * @param filename {String} relative file path
 | |
|      * @param options {Object} (UNUSED for now) specifies special handling of output
 | |
|      * @return {String} output for results
 | |
|      */
 | |
|     formatResults: function(results, filename, options) {
 | |
|         var messages = results.messages,
 | |
|             output = [];
 | |
| 
 | |
|         /**
 | |
|          * Replace special characters before write to output.
 | |
|          *
 | |
|          * Rules:
 | |
|          *  - single quotes is the escape sequence for double-quotes
 | |
|          *  - & is the escape sequence for &
 | |
|          *  - < is the escape sequence for <
 | |
|          *  - > is the escape sequence for >
 | |
|          *
 | |
|          * @param {String} message to escape
 | |
|          * @return escaped message as {String}
 | |
|          */
 | |
|         var escapeSpecialCharacters = function(str) {
 | |
|             if (!str || str.constructor !== String) {
 | |
|                 return "";
 | |
|             }
 | |
|             return str.replace(/\"/g, "'").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
 | |
|         };
 | |
| 
 | |
|         if (messages.length > 0) {
 | |
|             output.push("<file name=\""+filename+"\">");
 | |
|             CSSLint.Util.forEach(messages, function (message, i) {
 | |
|                 if (message.rollup) {
 | |
|                     output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
 | |
|                 } else {
 | |
|                     output.push("<issue line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" +
 | |
|                         " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
 | |
|                 }
 | |
|             });
 | |
|             output.push("</file>");
 | |
|         }
 | |
| 
 | |
|         return output.join("");
 | |
|     }
 | |
| });
 | |
| /*global CSSLint*/
 | |
| CSSLint.addFormatter({
 | |
|     //format information
 | |
|     id: "lint-xml",
 | |
|     name: "Lint XML format",
 | |
| 
 | |
|     /**
 | |
|      * Return opening root XML tag.
 | |
|      * @return {String} to prepend before all results
 | |
|      */
 | |
|     startFormat: function(){
 | |
|         return "<?xml version=\"1.0\" encoding=\"utf-8\"?><lint>";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Return closing root XML tag.
 | |
|      * @return {String} to append after all results
 | |
|      */
 | |
|     endFormat: function(){
 | |
|         return "</lint>";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Given CSS Lint results for a file, return output for this format.
 | |
|      * @param results {Object} with error and warning messages
 | |
|      * @param filename {String} relative file path
 | |
|      * @param options {Object} (UNUSED for now) specifies special handling of output
 | |
|      * @return {String} output for results
 | |
|      */
 | |
|     formatResults: function(results, filename, options) {
 | |
|         var messages = results.messages,
 | |
|             output = [];
 | |
| 
 | |
|         /**
 | |
|          * Replace special characters before write to output.
 | |
|          *
 | |
|          * Rules:
 | |
|          *  - single quotes is the escape sequence for double-quotes
 | |
|          *  - & is the escape sequence for &
 | |
|          *  - < is the escape sequence for <
 | |
|          *  - > is the escape sequence for >
 | |
|          *
 | |
|          * @param {String} message to escape
 | |
|          * @return escaped message as {String}
 | |
|          */
 | |
|         var escapeSpecialCharacters = function(str) {
 | |
|             if (!str || str.constructor !== String) {
 | |
|                 return "";
 | |
|             }
 | |
|             return str.replace(/\"/g, "'").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
 | |
|         };
 | |
| 
 | |
|         if (messages.length > 0) {
 | |
| 
 | |
|             output.push("<file name=\""+filename+"\">");
 | |
|             CSSLint.Util.forEach(messages, function (message, i) {
 | |
|                 if (message.rollup) {
 | |
|                     output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
 | |
|                 } else {
 | |
|                     output.push("<issue line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" +
 | |
|                         " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
 | |
|                 }
 | |
|             });
 | |
|             output.push("</file>");
 | |
|         }
 | |
| 
 | |
|         return output.join("");
 | |
|     }
 | |
| });
 | |
| /*global CSSLint*/
 | |
| CSSLint.addFormatter({
 | |
|     //format information
 | |
|     id: "text",
 | |
|     name: "Plain Text",
 | |
| 
 | |
|     /**
 | |
|      * Return content to be printed before all file results.
 | |
|      * @return {String} to prepend before all results
 | |
|      */
 | |
|     startFormat: function() {
 | |
|         return "";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Return content to be printed after all file results.
 | |
|      * @return {String} to append after all results
 | |
|      */
 | |
|     endFormat: function() {
 | |
|         return "";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Given CSS Lint results for a file, return output for this format.
 | |
|      * @param results {Object} with error and warning messages
 | |
|      * @param filename {String} relative file path
 | |
|      * @param options {Object} (Optional) specifies special handling of output
 | |
|      * @return {String} output for results
 | |
|      */
 | |
|     formatResults: function(results, filename, options) {
 | |
|         var messages = results.messages,
 | |
|             output = "";
 | |
|         options = options || {};
 | |
| 
 | |
|         if (messages.length === 0) {
 | |
|             return options.quiet ? "" : "\n\ncsslint: No errors in " + filename + ".";
 | |
|         }
 | |
| 
 | |
|         output = "\n\ncsslint: There are " + messages.length  +  " problems in " + filename + ".";
 | |
|         var pos = filename.lastIndexOf("/"),
 | |
|             shortFilename = filename;
 | |
| 
 | |
|         if (pos === -1){
 | |
|             pos = filename.lastIndexOf("\\");
 | |
|         }
 | |
|         if (pos > -1){
 | |
|             shortFilename = filename.substring(pos+1);
 | |
|         }
 | |
| 
 | |
|         CSSLint.Util.forEach(messages, function (message, i) {
 | |
|             output = output + "\n\n" + shortFilename;
 | |
|             if (message.rollup) {
 | |
|                 output += "\n" + (i+1) + ": " + message.type;
 | |
|                 output += "\n" + message.message;
 | |
|             } else {
 | |
|                 output += "\n" + (i+1) + ": " + message.type + " at line " + message.line + ", col " + message.col;
 | |
|                 output += "\n" + message.message;
 | |
|                 output += "\n" + message.evidence;
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         return output;
 | |
|     }
 | |
| });
 | |
| 
 | |
| 
 | |
| exports.CSSLint = CSSLint;
 | |
| 
 |