10859 lines
		
	
	
		
			No EOL
		
	
	
		
			369 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			10859 lines
		
	
	
		
			No EOL
		
	
	
		
			369 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*!
 | |
| CSSLint v1.0.4
 | |
| Copyright (c) 2016 Nicole Sullivan and Nicholas C. Zakas. All rights reserved.
 | |
| 
 | |
| Permission is hereby granted, free of charge, to any person obtaining a copy
 | |
| of this software and associated documentation files (the 'Software'), to deal
 | |
| 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.
 | |
| 
 | |
| */
 | |
| 
 | |
| var CSSLint = (function(){
 | |
|   var module = module || {},
 | |
|       exports = exports || {};
 | |
| 
 | |
| /*!
 | |
| Parser-Lib
 | |
| Copyright (c) 2009-2016 Nicholas C. Zakas. All rights reserved.
 | |
| 
 | |
| Permission is hereby granted, free of charge, to any person obtaining a copy
 | |
| of this software and associated documentation files (the "Software"), to deal
 | |
| 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 v1.1.0, Build time: 6-December-2016 10:31:29 */
 | |
| var parserlib = (function () {
 | |
| var require;
 | |
| require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| /* exported Colors */
 | |
| 
 | |
| var Colors = module.exports = {
 | |
|     __proto__       :null,
 | |
|     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",
 | |
|     darkgrey        :"#a9a9a9",
 | |
|     darkgreen       :"#006400",
 | |
|     darkkhaki       :"#bdb76b",
 | |
|     darkmagenta     :"#8b008b",
 | |
|     darkolivegreen  :"#556b2f",
 | |
|     darkorange      :"#ff8c00",
 | |
|     darkorchid      :"#9932cc",
 | |
|     darkred         :"#8b0000",
 | |
|     darksalmon      :"#e9967a",
 | |
|     darkseagreen    :"#8fbc8f",
 | |
|     darkslateblue   :"#483d8b",
 | |
|     darkslategray   :"#2f4f4f",
 | |
|     darkslategrey   :"#2f4f4f",
 | |
|     darkturquoise   :"#00ced1",
 | |
|     darkviolet      :"#9400d3",
 | |
|     deeppink        :"#ff1493",
 | |
|     deepskyblue     :"#00bfff",
 | |
|     dimgray         :"#696969",
 | |
|     dimgrey         :"#696969",
 | |
|     dodgerblue      :"#1e90ff",
 | |
|     firebrick       :"#b22222",
 | |
|     floralwhite     :"#fffaf0",
 | |
|     forestgreen     :"#228b22",
 | |
|     fuchsia         :"#ff00ff",
 | |
|     gainsboro       :"#dcdcdc",
 | |
|     ghostwhite      :"#f8f8ff",
 | |
|     gold            :"#ffd700",
 | |
|     goldenrod       :"#daa520",
 | |
|     gray            :"#808080",
 | |
|     grey            :"#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",
 | |
|     lightgrey       :"#d3d3d3",
 | |
|     lightgreen      :"#90ee90",
 | |
|     lightpink       :"#ffb6c1",
 | |
|     lightsalmon     :"#ffa07a",
 | |
|     lightseagreen   :"#20b2aa",
 | |
|     lightskyblue    :"#87cefa",
 | |
|     lightslategray  :"#778899",
 | |
|     lightslategrey  :"#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",
 | |
|     slategrey       :"#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",
 | |
|     //'currentColor' color keyword https://www.w3.org/TR/css3-color/#currentcolor
 | |
|     currentColor        :"The value of the 'color' property.",
 | |
|     //CSS2 system colors https://www.w3.org/TR/css3-color/#css2-system
 | |
|     activeBorder        :"Active window border.",
 | |
|     activecaption       :"Active window caption.",
 | |
|     appworkspace        :"Background color of multiple document interface.",
 | |
|     background          :"Desktop background.",
 | |
|     buttonface          :"The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.",
 | |
|     buttonhighlight     :"The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
 | |
|     buttonshadow        :"The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
 | |
|     buttontext          :"Text on push buttons.",
 | |
|     captiontext         :"Text in caption, size box, and scrollbar arrow box.",
 | |
|     graytext            :"Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.",
 | |
|     greytext            :"Greyed (disabled) text. This color is set to #000 if the current display driver does not support a solid grey color.",
 | |
|     highlight           :"Item(s) selected in a control.",
 | |
|     highlighttext       :"Text of item(s) selected in a control.",
 | |
|     inactiveborder      :"Inactive window border.",
 | |
|     inactivecaption     :"Inactive window caption.",
 | |
|     inactivecaptiontext :"Color of text in an inactive caption.",
 | |
|     infobackground      :"Background color for tooltip controls.",
 | |
|     infotext            :"Text color for tooltip controls.",
 | |
|     menu                :"Menu background.",
 | |
|     menutext            :"Text in menus.",
 | |
|     scrollbar           :"Scroll bar gray area.",
 | |
|     threeddarkshadow    :"The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
 | |
|     threedface          :"The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
 | |
|     threedhighlight     :"The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
 | |
|     threedlightshadow   :"The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
 | |
|     threedshadow        :"The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
 | |
|     window              :"Window background.",
 | |
|     windowframe         :"Window frame.",
 | |
|     windowtext          :"Text in windows."
 | |
| };
 | |
| 
 | |
| },{}],2:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = Combinator;
 | |
| 
 | |
| var SyntaxUnit = require("../util/SyntaxUnit");
 | |
| 
 | |
| var Parser = require("./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;
 | |
| 
 | |
| 
 | |
| },{"../util/SyntaxUnit":26,"./Parser":6}],3:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = Matcher;
 | |
| 
 | |
| var StringReader = require("../util/StringReader");
 | |
| var SyntaxError = require("../util/SyntaxError");
 | |
| 
 | |
| /**
 | |
|  * This class implements a combinator library for matcher functions.
 | |
|  * The combinators are described at:
 | |
|  * https://developer.mozilla.org/en-US/docs/Web/CSS/Value_definition_syntax#Component_value_combinators
 | |
|  */
 | |
| function Matcher(matchFunc, toString) {
 | |
|     this.match = function(expression) {
 | |
|         // Save/restore marks to ensure that failed matches always restore
 | |
|         // the original location in the expression.
 | |
|         var result;
 | |
|         expression.mark();
 | |
|         result = matchFunc(expression);
 | |
|         if (result) {
 | |
|             expression.drop();
 | |
|         } else {
 | |
|             expression.restore();
 | |
|         }
 | |
|         return result;
 | |
|     };
 | |
|     this.toString = typeof toString === "function" ? toString : function() {
 | |
|         return toString;
 | |
|     };
 | |
| }
 | |
| 
 | |
| /** Precedence table of combinators. */
 | |
| Matcher.prec = {
 | |
|     MOD:    5,
 | |
|     SEQ:    4,
 | |
|     ANDAND: 3,
 | |
|     OROR:   2,
 | |
|     ALT:    1
 | |
| };
 | |
| 
 | |
| /** Simple recursive-descent grammar to build matchers from strings. */
 | |
| Matcher.parse = function(str) {
 | |
|     var reader, eat, expr, oror, andand, seq, mod, term, result;
 | |
|     reader = new StringReader(str);
 | |
|     eat = function(matcher) {
 | |
|         var result = reader.readMatch(matcher);
 | |
|         if (result === null) {
 | |
|             throw new SyntaxError(
 | |
|                 "Expected "+matcher, reader.getLine(), reader.getCol());
 | |
|         }
 | |
|         return result;
 | |
|     };
 | |
|     expr = function() {
 | |
|         // expr = oror (" | " oror)*
 | |
|         var m = [ oror() ];
 | |
|         while (reader.readMatch(" | ") !== null) {
 | |
|             m.push(oror());
 | |
|         }
 | |
|         return m.length === 1 ? m[0] : Matcher.alt.apply(Matcher, m);
 | |
|     };
 | |
|     oror = function() {
 | |
|         // oror = andand ( " || " andand)*
 | |
|         var m = [ andand() ];
 | |
|         while (reader.readMatch(" || ") !== null) {
 | |
|             m.push(andand());
 | |
|         }
 | |
|         return m.length === 1 ? m[0] : Matcher.oror.apply(Matcher, m);
 | |
|     };
 | |
|     andand = function() {
 | |
|         // andand = seq ( " && " seq)*
 | |
|         var m = [ seq() ];
 | |
|         while (reader.readMatch(" && ") !== null) {
 | |
|             m.push(seq());
 | |
|         }
 | |
|         return m.length === 1 ? m[0] : Matcher.andand.apply(Matcher, m);
 | |
|     };
 | |
|     seq = function() {
 | |
|         // seq = mod ( " " mod)*
 | |
|         var m = [ mod() ];
 | |
|         while (reader.readMatch(/^ (?![&|\]])/) !== null) {
 | |
|             m.push(mod());
 | |
|         }
 | |
|         return m.length === 1 ? m[0] : Matcher.seq.apply(Matcher, m);
 | |
|     };
 | |
|     mod = function() {
 | |
|         // mod = term ( "?" | "*" | "+" | "#" | "{<num>,<num>}" )?
 | |
|         var m = term();
 | |
|         if (reader.readMatch("?") !== null) {
 | |
|             return m.question();
 | |
|         } else if (reader.readMatch("*") !== null) {
 | |
|             return m.star();
 | |
|         } else if (reader.readMatch("+") !== null) {
 | |
|             return m.plus();
 | |
|         } else if (reader.readMatch("#") !== null) {
 | |
|             return m.hash();
 | |
|         } else if (reader.readMatch(/^\{\s*/) !== null) {
 | |
|             var min = eat(/^\d+/);
 | |
|             eat(/^\s*,\s*/);
 | |
|             var max = eat(/^\d+/);
 | |
|             eat(/^\s*\}/);
 | |
|             return m.braces(+min, +max);
 | |
|         }
 | |
|         return m;
 | |
|     };
 | |
|     term = function() {
 | |
|         // term = <nt> | literal | "[ " expression " ]"
 | |
|         if (reader.readMatch("[ ") !== null) {
 | |
|             var m = expr();
 | |
|             eat(" ]");
 | |
|             return m;
 | |
|         }
 | |
|         return Matcher.fromType(eat(/^[^ ?*+#{]+/));
 | |
|     };
 | |
|     result = expr();
 | |
|     if (!reader.eof()) {
 | |
|         throw new SyntaxError(
 | |
|             "Expected end of string", reader.getLine(), reader.getCol());
 | |
|     }
 | |
|     return result;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Convert a string to a matcher (parsing simple alternations),
 | |
|  * or do nothing if the argument is already a matcher.
 | |
|  */
 | |
| Matcher.cast = function(m) {
 | |
|     if (m instanceof Matcher) {
 | |
|         return m;
 | |
|     }
 | |
|     return Matcher.parse(m);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Create a matcher for a single type.
 | |
|  */
 | |
| Matcher.fromType = function(type) {
 | |
|     // Late require of ValidationTypes to break a dependency cycle.
 | |
|     var ValidationTypes = require("./ValidationTypes");
 | |
|     return new Matcher(function(expression) {
 | |
|         return expression.hasNext() && ValidationTypes.isType(expression, type);
 | |
|     }, type);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Create a matcher for one or more juxtaposed words, which all must
 | |
|  * occur, in the given order.
 | |
|  */
 | |
| Matcher.seq = function() {
 | |
|     var ms = Array.prototype.slice.call(arguments).map(Matcher.cast);
 | |
|     if (ms.length === 1) {
 | |
|         return ms[0];
 | |
|     }
 | |
|     return new Matcher(function(expression) {
 | |
|         var i, result = true;
 | |
|         for (i = 0; result && i < ms.length; i++) {
 | |
|             result = ms[i].match(expression);
 | |
|         }
 | |
|         return result;
 | |
|     }, function(prec) {
 | |
|         var p = Matcher.prec.SEQ;
 | |
|         var s = ms.map(function(m) {
 | |
|             return m.toString(p);
 | |
|         }).join(" ");
 | |
|         if (prec > p) {
 | |
|             s = "[ " + s + " ]";
 | |
|         }
 | |
|         return s;
 | |
|     });
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Create a matcher for one or more alternatives, where exactly one
 | |
|  * must occur.
 | |
|  */
 | |
| Matcher.alt = function() {
 | |
|     var ms = Array.prototype.slice.call(arguments).map(Matcher.cast);
 | |
|     if (ms.length === 1) {
 | |
|         return ms[0];
 | |
|     }
 | |
|     return new Matcher(function(expression) {
 | |
|         var i, result = false;
 | |
|         for (i = 0; !result && i < ms.length; i++) {
 | |
|             result = ms[i].match(expression);
 | |
|         }
 | |
|         return result;
 | |
|     }, function(prec) {
 | |
|         var p = Matcher.prec.ALT;
 | |
|         var s = ms.map(function(m) {
 | |
|             return m.toString(p);
 | |
|         }).join(" | ");
 | |
|         if (prec > p) {
 | |
|             s = "[ " + s + " ]";
 | |
|         }
 | |
|         return s;
 | |
|     });
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Create a matcher for two or more options.  This implements the
 | |
|  * double bar (||) and double ampersand (&&) operators, as well as
 | |
|  * variants of && where some of the alternatives are optional.
 | |
|  * This will backtrack through even successful matches to try to
 | |
|  * maximize the number of items matched.
 | |
|  */
 | |
| Matcher.many = function(required) {
 | |
|     var ms = Array.prototype.slice.call(arguments, 1).reduce(function(acc, v) {
 | |
|         if (v.expand) {
 | |
|             // Insert all of the options for the given complex rule as
 | |
|             // individual options.
 | |
|             var ValidationTypes = require("./ValidationTypes");
 | |
|             acc.push.apply(acc, ValidationTypes.complex[v.expand].options);
 | |
|         } else {
 | |
|             acc.push(Matcher.cast(v));
 | |
|         }
 | |
|         return acc;
 | |
|     }, []);
 | |
| 
 | |
|     if (required === true) {
 | |
|         required = ms.map(function() {
 | |
|             return true;
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     var result = new Matcher(function(expression) {
 | |
|         var seen = [], max = 0, pass = 0;
 | |
|         var success = function(matchCount) {
 | |
|             if (pass === 0) {
 | |
|                 max = Math.max(matchCount, max);
 | |
|                 return matchCount === ms.length;
 | |
|             } else {
 | |
|                 return matchCount === max;
 | |
|             }
 | |
|         };
 | |
|         var tryMatch = function(matchCount) {
 | |
|             for (var i = 0; i < ms.length; i++) {
 | |
|                 if (seen[i]) {
 | |
|                     continue;
 | |
|                 }
 | |
|                 expression.mark();
 | |
|                 if (ms[i].match(expression)) {
 | |
|                     seen[i] = true;
 | |
|                     // Increase matchCount iff this was a required element
 | |
|                     // (or if all the elements are optional)
 | |
|                     if (tryMatch(matchCount + ((required === false || required[i]) ? 1 : 0))) {
 | |
|                         expression.drop();
 | |
|                         return true;
 | |
|                     }
 | |
|                     // Backtrack: try *not* matching using this rule, and
 | |
|                     // let's see if it leads to a better overall match.
 | |
|                     expression.restore();
 | |
|                     seen[i] = false;
 | |
|                 } else {
 | |
|                     expression.drop();
 | |
|                 }
 | |
|             }
 | |
|             return success(matchCount);
 | |
|         };
 | |
|         if (!tryMatch(0)) {
 | |
|             // Couldn't get a complete match, retrace our steps to make the
 | |
|             // match with the maximum # of required elements.
 | |
|             pass++;
 | |
|             tryMatch(0);
 | |
|         }
 | |
| 
 | |
|         if (required === false) {
 | |
|             return max > 0;
 | |
|         }
 | |
|         // Use finer-grained specification of which matchers are required.
 | |
|         for (var i = 0; i < ms.length; i++) {
 | |
|             if (required[i] && !seen[i]) {
 | |
|                 return false;
 | |
|             }
 | |
|         }
 | |
|         return true;
 | |
|     }, function(prec) {
 | |
|         var p = required === false ? Matcher.prec.OROR : Matcher.prec.ANDAND;
 | |
|         var s = ms.map(function(m, i) {
 | |
|             if (required !== false && !required[i]) {
 | |
|                 return m.toString(Matcher.prec.MOD) + "?";
 | |
|             }
 | |
|             return m.toString(p);
 | |
|         }).join(required === false ? " || " : " && ");
 | |
|         if (prec > p) {
 | |
|             s = "[ " + s + " ]";
 | |
|         }
 | |
|         return s;
 | |
|     });
 | |
|     result.options = ms;
 | |
|     return result;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Create a matcher for two or more options, where all options are
 | |
|  * mandatory but they may appear in any order.
 | |
|  */
 | |
| Matcher.andand = function() {
 | |
|     var args = Array.prototype.slice.call(arguments);
 | |
|     args.unshift(true);
 | |
|     return Matcher.many.apply(Matcher, args);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Create a matcher for two or more options, where options are
 | |
|  * optional and may appear in any order, but at least one must be
 | |
|  * present.
 | |
|  */
 | |
| Matcher.oror = function() {
 | |
|     var args = Array.prototype.slice.call(arguments);
 | |
|     args.unshift(false);
 | |
|     return Matcher.many.apply(Matcher, args);
 | |
| };
 | |
| 
 | |
| /** Instance methods on Matchers. */
 | |
| Matcher.prototype = {
 | |
|     constructor: Matcher,
 | |
|     // These are expected to be overridden in every instance.
 | |
|     match: function() { throw new Error("unimplemented"); },
 | |
|     toString: function() { throw new Error("unimplemented"); },
 | |
|     // This returns a standalone function to do the matching.
 | |
|     func: function() { return this.match.bind(this); },
 | |
|     // Basic combinators
 | |
|     then: function(m) { return Matcher.seq(this, m); },
 | |
|     or: function(m) { return Matcher.alt(this, m); },
 | |
|     andand: function(m) { return Matcher.many(true, this, m); },
 | |
|     oror: function(m) { return Matcher.many(false, this, m); },
 | |
|     // Component value multipliers
 | |
|     star: function() { return this.braces(0, Infinity, "*"); },
 | |
|     plus: function() { return this.braces(1, Infinity, "+"); },
 | |
|     question: function() { return this.braces(0, 1, "?"); },
 | |
|     hash: function() {
 | |
|         return this.braces(1, Infinity, "#", Matcher.cast(","));
 | |
|     },
 | |
|     braces: function(min, max, marker, optSep) {
 | |
|         var m1 = this, m2 = optSep ? optSep.then(this) : this;
 | |
|         if (!marker) {
 | |
|             marker = "{" + min + "," + max + "}";
 | |
|         }
 | |
|         return new Matcher(function(expression) {
 | |
|             var result = true, i;
 | |
|             for (i = 0; i < max; i++) {
 | |
|                 if (i > 0 && optSep) {
 | |
|                     result = m2.match(expression);
 | |
|                 } else {
 | |
|                     result = m1.match(expression);
 | |
|                 }
 | |
|                 if (!result) {
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             return i >= min;
 | |
|         }, function() {
 | |
|             return m1.toString(Matcher.prec.MOD) + marker;
 | |
|         });
 | |
|     }
 | |
| };
 | |
| 
 | |
| },{"../util/StringReader":24,"../util/SyntaxError":25,"./ValidationTypes":21}],4:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = MediaFeature;
 | |
| 
 | |
| var SyntaxUnit = require("../util/SyntaxUnit");
 | |
| 
 | |
| var Parser = require("./Parser");
 | |
| 
 | |
| /**
 | |
|  * 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;
 | |
| 
 | |
| 
 | |
| },{"../util/SyntaxUnit":26,"./Parser":6}],5:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = MediaQuery;
 | |
| 
 | |
| var SyntaxUnit = require("../util/SyntaxUnit");
 | |
| 
 | |
| var Parser = require("./Parser");
 | |
| 
 | |
| /**
 | |
|  * Represents an individual media query.
 | |
|  * @namespace parserlib.css
 | |
|  * @class MediaQuery
 | |
|  * @extends parserlib.util.SyntaxUnit
 | |
|  * @constructor
 | |
|  * @param {String} modifier The modifier "not" or "only" (or null).
 | |
|  * @param {String} mediaType The type of media (i.e., "print").
 | |
|  * @param {Array} parts Array of selectors parts making up this selector.
 | |
|  * @param {int} line The line of text on which the unit resides.
 | |
|  * @param {int} col The column of text on which the unit resides.
 | |
|  */
 | |
| function MediaQuery(modifier, mediaType, features, line, col) {
 | |
| 
 | |
|     SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType : "") + (mediaType && features.length > 0 ? " and " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE);
 | |
| 
 | |
|     /**
 | |
|      * 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;
 | |
| 
 | |
| 
 | |
| },{"../util/SyntaxUnit":26,"./Parser":6}],6:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = Parser;
 | |
| 
 | |
| var EventTarget = require("../util/EventTarget");
 | |
| var SyntaxError = require("../util/SyntaxError");
 | |
| var SyntaxUnit = require("../util/SyntaxUnit");
 | |
| 
 | |
| var Combinator = require("./Combinator");
 | |
| var MediaFeature = require("./MediaFeature");
 | |
| var MediaQuery = require("./MediaQuery");
 | |
| var PropertyName = require("./PropertyName");
 | |
| var PropertyValue = require("./PropertyValue");
 | |
| var PropertyValuePart = require("./PropertyValuePart");
 | |
| var Selector = require("./Selector");
 | |
| var SelectorPart = require("./SelectorPart");
 | |
| var SelectorSubPart = require("./SelectorSubPart");
 | |
| var TokenStream = require("./TokenStream");
 | |
| var Tokens = require("./Tokens");
 | |
| var Validation = require("./Validation");
 | |
| 
 | |
| /**
 | |
|  * 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 =  {
 | |
|             __proto__: null,
 | |
| 
 | |
|             //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_rule | supports_rule ] [S|CDO|CDC]* ]*
 | |
|                  *  ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     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.VIEWPORT_SYM:
 | |
|                                 this._viewport();
 | |
|                                 this._skipCruft();
 | |
|                                 break;
 | |
|                             case Tokens.DOCUMENT_SYM:
 | |
|                                 this._document();
 | |
|                                 this._skipCruft();
 | |
|                                 break;
 | |
|                             case Tokens.SUPPORTS_SYM:
 | |
|                                 this._supports();
 | |
|                                 this._skipCruft();
 | |
|                                 break;
 | |
|                             case Tokens.UNKNOWN_SYM:  //unknown @ rule
 | |
|                                 tokenStream.get();
 | |
|                                 if (!this.options.strict) {
 | |
| 
 | |
|                                     //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,
 | |
|                     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
 | |
|                     });
 | |
|                 }
 | |
| 
 | |
|             },
 | |
| 
 | |
|             _supports: function(emit) {
 | |
|                 /*
 | |
|                  * supports_rule
 | |
|                  *  : SUPPORTS_SYM S* supports_condition S* group_rule_body
 | |
|                  *  ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     line,
 | |
|                     col;
 | |
| 
 | |
|                 if (tokenStream.match(Tokens.SUPPORTS_SYM)) {
 | |
|                     line = tokenStream.token().startLine;
 | |
|                     col = tokenStream.token().startCol;
 | |
| 
 | |
|                     this._readWhitespace();
 | |
|                     this._supports_condition();
 | |
|                     this._readWhitespace();
 | |
| 
 | |
|                     tokenStream.mustMatch(Tokens.LBRACE);
 | |
|                     this._readWhitespace();
 | |
| 
 | |
|                     if (emit !== false) {
 | |
|                         this.fire({
 | |
|                             type:   "startsupports",
 | |
|                             line:   line,
 | |
|                             col:    col
 | |
|                         });
 | |
|                     }
 | |
| 
 | |
|                     while (true) {
 | |
|                         if (!this._ruleset()) {
 | |
|                             break;
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     tokenStream.mustMatch(Tokens.RBRACE);
 | |
|                     this._readWhitespace();
 | |
| 
 | |
|                     this.fire({
 | |
|                         type:   "endsupports",
 | |
|                         line:   line,
 | |
|                         col:    col
 | |
|                     });
 | |
|                 }
 | |
|             },
 | |
| 
 | |
|             _supports_condition: function() {
 | |
|                 /*
 | |
|                  * supports_condition
 | |
|                  *  : supports_negation | supports_conjunction | supports_disjunction |
 | |
|                  *    supports_condition_in_parens
 | |
|                  *  ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     ident;
 | |
| 
 | |
|                 if (tokenStream.match(Tokens.IDENT)) {
 | |
|                     ident = tokenStream.token().value.toLowerCase();
 | |
| 
 | |
|                     if (ident === "not") {
 | |
|                         tokenStream.mustMatch(Tokens.S);
 | |
|                         this._supports_condition_in_parens();
 | |
|                     } else {
 | |
|                         tokenStream.unget();
 | |
|                     }
 | |
|                 } else {
 | |
|                     this._supports_condition_in_parens();
 | |
|                     this._readWhitespace();
 | |
| 
 | |
|                     while (tokenStream.peek() === Tokens.IDENT) {
 | |
|                         ident = tokenStream.LT(1).value.toLowerCase();
 | |
|                         if (ident === "and" || ident === "or") {
 | |
|                             tokenStream.mustMatch(Tokens.IDENT);
 | |
|                             this._readWhitespace();
 | |
|                             this._supports_condition_in_parens();
 | |
|                             this._readWhitespace();
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             },
 | |
| 
 | |
|             _supports_condition_in_parens: function() {
 | |
|                 /*
 | |
|                  * supports_condition_in_parens
 | |
|                  *  : ( '(' S* supports_condition S* ')' ) | supports_declaration_condition |
 | |
|                  *    general_enclosed
 | |
|                  *  ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     ident;
 | |
| 
 | |
|                 if (tokenStream.match(Tokens.LPAREN)) {
 | |
|                     this._readWhitespace();
 | |
|                     if (tokenStream.match(Tokens.IDENT)) {
 | |
|                         // look ahead for not keyword, if not given, continue with declaration condition.
 | |
|                         ident = tokenStream.token().value.toLowerCase();
 | |
|                         if (ident === "not") {
 | |
|                             this._readWhitespace();
 | |
|                             this._supports_condition();
 | |
|                             this._readWhitespace();
 | |
|                             tokenStream.mustMatch(Tokens.RPAREN);
 | |
|                         } else {
 | |
|                             tokenStream.unget();
 | |
|                             this._supports_declaration_condition(false);
 | |
|                         }
 | |
|                     } else {
 | |
|                         this._supports_condition();
 | |
|                         this._readWhitespace();
 | |
|                         tokenStream.mustMatch(Tokens.RPAREN);
 | |
|                     }
 | |
|                 } else {
 | |
|                     this._supports_declaration_condition();
 | |
|                 }
 | |
|             },
 | |
| 
 | |
|             _supports_declaration_condition: function(requireStartParen) {
 | |
|                 /*
 | |
|                  * supports_declaration_condition
 | |
|                  *  : '(' S* declaration ')'
 | |
|                  *  ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream;
 | |
| 
 | |
|                 if (requireStartParen !== false) {
 | |
|                     tokenStream.mustMatch(Tokens.LPAREN);
 | |
|                 }
 | |
|                 this._readWhitespace();
 | |
|                 this._declaration();
 | |
|                 tokenStream.mustMatch(Tokens.RPAREN);
 | |
|             },
 | |
| 
 | |
|             _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 (tokenStream.peek() === Tokens.FONT_FACE_SYM) {
 | |
|                         this._font_face();
 | |
|                     } else if (tokenStream.peek() === Tokens.VIEWPORT_SYM) {
 | |
|                         this._viewport();
 | |
|                     } else if (tokenStream.peek() === Tokens.DOCUMENT_SYM) {
 | |
|                         this._document();
 | |
|                     } else if (tokenStream.peek() === Tokens.SUPPORTS_SYM) {
 | |
|                         this._supports();
 | |
|                     } else if (tokenStream.peek() === Tokens.MEDIA_SYM) {
 | |
|                         this._media();
 | |
|                     } else if (!this._ruleset()) {
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 tokenStream.mustMatch(Tokens.RBRACE);
 | |
|                 this._readWhitespace();
 | |
| 
 | |
|                 this.fire({
 | |
|                     type:   "endmedia",
 | |
|                     media:  mediaList,
 | |
|                     line:   line,
 | |
|                     col:    col
 | |
|                 });
 | |
|             },
 | |
| 
 | |
| 
 | |
|             //CSS3 Media Queries
 | |
|             _media_query_list: function() {
 | |
|                 /*
 | |
|                  * media_query_list
 | |
|                  *   : 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;
 | |
| 
 | |
|                 this._readWhitespace();
 | |
| 
 | |
|                 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
 | |
|                 });
 | |
|             },
 | |
| 
 | |
|             _viewport: function() {
 | |
|                 /*
 | |
|                  * viewport
 | |
|                  *   : VIEWPORT_SYM S*
 | |
|                  *     '{' S* declaration? [ ';' S* declaration? ]* '}' S*
 | |
|                  *   ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     line,
 | |
|                     col;
 | |
| 
 | |
|                 tokenStream.mustMatch(Tokens.VIEWPORT_SYM);
 | |
|                 line = tokenStream.token().startLine;
 | |
|                 col = tokenStream.token().startCol;
 | |
| 
 | |
|                 this._readWhitespace();
 | |
| 
 | |
|                 this.fire({
 | |
|                     type:   "startviewport",
 | |
|                     line:   line,
 | |
|                     col:    col
 | |
|                 });
 | |
| 
 | |
|                 this._readDeclarations(true);
 | |
| 
 | |
|                 this.fire({
 | |
|                     type:   "endviewport",
 | |
|                     line:   line,
 | |
|                     col:    col
 | |
|                 });
 | |
| 
 | |
|             },
 | |
| 
 | |
|             _document: function() {
 | |
|                 /*
 | |
|                  * document
 | |
|                  *   : DOCUMENT_SYM S*
 | |
|                  *     _document_function [ ',' S* _document_function ]* S*
 | |
|                  *     '{' S* ruleset* '}'
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     token,
 | |
|                     functions = [],
 | |
|                     prefix = "";
 | |
| 
 | |
|                 tokenStream.mustMatch(Tokens.DOCUMENT_SYM);
 | |
|                 token = tokenStream.token();
 | |
|                 if (/^@\-([^\-]+)\-/.test(token.value)) {
 | |
|                     prefix = RegExp.$1;
 | |
|                 }
 | |
| 
 | |
|                 this._readWhitespace();
 | |
|                 functions.push(this._document_function());
 | |
| 
 | |
|                 while (tokenStream.match(Tokens.COMMA)) {
 | |
|                     this._readWhitespace();
 | |
|                     functions.push(this._document_function());
 | |
|                 }
 | |
| 
 | |
|                 tokenStream.mustMatch(Tokens.LBRACE);
 | |
|                 this._readWhitespace();
 | |
| 
 | |
|                 this.fire({
 | |
|                     type:      "startdocument",
 | |
|                     functions: functions,
 | |
|                     prefix:    prefix,
 | |
|                     line:      token.startLine,
 | |
|                     col:       token.startCol
 | |
|                 });
 | |
| 
 | |
|                 var ok = true;
 | |
|                 while (ok) {
 | |
|                     switch (tokenStream.peek()) {
 | |
|                         case Tokens.PAGE_SYM:
 | |
|                             this._page();
 | |
|                             break;
 | |
|                         case Tokens.FONT_FACE_SYM:
 | |
|                             this._font_face();
 | |
|                             break;
 | |
|                         case Tokens.VIEWPORT_SYM:
 | |
|                             this._viewport();
 | |
|                             break;
 | |
|                         case Tokens.MEDIA_SYM:
 | |
|                             this._media();
 | |
|                             break;
 | |
|                         case Tokens.KEYFRAMES_SYM:
 | |
|                             this._keyframes();
 | |
|                             break;
 | |
|                         case Tokens.DOCUMENT_SYM:
 | |
|                             this._document();
 | |
|                             break;
 | |
|                         default:
 | |
|                             ok = Boolean(this._ruleset());
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 tokenStream.mustMatch(Tokens.RBRACE);
 | |
|                 token = tokenStream.token();
 | |
|                 this._readWhitespace();
 | |
| 
 | |
|                 this.fire({
 | |
|                     type:      "enddocument",
 | |
|                     functions: functions,
 | |
|                     prefix:    prefix,
 | |
|                     line:      token.startLine,
 | |
|                     col:       token.startCol
 | |
|                 });
 | |
|             },
 | |
| 
 | |
|             _document_function: function() {
 | |
|                 /*
 | |
|                  * document_function
 | |
|                  *   : function | URI S*
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     value;
 | |
| 
 | |
|                 if (tokenStream.match(Tokens.URI)) {
 | |
|                     value = tokenStream.token().value;
 | |
|                     this._readWhitespace();
 | |
|                 } else {
 | |
|                     value = this._function();
 | |
|                 }
 | |
| 
 | |
|                 return value;
 | |
|             },
 | |
| 
 | |
|             _operator: function(inFunction) {
 | |
| 
 | |
|                 /*
 | |
|                  * operator (outside function)
 | |
|                  *  : '/' S* | ',' S* | /( empty )/
 | |
|                  * operator (inside function)
 | |
|                  *  : '/' S* | '+' S* | '*' S* | '-' S* /( empty )/
 | |
|                  *  ;
 | |
|                  */
 | |
| 
 | |
|                 var tokenStream = this._tokenStream,
 | |
|                     token       = null;
 | |
| 
 | |
|                 if (tokenStream.match([Tokens.SLASH, Tokens.COMMA]) ||
 | |
|                     (inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))) {
 | |
|                     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(tokenStream.LT(1));
 | |
|                         } else {
 | |
| 
 | |
|                             //nextSelector is an instance of SelectorPart
 | |
|                             selector.push(nextSelector);
 | |
|                         }
 | |
|                     } else {
 | |
| 
 | |
|                         //if there's not whitespace, we're done
 | |
|                         if (this._readWhitespace()) {
 | |
| 
 | |
|                             //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,
 | |
|                     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);
 | |
|                     } else {
 | |
|                         var startLine = tokenStream.LT(1).startLine,
 | |
|                             startCol  = tokenStream.LT(0).startCol;
 | |
|                         throw new SyntaxError("Expected a `FUNCTION` or `IDENT` after colon at line " + startLine + ", col " + startCol + ".", startLine, startCol);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 return pseudo;
 | |
|             },
 | |
| 
 | |
|             //CSS3 Selectors
 | |
|             _functional_pseudo: function() {
 | |
|                 /*
 | |
|                  * functional_pseudo
 | |
|                  *   : FUNCTION 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, Tokens.SLASH])) {
 | |
| 
 | |
|                     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,
 | |
|                     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,
 | |
|                     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(inFunction) {
 | |
|                 /*
 | |
|                  * expr
 | |
|                  *   : term [ operator term ]*
 | |
|                  *   ;
 | |
|                  */
 | |
| 
 | |
|                 var values      = [],
 | |
|                     //valueParts    = [],
 | |
|                     value       = null,
 | |
|                     operator    = null;
 | |
| 
 | |
|                 value = this._term(inFunction);
 | |
|                 if (value !== null) {
 | |
| 
 | |
|                     values.push(value);
 | |
| 
 | |
|                     do {
 | |
|                         operator = this._operator(inFunction);
 | |
| 
 | |
|                         //if there's an operator, keep building up the value parts
 | |
|                         if (operator) {
 | |
|                             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(inFunction);
 | |
| 
 | |
|                         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(inFunction) {
 | |
| 
 | |
|                 /*
 | |
|                  * 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,
 | |
|                     endChar     = null,
 | |
|                     part        = 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 it's a simple block
 | |
|                 } else if (inFunction && tokenStream.match([Tokens.LPAREN, Tokens.LBRACE, Tokens.LBRACKET])) {
 | |
| 
 | |
|                     token = tokenStream.token();
 | |
|                     endChar = token.endChar;
 | |
|                     value = token.value + this._expr(inFunction).text;
 | |
|                     if (unary === null) {
 | |
|                         line = tokenStream.token().startLine;
 | |
|                         col = tokenStream.token().startCol;
 | |
|                     }
 | |
|                     tokenStream.mustMatch(Tokens.type(endChar));
 | |
|                     value += endChar;
 | |
|                     this._readWhitespace();
 | |
| 
 | |
|                 //see if there's a simple match
 | |
|                 } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH,
 | |
|                         Tokens.ANGLE, Tokens.TIME,
 | |
|                         Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])) {
 | |
| 
 | |
|                     value = tokenStream.token().value;
 | |
|                     if (unary === null) {
 | |
|                         line = tokenStream.token().startLine;
 | |
|                         col = tokenStream.token().startCol;
 | |
|                         // Correct potentially-inaccurate IDENT parsing in
 | |
|                         // PropertyValuePart constructor.
 | |
|                         part = PropertyValuePart.fromToken(tokenStream.token());
 | |
|                     }
 | |
|                     this._readWhitespace();
 | |
|                 } else {
 | |
| 
 | |
|                     //see if it's a color
 | |
|                     token = this._hexcolor();
 | |
|                     if (token === null) {
 | |
| 
 | |
|                         //if 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 part !== null ? part : 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(true);
 | |
|                     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,
 | |
|                     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,
 | |
|                     prefix = "";
 | |
| 
 | |
|                 tokenStream.mustMatch(Tokens.KEYFRAMES_SYM);
 | |
|                 token = tokenStream.token();
 | |
|                 if (/^@\-([^\-]+)\-/.test(token.value)) {
 | |
|                     prefix = RegExp.$1;
 | |
|                 }
 | |
| 
 | |
|                 this._readWhitespace();
 | |
|                 name = this._keyframe_name();
 | |
| 
 | |
|                 this._readWhitespace();
 | |
|                 tokenStream.mustMatch(Tokens.LBRACE);
 | |
| 
 | |
|                 this.fire({
 | |
|                     type:   "startkeyframes",
 | |
|                     name:   name,
 | |
|                     prefix: prefix,
 | |
|                     line:   token.startLine,
 | |
|                     col:    token.startCol
 | |
|                 });
 | |
| 
 | |
|                 this._readWhitespace();
 | |
|                 tt = tokenStream.peek();
 | |
| 
 | |
|                 //check for key
 | |
|                 while (tt === Tokens.IDENT || tt === Tokens.PERCENTAGE) {
 | |
|                     this._keyframe_rule();
 | |
|                     this._readWhitespace();
 | |
|                     tt = tokenStream.peek();
 | |
|                 }
 | |
| 
 | |
|                 this.fire({
 | |
|                     type:   "endkeyframes",
 | |
|                     name:   name,
 | |
|                     prefix: prefix,
 | |
|                     line:   token.startLine,
 | |
|                     col:    token.startCol
 | |
|                 });
 | |
| 
 | |
|                 this._readWhitespace();
 | |
|                 tokenStream.mustMatch(Tokens.RBRACE);
 | |
|                 this._readWhitespace();
 | |
| 
 | |
|             },
 | |
| 
 | |
|             _keyframe_name: function() {
 | |
| 
 | |
|                 /*
 | |
|                  * keyframe_name:
 | |
|                  *   : IDENT
 | |
|                  *   | STRING
 | |
|                  *   ;
 | |
|                  */
 | |
|                 var tokenStream = this._tokenStream;
 | |
| 
 | |
|                 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 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,
 | |
|                     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 declaration if 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 (Object.prototype.hasOwnProperty.call(additions, prop)) {
 | |
|             proto[prop] = additions[prop];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return proto;
 | |
| }();
 | |
| 
 | |
| 
 | |
| /*
 | |
| nth
 | |
|   : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? |
 | |
|          ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S*
 | |
|   ;
 | |
| */
 | |
| 
 | |
| },{"../util/EventTarget":23,"../util/SyntaxError":25,"../util/SyntaxUnit":26,"./Combinator":2,"./MediaFeature":4,"./MediaQuery":5,"./PropertyName":8,"./PropertyValue":9,"./PropertyValuePart":11,"./Selector":13,"./SelectorPart":14,"./SelectorSubPart":15,"./TokenStream":17,"./Tokens":18,"./Validation":19}],7:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| /* exported Properties */
 | |
| 
 | |
| var Properties = module.exports = {
 | |
|     __proto__: null,
 | |
| 
 | |
|     //A
 | |
|     "align-items"                   : "flex-start | flex-end | center | baseline | stretch",
 | |
|     "align-content"                 : "flex-start | flex-end | center | space-between | space-around | stretch",
 | |
|     "align-self"                    : "auto | flex-start | flex-end | center | baseline | stretch",
 | |
|     "all"                           : "initial | inherit | unset",
 | |
|     "-webkit-align-items"           : "flex-start | flex-end | center | baseline | stretch",
 | |
|     "-webkit-align-content"         : "flex-start | flex-end | center | space-between | space-around | stretch",
 | |
|     "-webkit-align-self"            : "auto | flex-start | flex-end | center | baseline | stretch",
 | |
|     "alignment-adjust"              : "auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | <percentage> | <length>",
 | |
|     "alignment-baseline"            : "auto | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
 | |
|     "animation"                     : 1,
 | |
|     "animation-delay"               : "<time>#",
 | |
|     "animation-direction"           : "<single-animation-direction>#",
 | |
|     "animation-duration"            : "<time>#",
 | |
|     "animation-fill-mode"           : "[ none | forwards | backwards | both ]#",
 | |
|     "animation-iteration-count"     : "[ <number> | infinite ]#",
 | |
|     "animation-name"                : "[ none | <single-animation-name> ]#",
 | |
|     "animation-play-state"          : "[ running | paused ]#",
 | |
|     "animation-timing-function"     : 1,
 | |
| 
 | |
|     //vendor prefixed
 | |
|     "-moz-animation-delay"               : "<time>#",
 | |
|     "-moz-animation-direction"           : "[ normal | alternate ]#",
 | |
|     "-moz-animation-duration"            : "<time>#",
 | |
|     "-moz-animation-iteration-count"     : "[ <number> | infinite ]#",
 | |
|     "-moz-animation-name"                : "[ none | <single-animation-name> ]#",
 | |
|     "-moz-animation-play-state"          : "[ running | paused ]#",
 | |
| 
 | |
|     "-ms-animation-delay"               : "<time>#",
 | |
|     "-ms-animation-direction"           : "[ normal | alternate ]#",
 | |
|     "-ms-animation-duration"            : "<time>#",
 | |
|     "-ms-animation-iteration-count"     : "[ <number> | infinite ]#",
 | |
|     "-ms-animation-name"                : "[ none | <single-animation-name> ]#",
 | |
|     "-ms-animation-play-state"          : "[ running | paused ]#",
 | |
| 
 | |
|     "-webkit-animation-delay"               : "<time>#",
 | |
|     "-webkit-animation-direction"           : "[ normal | alternate ]#",
 | |
|     "-webkit-animation-duration"            : "<time>#",
 | |
|     "-webkit-animation-fill-mode"           : "[ none | forwards | backwards | both ]#",
 | |
|     "-webkit-animation-iteration-count"     : "[ <number> | infinite ]#",
 | |
|     "-webkit-animation-name"                : "[ none | <single-animation-name> ]#",
 | |
|     "-webkit-animation-play-state"          : "[ running | paused ]#",
 | |
| 
 | |
|     "-o-animation-delay"               : "<time>#",
 | |
|     "-o-animation-direction"           : "[ normal | alternate ]#",
 | |
|     "-o-animation-duration"            : "<time>#",
 | |
|     "-o-animation-iteration-count"     : "[ <number> | infinite ]#",
 | |
|     "-o-animation-name"                : "[ none | <single-animation-name> ]#",
 | |
|     "-o-animation-play-state"          : "[ running | paused ]#",
 | |
| 
 | |
|     "appearance"                    : "none | auto",
 | |
|     "-moz-appearance"               : "none | button | button-arrow-down | button-arrow-next | button-arrow-previous | button-arrow-up | button-bevel | button-focus | caret | checkbox | checkbox-container | checkbox-label | checkmenuitem | dualbutton | groupbox | listbox | listitem | menuarrow | menubar | menucheckbox | menuimage | menuitem | menuitemtext | menulist | menulist-button | menulist-text | menulist-textfield | menupopup | menuradio | menuseparator | meterbar | meterchunk | progressbar | progressbar-vertical | progresschunk | progresschunk-vertical | radio | radio-container | radio-label | radiomenuitem | range | range-thumb | resizer | resizerpanel | scale-horizontal | scalethumbend | scalethumb-horizontal | scalethumbstart | scalethumbtick | scalethumb-vertical | scale-vertical | scrollbarbutton-down | scrollbarbutton-left | scrollbarbutton-right | scrollbarbutton-up | scrollbarthumb-horizontal | scrollbarthumb-vertical | scrollbartrack-horizontal | scrollbartrack-vertical | searchfield | separator | sheet | spinner | spinner-downbutton | spinner-textfield | spinner-upbutton | splitter | statusbar | statusbarpanel | tab | tabpanel | tabpanels | tab-scroll-arrow-back | tab-scroll-arrow-forward | textfield | textfield-multiline | toolbar | toolbarbutton | toolbarbutton-dropdown | toolbargripper | toolbox | tooltip | treeheader | treeheadercell | treeheadersortarrow | treeitem | treeline | treetwisty | treetwistyopen | treeview | -moz-mac-unified-toolbar | -moz-win-borderless-glass | -moz-win-browsertabbar-toolbox | -moz-win-communicationstext | -moz-win-communications-toolbox | -moz-win-exclude-glass | -moz-win-glass | -moz-win-mediatext | -moz-win-media-toolbox | -moz-window-button-box | -moz-window-button-box-maximized | -moz-window-button-close | -moz-window-button-maximize | -moz-window-button-minimize | -moz-window-button-restore | -moz-window-frame-bottom | -moz-window-frame-left | -moz-window-frame-right | -moz-window-titlebar | -moz-window-titlebar-maximized",
 | |
|     "-ms-appearance"                : "none | icon | window | desktop | workspace | document | tooltip | dialog | button | push-button | hyperlink | radio | 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",
 | |
|     "-webkit-appearance"            : "none | button | button-bevel | caps-lock-indicator | caret | checkbox | default-button | listbox	| listitem | media-fullscreen-button | media-mute-button | media-play-button | media-seek-back-button	| media-seek-forward-button	| media-slider | media-sliderthumb | menulist	| menulist-button	| menulist-text	| menulist-textfield | push-button	| radio	| searchfield	| searchfield-cancel-button	| searchfield-decoration | searchfield-results-button | searchfield-results-decoration | slider-horizontal | slider-vertical | sliderthumb-horizontal | sliderthumb-vertical	| square-button	| textarea	| textfield	| scrollbarbutton-down | scrollbarbutton-left | scrollbarbutton-right | scrollbarbutton-up | scrollbargripper-horizontal | scrollbargripper-vertical | scrollbarthumb-horizontal | scrollbarthumb-vertical | scrollbartrack-horizontal | scrollbartrack-vertical",
 | |
|     "-o-appearance"                 : "none | window | desktop | workspace | document | tooltip | dialog | button | push-button | hyperlink | radio | 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",
 | |
| 
 | |
|     "azimuth"                       : "<azimuth>",
 | |
| 
 | |
|     //B
 | |
|     "backface-visibility"           : "visible | hidden",
 | |
|     "background"                    : 1,
 | |
|     "background-attachment"         : "<attachment>#",
 | |
|     "background-clip"               : "<box>#",
 | |
|     "background-color"              : "<color>",
 | |
|     "background-image"              : "<bg-image>#",
 | |
|     "background-origin"             : "<box>#",
 | |
|     "background-position"           : "<bg-position>",
 | |
|     "background-repeat"             : "<repeat-style>#",
 | |
|     "background-size"               : "<bg-size>#",
 | |
|     "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",
 | |
|     "border-color"                  : "<color>{1,4}",
 | |
|     "border-image"                  : 1,
 | |
|     "border-image-outset"           : "[ <length> | <number> ]{1,4}",
 | |
|     "border-image-repeat"           : "[ stretch | repeat | round ]{1,2}",
 | |
|     "border-image-slice"            : "<border-image-slice>",
 | |
|     "border-image-source"           : "<image> | none",
 | |
|     "border-image-width"            : "[ <length> | <percentage> | <number> | auto ]{1,4}",
 | |
|     "border-left"                   : "<border-width> || <border-style> || <color>",
 | |
|     "border-left-color"             : "<color>",
 | |
|     "border-left-style"             : "<border-style>",
 | |
|     "border-left-width"             : "<border-width>",
 | |
|     "border-radius"                 : "<border-radius>",
 | |
|     "border-right"                  : "<border-width> || <border-style> || <color>",
 | |
|     "border-right-color"            : "<color>",
 | |
|     "border-right-style"            : "<border-style>",
 | |
|     "border-right-width"            : "<border-width>",
 | |
|     "border-spacing"                : "<length>{1,2}",
 | |
|     "border-style"                  : "<border-style>{1,4}",
 | |
|     "border-top"                    : "<border-width> || <border-style> || <color>",
 | |
|     "border-top-color"              : "<color>",
 | |
|     "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"                  : "<border-width>{1,4}",
 | |
|     "bottom"                        : "<margin-width>",
 | |
|     "-moz-box-align"                : "start | end | center | baseline | stretch",
 | |
|     "-moz-box-decoration-break"     : "slice | clone",
 | |
|     "-moz-box-direction"            : "normal | reverse",
 | |
|     "-moz-box-flex"                 : "<number>",
 | |
|     "-moz-box-flex-group"           : "<integer>",
 | |
|     "-moz-box-lines"                : "single | multiple",
 | |
|     "-moz-box-ordinal-group"        : "<integer>",
 | |
|     "-moz-box-orient"               : "horizontal | vertical | inline-axis | block-axis",
 | |
|     "-moz-box-pack"                 : "start | end | center | justify",
 | |
|     "-o-box-decoration-break"       : "slice | clone",
 | |
|     "-webkit-box-align"             : "start | end | center | baseline | stretch",
 | |
|     "-webkit-box-decoration-break"  : "slice | clone",
 | |
|     "-webkit-box-direction"         : "normal | reverse",
 | |
|     "-webkit-box-flex"              : "<number>",
 | |
|     "-webkit-box-flex-group"        : "<integer>",
 | |
|     "-webkit-box-lines"             : "single | multiple",
 | |
|     "-webkit-box-ordinal-group"     : "<integer>",
 | |
|     "-webkit-box-orient"            : "horizontal | vertical | inline-axis | block-axis",
 | |
|     "-webkit-box-pack"              : "start | end | center | justify",
 | |
|     "box-decoration-break"          : "slice | clone",
 | |
|     "box-shadow"                    : "<box-shadow>",
 | |
|     "box-sizing"                    : "content-box | border-box",
 | |
|     "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",
 | |
|     "clear"                         : "none | right | left | both",
 | |
|     "clip"                          : "<shape> | auto",
 | |
|     "-webkit-clip-path"             : "<clip-source> | <clip-path> | none",
 | |
|     "clip-path"                     : "<clip-source> | <clip-path> | none",
 | |
|     "clip-rule"                     : "nonzero | evenodd",
 | |
|     "color"                         : "<color>",
 | |
|     "color-interpolation"           : "auto | sRGB | linearRGB",
 | |
|     "color-interpolation-filters"   : "auto | sRGB | linearRGB",
 | |
|     "color-profile"                 : 1,
 | |
|     "color-rendering"               : "auto | optimizeSpeed | optimizeQuality",
 | |
|     "column-count"                  : "<integer> | auto",                      //https://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",
 | |
|     "cue-after"                     : 1,
 | |
|     "cue-before"                    : 1,
 | |
|     "cursor"                        : 1,
 | |
| 
 | |
|     //D
 | |
|     "direction"                     : "ltr | rtl",
 | |
|     "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 | grid | inline-grid | run-in | ruby | ruby-base | ruby-text | ruby-base-container | ruby-text-container | contents | none | -moz-box | -moz-inline-block | -moz-inline-box | -moz-inline-grid | -moz-inline-stack | -moz-inline-table | -moz-grid | -moz-grid-group | -moz-grid-line | -moz-groupbox | -moz-deck | -moz-popup | -moz-stack | -moz-marker | -webkit-box | -webkit-inline-box | -ms-flexbox | -ms-inline-flexbox | flex | -webkit-flex | inline-flex | -webkit-inline-flex",
 | |
|     "dominant-baseline"             : "auto | use-script | no-change | reset-size | ideographic | alphabetic | hanging | mathematical | central | middle | text-after-edge | text-before-edge",
 | |
|     "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"            : "<integer>",
 | |
| 
 | |
|     //E
 | |
|     "elevation"                     : "<angle> | below | level | above | higher | lower",
 | |
|     "empty-cells"                   : "show | hide",
 | |
|     "enable-background"             : 1,
 | |
| 
 | |
|     //F
 | |
|     "fill"                          : "<paint>",
 | |
|     "fill-opacity"                  : "<opacity-value>",
 | |
|     "fill-rule"                     : "nonzero | evenodd",
 | |
|     "filter"                        : "<filter-function-list> | none",
 | |
|     "fit"                           : "fill | hidden | meet | slice",
 | |
|     "fit-position"                  : 1,
 | |
|     "flex"                          : "<flex>",
 | |
|     "flex-basis"                    : "<width>",
 | |
|     "flex-direction"                : "row | row-reverse | column | column-reverse",
 | |
|     "flex-flow"                     : "<flex-direction> || <flex-wrap>",
 | |
|     "flex-grow"                     : "<number>",
 | |
|     "flex-shrink"                   : "<number>",
 | |
|     "flex-wrap"                     : "nowrap | wrap | wrap-reverse",
 | |
|     "-webkit-flex"                  : "<flex>",
 | |
|     "-webkit-flex-basis"            : "<width>",
 | |
|     "-webkit-flex-direction"        : "row | row-reverse | column | column-reverse",
 | |
|     "-webkit-flex-flow"             : "<flex-direction> || <flex-wrap>",
 | |
|     "-webkit-flex-grow"             : "<number>",
 | |
|     "-webkit-flex-shrink"           : "<number>",
 | |
|     "-webkit-flex-wrap"             : "nowrap | wrap | wrap-reverse",
 | |
|     "-ms-flex"                      : "<flex>",
 | |
|     "-ms-flex-align"                : "start | end | center | stretch | baseline",
 | |
|     "-ms-flex-direction"            : "row | row-reverse | column | column-reverse",
 | |
|     "-ms-flex-order"                : "<number>",
 | |
|     "-ms-flex-pack"                 : "start | end | center | justify",
 | |
|     "-ms-flex-wrap"                 : "nowrap | wrap | wrap-reverse",
 | |
|     "float"                         : "left | right | none",
 | |
|     "float-offset"                  : 1,
 | |
|     "flood-color"                   : 1,
 | |
|     "flood-opacity"                 : "<opacity-value>",
 | |
|     "font"                          : "<font-shorthand> | caption | icon | menu | message-box | small-caption | status-bar",
 | |
|     "font-family"                   : "<font-family>",
 | |
|     "font-feature-settings"         : "<feature-tag-value> | normal",
 | |
|     "font-kerning"                  : "auto | normal | none",
 | |
|     "font-size"                     : "<font-size>",
 | |
|     "font-size-adjust"              : "<number> | none",
 | |
|     "font-stretch"                  : "<font-stretch>",
 | |
|     "font-style"                    : "<font-style>",
 | |
|     "font-variant"                  : "<font-variant> | normal | none",
 | |
|     "font-variant-alternates"       : "<font-variant-alternates> | normal",
 | |
|     "font-variant-caps"             : "<font-variant-caps> | normal",
 | |
|     "font-variant-east-asian"       : "<font-variant-east-asian> | normal",
 | |
|     "font-variant-ligatures"        : "<font-variant-ligatures> | normal | none",
 | |
|     "font-variant-numeric"          : "<font-variant-numeric> | normal",
 | |
|     "font-variant-position"         : "normal | sub | super",
 | |
|     "font-weight"                   : "<font-weight>",
 | |
| 
 | |
|     //G
 | |
|     "glyph-orientation-horizontal"  : "<glyph-angle>",
 | |
|     "glyph-orientation-vertical"    : "auto | <glyph-angle>",
 | |
|     "grid"                          : 1,
 | |
|     "grid-area"                     : 1,
 | |
|     "grid-auto-columns"             : 1,
 | |
|     "grid-auto-flow"                : 1,
 | |
|     "grid-auto-position"            : 1,
 | |
|     "grid-auto-rows"                : 1,
 | |
|     "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-start"             : 1,
 | |
|     "grid-column-end"               : 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-start"                : 1,
 | |
|     "grid-row-end"                  : 1,
 | |
|     "grid-row-span"                 : "<integer>",
 | |
|     "grid-row-sizing"               : 1,
 | |
|     "grid-template"                 : 1,
 | |
|     "grid-template-areas"           : 1,
 | |
|     "grid-template-columns"         : 1,
 | |
|     "grid-template-rows"            : 1,
 | |
| 
 | |
|     //H
 | |
|     "hanging-punctuation"           : 1,
 | |
|     "height"                        : "<margin-width> | <content-sizing>",
 | |
|     "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"               : "auto | optimizeSpeed | optimizeQuality",
 | |
|     "image-resolution"              : 1,
 | |
|     "ime-mode"                      : "auto | normal | active | inactive | disabled",
 | |
|     "inline-box-align"              : "last | <integer>",
 | |
| 
 | |
|     //J
 | |
|     "justify-content"               : "flex-start | flex-end | center | space-between | space-around",
 | |
|     "-webkit-justify-content"       : "flex-start | flex-end | center | space-between | space-around",
 | |
| 
 | |
|     //K
 | |
|     "kerning"                       : "auto | <length>",
 | |
| 
 | |
|     //L
 | |
|     "left"                          : "<margin-width>",
 | |
|     "letter-spacing"                : "<length> | normal",
 | |
|     "line-height"                   : "<line-height>",
 | |
|     "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",
 | |
|     "list-style-position"           : "inside | outside",
 | |
|     "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",
 | |
| 
 | |
|     //M
 | |
|     "margin"                        : "<margin-width>{1,4}",
 | |
|     "margin-bottom"                 : "<margin-width>",
 | |
|     "margin-left"                   : "<margin-width>",
 | |
|     "margin-right"                  : "<margin-width>",
 | |
|     "margin-top"                    : "<margin-width>",
 | |
|     "mark"                          : 1,
 | |
|     "mark-after"                    : 1,
 | |
|     "mark-before"                   : 1,
 | |
|     "marker"                        : 1,
 | |
|     "marker-end"                    : 1,
 | |
|     "marker-mid"                    : 1,
 | |
|     "marker-start"                  : 1,
 | |
|     "marks"                         : 1,
 | |
|     "marquee-direction"             : 1,
 | |
|     "marquee-play-count"            : 1,
 | |
|     "marquee-speed"                 : 1,
 | |
|     "marquee-style"                 : 1,
 | |
|     "mask"                          : 1,
 | |
|     "max-height"                    : "<length> | <percentage> | <content-sizing> | none",
 | |
|     "max-width"                     : "<length> | <percentage> | <content-sizing> | none",
 | |
|     "min-height"                    : "<length> | <percentage> | <content-sizing> | contain-floats | -moz-contain-floats | -webkit-contain-floats",
 | |
|     "min-width"                     : "<length> | <percentage> | <content-sizing> | contain-floats | -moz-contain-floats | -webkit-contain-floats",
 | |
|     "move-to"                       : 1,
 | |
| 
 | |
|     //N
 | |
|     "nav-down"                      : 1,
 | |
|     "nav-index"                     : 1,
 | |
|     "nav-left"                      : 1,
 | |
|     "nav-right"                     : 1,
 | |
|     "nav-up"                        : 1,
 | |
| 
 | |
|     //O
 | |
|     "object-fit"                    : "fill | contain | cover | none | scale-down",
 | |
|     "object-position"               : "<position>",
 | |
|     "opacity"                       : "<opacity-value>",
 | |
|     "order"                         : "<integer>",
 | |
|     "-webkit-order"                 : "<integer>",
 | |
|     "orphans"                       : "<integer>",
 | |
|     "outline"                       : 1,
 | |
|     "outline-color"                 : "<color> | invert",
 | |
|     "outline-offset"                : 1,
 | |
|     "outline-style"                 : "<border-style>",
 | |
|     "outline-width"                 : "<border-width>",
 | |
|     "overflow"                      : "visible | hidden | scroll | auto",
 | |
|     "overflow-style"                : 1,
 | |
|     "overflow-wrap"                 : "normal | break-word",
 | |
|     "overflow-x"                    : 1,
 | |
|     "overflow-y"                    : 1,
 | |
| 
 | |
|     //P
 | |
|     "padding"                       : "<padding-width>{1,4}",
 | |
|     "padding-bottom"                : "<padding-width>",
 | |
|     "padding-left"                  : "<padding-width>",
 | |
|     "padding-right"                 : "<padding-width>",
 | |
|     "padding-top"                   : "<padding-width>",
 | |
|     "page"                          : 1,
 | |
|     "page-break-after"              : "auto | always | avoid | left | right",
 | |
|     "page-break-before"             : "auto | always | avoid | left | right",
 | |
|     "page-break-inside"             : "auto | avoid",
 | |
|     "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,
 | |
|     "pointer-events"                : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all",
 | |
|     "position"                      : "static | relative | absolute | fixed",
 | |
|     "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>",
 | |
|     "rotation"                      : 1,
 | |
|     "rotation-point"                : 1,
 | |
|     "ruby-align"                    : 1,
 | |
|     "ruby-overhang"                 : 1,
 | |
|     "ruby-position"                 : 1,
 | |
|     "ruby-span"                     : 1,
 | |
| 
 | |
|     //S
 | |
|     "shape-rendering"               : "auto | optimizeSpeed | crispEdges | geometricPrecision",
 | |
|     "size"                          : 1,
 | |
|     "speak"                         : "normal | none | spell-out",
 | |
|     "speak-header"                  : "once | always",
 | |
|     "speak-numeral"                 : "digits | continuous",
 | |
|     "speak-punctuation"             : "code | none",
 | |
|     "speech-rate"                   : 1,
 | |
|     "src"                           : 1,
 | |
|     "stop-color"                    : 1,
 | |
|     "stop-opacity"                  : "<opacity-value>",
 | |
|     "stress"                        : 1,
 | |
|     "string-set"                    : 1,
 | |
|     "stroke"                        : "<paint>",
 | |
|     "stroke-dasharray"              : "none | <dasharray>",
 | |
|     "stroke-dashoffset"             : "<percentage> | <length>",
 | |
|     "stroke-linecap"                : "butt | round | square",
 | |
|     "stroke-linejoin"               : "miter | round | bevel",
 | |
|     "stroke-miterlimit"             : "<miterlimit>",
 | |
|     "stroke-opacity"                : "<opacity-value>",
 | |
|     "stroke-width"                  : "<percentage> | <length>",
 | |
| 
 | |
|     "table-layout"                  : "auto | fixed",
 | |
|     "tab-size"                      : "<integer> | <length>",
 | |
|     "target"                        : 1,
 | |
|     "target-name"                   : 1,
 | |
|     "target-new"                    : 1,
 | |
|     "target-position"               : 1,
 | |
|     "text-align"                    : "left | right | center | justify | match-parent | start | end",
 | |
|     "text-align-last"               : 1,
 | |
|     "text-anchor"                   : "start | middle | end",
 | |
|     "text-decoration"               : "<text-decoration-line> || <text-decoration-style> || <text-decoration-color>",
 | |
|     "text-decoration-color"         : "<text-decoration-color>",
 | |
|     "text-decoration-line"          : "<text-decoration-line>",
 | |
|     "text-decoration-style"         : "<text-decoration-style>",
 | |
|     "text-emphasis"                 : 1,
 | |
|     "text-height"                   : 1,
 | |
|     "text-indent"                   : "<length> | <percentage>",
 | |
|     "text-justify"                  : "auto | none | inter-word | inter-ideograph | inter-cluster | distribute | kashida",
 | |
|     "text-outline"                  : 1,
 | |
|     "text-overflow"                 : 1,
 | |
|     "text-rendering"                : "auto | optimizeSpeed | optimizeLegibility | geometricPrecision",
 | |
|     "text-shadow"                   : 1,
 | |
|     "text-transform"                : "capitalize | uppercase | lowercase | none",
 | |
|     "text-wrap"                     : "normal | none | avoid",
 | |
|     "top"                           : "<margin-width>",
 | |
|     "-ms-touch-action"              : "auto | none | pan-x | pan-y | pan-left | pan-right | pan-up | pan-down | manipulation",
 | |
|     "touch-action"                  : "auto | none | pan-x | pan-y | pan-left | pan-right | pan-up | pan-down | manipulation",
 | |
|     "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 | isolate | bidi-override | isolate-override | plaintext",
 | |
|     "user-modify"                   : "read-only | read-write | write-only",
 | |
|     "user-select"                   : "none | text | toggle | element | elements | all",
 | |
| 
 | |
|     //V
 | |
|     "vertical-align"                : "auto | use-script | baseline | sub | super | top | text-top | central | middle | bottom | text-bottom | <percentage> | <length>",
 | |
|     "visibility"                    : "visible | hidden | collapse",
 | |
|     "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 | -pre-wrap | -o-pre-wrap | -moz-pre-wrap | -hp-pre-wrap",   // https://perishablepress.com/wrapping-content/
 | |
|     "white-space-collapse"          : 1,
 | |
|     "widows"                        : "<integer>",
 | |
|     "width"                         : "<length> | <percentage> | <content-sizing> | auto",
 | |
|     "will-change"                   : "<will-change>",
 | |
|     "word-break"                    : "normal | keep-all | break-all",
 | |
|     "word-spacing"                  : "<length> | normal",
 | |
|     "word-wrap"                     : "normal | break-word",
 | |
|     "writing-mode"                  : "horizontal-tb | vertical-rl | vertical-lr | lr-tb | rl-tb | tb-rl | bt-rl | tb-lr | bt-lr | lr-bt | rl-bt | lr | rl | tb",
 | |
| 
 | |
|     //Z
 | |
|     "z-index"                       : "<integer> | auto",
 | |
|     "zoom"                          : "<number> | <percentage> | normal"
 | |
| };
 | |
| 
 | |
| },{}],8:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = PropertyName;
 | |
| 
 | |
| var SyntaxUnit = require("../util/SyntaxUnit");
 | |
| 
 | |
| var Parser = require("./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;
 | |
| };
 | |
| 
 | |
| },{"../util/SyntaxUnit":26,"./Parser":6}],9:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = PropertyValue;
 | |
| 
 | |
| var SyntaxUnit = require("../util/SyntaxUnit");
 | |
| 
 | |
| var Parser = require("./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;
 | |
| 
 | |
| 
 | |
| },{"../util/SyntaxUnit":26,"./Parser":6}],10:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = PropertyValueIterator;
 | |
| 
 | |
| /**
 | |
|  * 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 previous 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();
 | |
|     }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Drops the last saved bookmark.
 | |
|  * @return {void}
 | |
|  * @method drop
 | |
|  */
 | |
| PropertyValueIterator.prototype.drop = function() {
 | |
|     this._marks.pop();
 | |
| };
 | |
| 
 | |
| },{}],11:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = PropertyValuePart;
 | |
| 
 | |
| var SyntaxUnit = require("../util/SyntaxUnit");
 | |
| 
 | |
| var Colors = require("./Colors");
 | |
| var Parser = require("./Parser");
 | |
| var Tokens = require("./Tokens");
 | |
| 
 | |
| /**
 | |
|  * 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, optionalHint) {
 | |
|     var hint = optionalHint || {};
 | |
| 
 | |
|     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":
 | |
|             case "vh":
 | |
|             case "vw":
 | |
|             case "vmax":
 | |
|             case "vmin":
 | |
|                 this.type = "length";
 | |
|                 break;
 | |
| 
 | |
|             case "fr":
 | |
|                 this.type = "grid";
 | |
|                 break;
 | |
| 
 | |
|             case "deg":
 | |
|             case "rad":
 | |
|             case "grad":
 | |
|             case "turn":
 | |
|                 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)) {  //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
 | |
|         // generated by TokenStream.readURI, so always double-quoted.
 | |
|         this.type   = "uri";
 | |
|         this.uri    = PropertyValuePart.parseString(RegExp.$1);
 | |
|     } else if (/^([^\(]+)\(/i.test(text)) {
 | |
|         this.type   = "function";
 | |
|         this.name   = RegExp.$1;
 | |
|         this.value  = text;
 | |
|     } else if (/^"([^\n\r\f\\"]|\\\r\n|\\[^\r0-9a-f]|\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)*"/i.test(text)) {    //double-quoted string
 | |
|         this.type   = "string";
 | |
|         this.value  = PropertyValuePart.parseString(text);
 | |
|     } else if (/^'([^\n\r\f\\']|\\\r\n|\\[^\r0-9a-f]|\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)*'/i.test(text)) {    //single-quoted string
 | |
|         this.type   = "string";
 | |
|         this.value  = PropertyValuePart.parseString(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_\u00A0-\uFFFF][a-z0-9\-_\u00A0-\uFFFF]*$/i.test(text)) {
 | |
|         this.type   = "identifier";
 | |
|         this.value  = text;
 | |
|     }
 | |
| 
 | |
|     // There can be ambiguity with escape sequences in identifiers, as
 | |
|     // well as with "color" parts which are also "identifiers", so record
 | |
|     // an explicit hint when the token generating this PropertyValuePart
 | |
|     // was an identifier.
 | |
|     this.wasIdent = Boolean(hint.ident);
 | |
| 
 | |
| }
 | |
| 
 | |
| PropertyValuePart.prototype = new SyntaxUnit();
 | |
| PropertyValuePart.prototype.constructor = PropertyValuePart;
 | |
| 
 | |
| /**
 | |
|  * Helper method to parse a CSS string.
 | |
|  */
 | |
| PropertyValuePart.parseString = function(str) {
 | |
|     str = str.slice(1, -1); // Strip surrounding single/double quotes
 | |
|     var replacer = function(match, esc) {
 | |
|         if (/^(\n|\r\n|\r|\f)$/.test(esc)) {
 | |
|             return "";
 | |
|         }
 | |
|         var m = /^[0-9a-f]{1,6}/i.exec(esc);
 | |
|         if (m) {
 | |
|             var codePoint = parseInt(m[0], 16);
 | |
|             if (String.fromCodePoint) {
 | |
|                 return String.fromCodePoint(codePoint);
 | |
|             } else {
 | |
|                 // XXX No support for surrogates on old JavaScript engines.
 | |
|                 return String.fromCharCode(codePoint);
 | |
|             }
 | |
|         }
 | |
|         return esc;
 | |
|     };
 | |
|     return str.replace(/\\(\r\n|[^\r0-9a-f]|[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)/ig,
 | |
|                        replacer);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Helper method to serialize a CSS string.
 | |
|  */
 | |
| PropertyValuePart.serializeString = function(value) {
 | |
|     var replacer = function(match, c) {
 | |
|         if (c === "\"") {
 | |
|             return "\\" + c;
 | |
|         }
 | |
|         var cp = String.codePointAt ? String.codePointAt(0) :
 | |
|             // We only escape non-surrogate chars, so using charCodeAt
 | |
|             // is harmless here.
 | |
|             String.charCodeAt(0);
 | |
|         return "\\" + cp.toString(16) + " ";
 | |
|     };
 | |
|     return "\"" + value.replace(/["\r\n\f]/g, replacer) + "\"";
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * 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) {
 | |
|     var part = new PropertyValuePart(token.value, token.startLine, token.startCol, {
 | |
|         // Tokens can have escaped characters that would fool the type
 | |
|         // identification in the PropertyValuePart constructor, so pass
 | |
|         // in a hint if this was an identifier.
 | |
|         ident: token.type === Tokens.IDENT
 | |
|     });
 | |
|     return part;
 | |
| };
 | |
| 
 | |
| },{"../util/SyntaxUnit":26,"./Colors":1,"./Parser":6,"./Tokens":18}],12:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| var Pseudos = module.exports = {
 | |
|     __proto__:       null,
 | |
|     ":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;
 | |
| };
 | |
| 
 | |
| },{}],13:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = Selector;
 | |
| 
 | |
| var SyntaxUnit = require("../util/SyntaxUnit");
 | |
| 
 | |
| var Parser = require("./Parser");
 | |
| var Specificity = require("./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;
 | |
| 
 | |
| 
 | |
| },{"../util/SyntaxUnit":26,"./Parser":6,"./Specificity":16}],14:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = SelectorPart;
 | |
| 
 | |
| var SyntaxUnit = require("../util/SyntaxUnit");
 | |
| 
 | |
| var Parser = require("./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;
 | |
| 
 | |
| 
 | |
| },{"../util/SyntaxUnit":26,"./Parser":6}],15:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = SelectorSubPart;
 | |
| 
 | |
| var SyntaxUnit = require("../util/SyntaxUnit");
 | |
| 
 | |
| var Parser = require("./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;
 | |
| 
 | |
| 
 | |
| },{"../util/SyntaxUnit":26,"./Parser":6}],16:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = Specificity;
 | |
| 
 | |
| var Pseudos = require("./Pseudos");
 | |
| var SelectorPart = require("./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);
 | |
| };
 | |
| 
 | |
| },{"./Pseudos":12,"./SelectorPart":14}],17:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = TokenStream;
 | |
| 
 | |
| var TokenStreamBase = require("../util/TokenStreamBase");
 | |
| 
 | |
| var PropertyValuePart = require("./PropertyValuePart");
 | |
| var Tokens = require("./Tokens");
 | |
| 
 | |
| var h = /^[0-9a-fA-F]$/,
 | |
|     nonascii = /^[\u00A0-\uFFFF]$/,
 | |
|     nl = /\n|\r\n|\r|\f/,
 | |
|     whitespace = /\u0009|\u000a|\u000c|\u000d|\u0020/;
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // 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 && whitespace.test(c);
 | |
| }
 | |
| 
 | |
| function isNewLine(c) {
 | |
|     return c !== null && nl.test(c);
 | |
| }
 | |
| 
 | |
| function isNameStart(c) {
 | |
|     return c !== null && /[a-z_\u00A0-\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 (Object.prototype.hasOwnProperty.call(supplier, 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.
 | |
|      * @return {Object} A token object representing the next token.
 | |
|      * @method _getToken
 | |
|      * @private
 | |
|      */
 | |
|     _getToken: function() {
 | |
| 
 | |
|         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:
 | |
|                  * - IDENT
 | |
|                  * - CHAR
 | |
|                  */
 | |
|                 case "\\":
 | |
|                     if (/[^\r\n\f]/.test(reader.peek())) {
 | |
|                         token = this.identOrFunctionToken(this.readEscape(c, true), startLine, startCol);
 | |
|                     } else {
 | |
|                         token = this.charToken(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,
 | |
|             endChar:    options.endChar,
 | |
|             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,
 | |
|             ident;
 | |
| 
 | |
|         /*
 | |
|          * 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);
 | |
|         var opts = {};
 | |
| 
 | |
|         if (tt === -1) {
 | |
|             tt = Tokens.CHAR;
 | |
|         } else {
 | |
|             opts.endChar = Tokens[tt].endChar;
 | |
|         }
 | |
| 
 | |
|         return this.createToken(tt, c, startLine, startCol, opts);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * 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 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 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,
 | |
|             uriFns  = ["url(", "url-prefix(", "domain("],
 | |
|             uri;
 | |
| 
 | |
|         //if there's a left paren immediately after, it's a URI or function
 | |
|         if (reader.peek() === "(") {
 | |
|             ident += reader.read();
 | |
|             if (uriFns.indexOf(ident.toLowerCase()) > -1) {
 | |
|                 reader.mark();
 | |
|                 uri = this.readURI(ident);
 | |
|                 if (uri === null) {
 | |
|                     //didn't find a valid URL or there's no closing paren
 | |
|                     reader.reset();
 | |
|                     tt = Tokens.FUNCTION;
 | |
|                 } else {
 | |
|                     tt = Tokens.URI;
 | |
|                     ident = uri;
 | |
|                 }
 | |
|             } 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$|^vmax$|^vmin$|^ch$|^cm$|^mm$|^in$|^pt$|^pc$/i.test(ident)) {
 | |
|                 tt = Tokens.LENGTH;
 | |
|             } else if (/^deg|^rad$|^grad$|^turn$/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,
 | |
|             tt      = Tokens.STRING,
 | |
|             c       = reader.read(),
 | |
|             i;
 | |
| 
 | |
|         while (c) {
 | |
|             string += c;
 | |
| 
 | |
|             if (c === "\\") {
 | |
|                 c = reader.read();
 | |
|                 if (c === null) {
 | |
|                     break; // premature EOF after backslash
 | |
|                 } else if (/[^\r\n\f0-9a-f]/i.test(c)) {
 | |
|                     // single-character escape
 | |
|                     string += c;
 | |
|                 } else {
 | |
|                     // read up to six hex digits
 | |
|                     for (i=0; isHexDigit(c) && i<6; i++) {
 | |
|                         string += c;
 | |
|                         c = reader.read();
 | |
|                     }
 | |
|                     // swallow trailing newline or space
 | |
|                     if (c === "\r" && reader.peek() === "\n") {
 | |
|                         string += c;
 | |
|                         c = reader.read();
 | |
|                     }
 | |
|                     if (isWhitespace(c)) {
 | |
|                         string += c;
 | |
|                     } else {
 | |
|                         // This character is null or not part of the escape;
 | |
|                         // jump back to the top to process it.
 | |
|                         continue;
 | |
|                     }
 | |
|                 }
 | |
|             } else if (c === delim) {
 | |
|                 break; // delimiter found.
 | |
|             } else if (isNewLine(reader.peek())) {
 | |
|                 // newline without an escapement: it's an invalid string
 | |
|                 tt = Tokens.INVALID;
 | |
|                 break;
 | |
|             }
 | |
|             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 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;
 | |
|     },
 | |
| 
 | |
|     // returns null w/o resetting reader if string is invalid.
 | |
|     readString: function() {
 | |
|         var token = this.stringToken(this._reader.read(), 0, 0);
 | |
|         return token.type === Tokens.INVALID ? null : token.value;
 | |
|     },
 | |
| 
 | |
|     // returns null w/o resetting reader if URI is invalid.
 | |
|     readURI: function(first) {
 | |
|         var reader  = this._reader,
 | |
|             uri     = first,
 | |
|             inner   = "",
 | |
|             c       = reader.peek();
 | |
| 
 | |
|         //skip whitespace before
 | |
|         while (c && isWhitespace(c)) {
 | |
|             reader.read();
 | |
|             c = reader.peek();
 | |
|         }
 | |
| 
 | |
|         //it's a string
 | |
|         if (c === "'" || c === "\"") {
 | |
|             inner = this.readString();
 | |
|             if (inner !== null) {
 | |
|                 inner = PropertyValuePart.parseString(inner);
 | |
|             }
 | |
|         } else {
 | |
|             inner = this.readUnquotedURL();
 | |
|         }
 | |
| 
 | |
|         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 === null || c !== ")") {
 | |
|             uri = null;
 | |
|         } else {
 | |
|             // Ensure argument to URL is always double-quoted
 | |
|             // (This simplifies later processing in PropertyValuePart.)
 | |
|             uri += PropertyValuePart.serializeString(inner) + reader.read();
 | |
|         }
 | |
| 
 | |
|         return uri;
 | |
|     },
 | |
|     // This method never fails, although it may return an empty string.
 | |
|     readUnquotedURL: function(first) {
 | |
|         var reader  = this._reader,
 | |
|             url     = first || "",
 | |
|             c;
 | |
| 
 | |
|         for (c = reader.peek(); c; c = reader.peek()) {
 | |
|             // Note that the grammar at
 | |
|             // https://www.w3.org/TR/CSS2/grammar.html#scanner
 | |
|             // incorrectly includes the backslash character in the
 | |
|             // `url` production, although it is correctly omitted in
 | |
|             // the `baduri1` production.
 | |
|             if (nonascii.test(c) || /^[\-!#$%&*-\[\]-~]$/.test(c)) {
 | |
|                 url += c;
 | |
|                 reader.read();
 | |
|             } else if (c === "\\") {
 | |
|                 if (/^[^\r\n\f]$/.test(reader.peek(2))) {
 | |
|                     url += this.readEscape(reader.read(), true);
 | |
|                 } else {
 | |
|                     break; // bad escape sequence.
 | |
|                 }
 | |
|             } else {
 | |
|                 break; // bad character
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return url;
 | |
|     },
 | |
| 
 | |
|     readName: function(first) {
 | |
|         var reader  = this._reader,
 | |
|             ident   = first || "",
 | |
|             c;
 | |
| 
 | |
|         for (c = reader.peek(); c; c = reader.peek()) {
 | |
|             if (c === "\\") {
 | |
|                 if (/^[^\r\n\f]$/.test(reader.peek(2))) {
 | |
|                     ident += this.readEscape(reader.read(), true);
 | |
|                 } else {
 | |
|                     // Bad escape sequence.
 | |
|                     break;
 | |
|                 }
 | |
|             } else if (isNameChar(c)) {
 | |
|                 ident += reader.read();
 | |
|             } else {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return ident;
 | |
|     },
 | |
| 
 | |
|     readEscape: function(first, unescape) {
 | |
|         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 === 1) {
 | |
|             if (/^[^\r\n\f0-9a-f]$/.test(c)) {
 | |
|                 reader.read();
 | |
|                 if (unescape) {
 | |
|                     return c;
 | |
|                 }
 | |
|             } else {
 | |
|                 // We should never get here (readName won't call readEscape
 | |
|                 // if the escape sequence is bad).
 | |
|                 throw new Error("Bad escape sequence.");
 | |
|             }
 | |
|         } else if (c === "\r") {
 | |
|             reader.read();
 | |
|             if (reader.peek() === "\n") {
 | |
|                 c += reader.read();
 | |
|             }
 | |
|         } else if (/^[ \t\n\f]$/.test(c)) {
 | |
|             reader.read();
 | |
|         } else {
 | |
|             c = "";
 | |
|         }
 | |
| 
 | |
|         if (unescape) {
 | |
|             var cp = parseInt(cssEscape.slice(first.length), 16);
 | |
|             return String.fromCodePoint ? String.fromCodePoint(cp) :
 | |
|                 String.fromCharCode(cp);
 | |
|         }
 | |
|         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 "";
 | |
|         }
 | |
| 
 | |
|     }
 | |
| });
 | |
| 
 | |
| },{"../util/TokenStreamBase":27,"./PropertyValuePart":11,"./Tokens":18}],18:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| var Tokens = module.exports = [
 | |
| 
 | |
|     /*
 | |
|      * The following token names are defined in CSS3 Grammar: https://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: "SUPPORTS_SYM", text: "@supports" },
 | |
|     { name: "VIEWPORT_SYM", text: ["@viewport", "@-ms-viewport", "@-o-viewport"] },
 | |
|     { name: "DOCUMENT_SYM", text: ["@document", "@-moz-document"] },
 | |
|     { name: "UNKNOWN_SYM" },
 | |
|     //{ name: "ATKEYWORD"},
 | |
| 
 | |
|     // CSS3 animations
 | |
|     { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-o-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: https://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: https://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",
 | |
|         endChar: "}",
 | |
|         text: "{"
 | |
|     },
 | |
|     {
 | |
|         name: "RBRACE",
 | |
|         text: "}"
 | |
|     },
 | |
|     {
 | |
|         name: "LBRACKET",
 | |
|         endChar: "]",
 | |
|         text: "["
 | |
|     },
 | |
|     {
 | |
|         name: "RBRACKET",
 | |
|         text: "]"
 | |
|     },
 | |
|     {
 | |
|         name: "EQUALS",
 | |
|         text: "="
 | |
|     },
 | |
|     {
 | |
|         name: "COLON",
 | |
|         text: ":"
 | |
|     },
 | |
|     {
 | |
|         name: "SEMICOLON",
 | |
|         text: ";"
 | |
|     },
 | |
|     {
 | |
|         name: "LPAREN",
 | |
|         endChar: ")",
 | |
|         text: "("
 | |
|     },
 | |
|     {
 | |
|         name: "RPAREN",
 | |
|         text: ")"
 | |
|     },
 | |
|     {
 | |
|         name: "DOT",
 | |
|         text: "."
 | |
|     }
 | |
| ];
 | |
| 
 | |
| (function() {
 | |
|     var nameMap = [],
 | |
|         typeMap = Object.create(null);
 | |
| 
 | |
|     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;
 | |
|     };
 | |
| })();
 | |
| 
 | |
| },{}],19:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| /* exported Validation */
 | |
| 
 | |
| var Matcher = require("./Matcher");
 | |
| var Properties = require("./Properties");
 | |
| var ValidationTypes = require("./ValidationTypes");
 | |
| var ValidationError = require("./ValidationError");
 | |
| var PropertyValueIterator = require("./PropertyValueIterator");
 | |
| 
 | |
| var Validation = module.exports = {
 | |
| 
 | |
|     validate: function(property, value) {
 | |
| 
 | |
|         //normalize name
 | |
|         var name        = property.toString().toLowerCase(),
 | |
|             expression  = new PropertyValueIterator(value),
 | |
|             spec        = Properties[name],
 | |
|             part;
 | |
| 
 | |
|         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") {
 | |
| 
 | |
|             // All properties accept some CSS-wide values.
 | |
|             // https://drafts.csswg.org/css-values-3/#common-keywords
 | |
|             if (ValidationTypes.isAny(expression, "inherit | initial | unset")) {
 | |
|                 if (expression.hasNext()) {
 | |
|                     part = expression.next();
 | |
|                     throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
 | |
|                 }
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Property-specific validation.
 | |
|             this.singleProperty(spec, expression);
 | |
| 
 | |
|         }
 | |
| 
 | |
|     },
 | |
| 
 | |
|     singleProperty: function(types, expression) {
 | |
| 
 | |
|         var result      = false,
 | |
|             value       = expression.value,
 | |
|             part;
 | |
| 
 | |
|         result = Matcher.parse(types).match(expression);
 | |
| 
 | |
|         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 (" + ValidationTypes.describe(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);
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
| };
 | |
| 
 | |
| },{"./Matcher":3,"./Properties":7,"./PropertyValueIterator":10,"./ValidationError":20,"./ValidationTypes":21}],20:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = ValidationError;
 | |
| 
 | |
| /**
 | |
|  * 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();
 | |
| 
 | |
| },{}],21:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| var ValidationTypes = module.exports;
 | |
| 
 | |
| var Matcher = require("./Matcher");
 | |
| 
 | |
| function copy(to, from) {
 | |
|     Object.keys(from).forEach(function(prop) {
 | |
|         to[prop] = from[prop];
 | |
|     });
 | |
| }
 | |
| copy(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 (args[i].charAt(0) === "<") {
 | |
|                 found = this.simple[args[i]](part);
 | |
|             } else if (args[i].slice(-2) === "()") {
 | |
|                 found = (part.type === "function" &&
 | |
|                          part.name === args[i].slice(0, -2));
 | |
|             } else if (text === args[i].toLowerCase()) {
 | |
|                 found = true;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return found;
 | |
|     },
 | |
| 
 | |
|     isSimple: function(type) {
 | |
|         return Boolean(this.simple[type]);
 | |
|     },
 | |
| 
 | |
|     isComplex: function(type) {
 | |
|         return Boolean(this.complex[type]);
 | |
|     },
 | |
| 
 | |
|     describe: function(type) {
 | |
|         if (this.complex[type] instanceof Matcher) {
 | |
|             return this.complex[type].toString(0);
 | |
|         }
 | |
|         return 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 expression
 | |
|      * 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 if (this.complex[type] instanceof Matcher) {
 | |
|             result = this.complex[type].match(expression);
 | |
|         } else {
 | |
|             result = this.complex[type](expression);
 | |
|         }
 | |
| 
 | |
|         return result;
 | |
|     },
 | |
| 
 | |
| 
 | |
|     simple: {
 | |
|         __proto__: null,
 | |
| 
 | |
|         "<absolute-size>":
 | |
|             "xx-small | x-small | small | medium | large | x-large | xx-large",
 | |
| 
 | |
|         "<animateable-feature>":
 | |
|             "scroll-position | contents | <animateable-feature-name>",
 | |
| 
 | |
|         "<animateable-feature-name>": function(part) {
 | |
|             return this["<ident>"](part) &&
 | |
|                 !/^(unset|initial|inherit|will-change|auto|scroll-position|contents)$/i.test(part);
 | |
|         },
 | |
| 
 | |
|         "<angle>": function(part) {
 | |
|             return part.type === "angle";
 | |
|         },
 | |
| 
 | |
|         "<attachment>": "scroll | fixed | local",
 | |
| 
 | |
|         "<attr>": "attr()",
 | |
| 
 | |
|         // inset() = inset( <shape-arg>{1,4} [round <border-radius>]? )
 | |
|         // circle() = circle( [<shape-radius>]? [at <position>]? )
 | |
|         // ellipse() = ellipse( [<shape-radius>{2}]? [at <position>]? )
 | |
|         // polygon() = polygon( [<fill-rule>,]? [<shape-arg> <shape-arg>]# )
 | |
|         "<basic-shape>": "inset() | circle() | ellipse() | polygon()",
 | |
| 
 | |
|         "<bg-image>": "<image> | <gradient> | none",
 | |
| 
 | |
|         "<border-style>":
 | |
|             "none | hidden | dotted | dashed | solid | double | groove | " +
 | |
|             "ridge | inset | outset",
 | |
| 
 | |
|         "<border-width>": "<length> | thin | medium | thick",
 | |
| 
 | |
|         "<box>": "padding-box | border-box | content-box",
 | |
| 
 | |
|         "<clip-source>": "<uri>",
 | |
| 
 | |
|         "<color>": function(part) {
 | |
|             return part.type === "color" || String(part) === "transparent" || String(part) === "currentColor";
 | |
|         },
 | |
| 
 | |
|         // The SVG <color> spec doesn't include "currentColor" or "transparent" as a color.
 | |
|         "<color-svg>": function(part) {
 | |
|             return part.type === "color";
 | |
|         },
 | |
| 
 | |
|         "<content>": "content()",
 | |
| 
 | |
|         // https://www.w3.org/TR/css3-sizing/#width-height-keywords
 | |
|         "<content-sizing>":
 | |
|             "fill-available | -moz-available | -webkit-fill-available | " +
 | |
|             "max-content | -moz-max-content | -webkit-max-content | " +
 | |
|             "min-content | -moz-min-content | -webkit-min-content | " +
 | |
|             "fit-content | -moz-fit-content | -webkit-fit-content",
 | |
| 
 | |
|         "<feature-tag-value>": function(part) {
 | |
|             return part.type === "function" && /^[A-Z0-9]{4}$/i.test(part);
 | |
|         },
 | |
| 
 | |
|         // custom() isn't actually in the spec
 | |
|         "<filter-function>":
 | |
|             "blur() | brightness() | contrast() | custom() | " +
 | |
|             "drop-shadow() | grayscale() | hue-rotate() | invert() | " +
 | |
|             "opacity() | saturate() | sepia()",
 | |
| 
 | |
|         "<flex-basis>": "<width>",
 | |
| 
 | |
|         "<flex-direction>": "row | row-reverse | column | column-reverse",
 | |
| 
 | |
|         "<flex-grow>": "<number>",
 | |
| 
 | |
|         "<flex-shrink>": "<number>",
 | |
| 
 | |
|         "<flex-wrap>": "nowrap | wrap | wrap-reverse",
 | |
| 
 | |
|         "<font-size>":
 | |
|             "<absolute-size> | <relative-size> | <length> | <percentage>",
 | |
| 
 | |
|         "<font-stretch>":
 | |
|             "normal | ultra-condensed | extra-condensed | condensed | " +
 | |
|             "semi-condensed | semi-expanded | expanded | extra-expanded | " +
 | |
|             "ultra-expanded",
 | |
| 
 | |
|         "<font-style>": "normal | italic | oblique",
 | |
| 
 | |
|         "<font-variant-caps>":
 | |
|             "small-caps | all-small-caps | petite-caps | all-petite-caps | " +
 | |
|             "unicase | titling-caps",
 | |
| 
 | |
|         "<font-variant-css21>": "normal | small-caps",
 | |
| 
 | |
|         "<font-weight>":
 | |
|             "normal | bold | bolder | lighter | " +
 | |
|             "100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900",
 | |
| 
 | |
|         "<generic-family>":
 | |
|             "serif | sans-serif | cursive | fantasy | monospace",
 | |
| 
 | |
|         "<geometry-box>": "<shape-box> | fill-box | stroke-box | view-box",
 | |
| 
 | |
|         "<glyph-angle>": function(part) {
 | |
|             return part.type === "angle" && part.units === "deg";
 | |
|         },
 | |
| 
 | |
|         "<gradient>": function(part) {
 | |
|             return part.type === "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?(?:repeating\-)?(?:radial\-|linear\-)?gradient/i.test(part);
 | |
|         },
 | |
| 
 | |
|         "<icccolor>":
 | |
|             "cielab() | cielch() | cielchab() | " +
 | |
|             "icc-color() | icc-named-color()",
 | |
| 
 | |
|         //any identifier
 | |
|         "<ident>": function(part) {
 | |
|             return part.type === "identifier" || part.wasIdent;
 | |
|         },
 | |
| 
 | |
|         "<ident-not-generic-family>": function(part) {
 | |
|             return this["<ident>"](part) && !this["<generic-family>"](part);
 | |
|         },
 | |
| 
 | |
|         "<image>": "<uri>",
 | |
| 
 | |
|         "<integer>": function(part) {
 | |
|             return part.type === "integer";
 | |
|         },
 | |
| 
 | |
|         "<length>": function(part) {
 | |
|             if (part.type === "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?calc/i.test(part)) {
 | |
|                 return true;
 | |
|             } else {
 | |
|                 return part.type === "length" || part.type === "number" || part.type === "integer" || String(part) === "0";
 | |
|             }
 | |
|         },
 | |
| 
 | |
|         "<line>": function(part) {
 | |
|             return part.type === "integer";
 | |
|         },
 | |
| 
 | |
|         "<line-height>": "<number> | <length> | <percentage> | normal",
 | |
| 
 | |
|         "<margin-width>": "<length> | <percentage> | auto",
 | |
| 
 | |
|         "<miterlimit>": function(part) {
 | |
|             return this["<number>"](part) && part.value >= 1;
 | |
|         },
 | |
| 
 | |
|         "<nonnegative-length-or-percentage>": function(part) {
 | |
|             return (this["<length>"](part) || this["<percentage>"](part)) &&
 | |
|                 (String(part) === "0" || part.type === "function" || (part.value) >= 0);
 | |
|         },
 | |
| 
 | |
|         "<nonnegative-number-or-percentage>": function(part) {
 | |
|             return (this["<number>"](part) || this["<percentage>"](part)) &&
 | |
|                 (String(part) === "0" || part.type === "function" || (part.value) >= 0);
 | |
|         },
 | |
| 
 | |
|         "<number>": function(part) {
 | |
|             return part.type === "number" || this["<integer>"](part);
 | |
|         },
 | |
| 
 | |
|         "<opacity-value>": function(part) {
 | |
|             return this["<number>"](part) && part.value >= 0 && part.value <= 1;
 | |
|         },
 | |
| 
 | |
|         "<padding-width>": "<nonnegative-length-or-percentage>",
 | |
| 
 | |
|         "<percentage>": function(part) {
 | |
|             return part.type === "percentage" || String(part) === "0";
 | |
|         },
 | |
| 
 | |
|         "<relative-size>": "smaller | larger",
 | |
| 
 | |
|         "<shape>": "rect() | inset-rect()",
 | |
| 
 | |
|         "<shape-box>": "<box> | margin-box",
 | |
| 
 | |
|         "<single-animation-direction>":
 | |
|             "normal | reverse | alternate | alternate-reverse",
 | |
| 
 | |
|         "<single-animation-name>": function(part) {
 | |
|             return this["<ident>"](part) &&
 | |
|                 /^-?[a-z_][-a-z0-9_]+$/i.test(part) &&
 | |
|                 !/^(none|unset|initial|inherit)$/i.test(part);
 | |
|         },
 | |
| 
 | |
|         "<string>": function(part) {
 | |
|             return part.type === "string";
 | |
|         },
 | |
| 
 | |
|         "<time>": function(part) {
 | |
|             return part.type === "time";
 | |
|         },
 | |
| 
 | |
|         "<uri>": function(part) {
 | |
|             return part.type === "uri";
 | |
|         },
 | |
| 
 | |
|         "<width>": "<margin-width>"
 | |
|     },
 | |
| 
 | |
|     complex: {
 | |
|         __proto__: null,
 | |
| 
 | |
|         "<azimuth>":
 | |
|             "<angle>" +
 | |
|             " | " +
 | |
|             "[ [ left-side | far-left | left | center-left | center | " +
 | |
|             "center-right | right | far-right | right-side ] || behind ]" +
 | |
|             " | "+
 | |
|             "leftwards | rightwards",
 | |
| 
 | |
|         "<bg-position>": "<position>#",
 | |
| 
 | |
|         "<bg-size>":
 | |
|             "[ <length> | <percentage> | auto ]{1,2} | cover | contain",
 | |
| 
 | |
|         "<border-image-slice>":
 | |
|         // [<number> | <percentage>]{1,4} && fill?
 | |
|         // *but* fill can appear between any of the numbers
 | |
|         Matcher.many([true /* first element is required */],
 | |
|                      Matcher.cast("<nonnegative-number-or-percentage>"),
 | |
|                      Matcher.cast("<nonnegative-number-or-percentage>"),
 | |
|                      Matcher.cast("<nonnegative-number-or-percentage>"),
 | |
|                      Matcher.cast("<nonnegative-number-or-percentage>"),
 | |
|                      "fill"),
 | |
| 
 | |
|         "<border-radius>":
 | |
|             "<nonnegative-length-or-percentage>{1,4} " +
 | |
|             "[ / <nonnegative-length-or-percentage>{1,4} ]?",
 | |
| 
 | |
|         "<box-shadow>": "none | <shadow>#",
 | |
| 
 | |
|         "<clip-path>": "<basic-shape> || <geometry-box>",
 | |
| 
 | |
|         "<dasharray>":
 | |
|         // "list of comma and/or white space separated <length>s and
 | |
|         // <percentage>s".  There is a non-negative constraint.
 | |
|         Matcher.cast("<nonnegative-length-or-percentage>")
 | |
|             .braces(1, Infinity, "#", Matcher.cast(",").question()),
 | |
| 
 | |
|         "<family-name>":
 | |
|             // <string> | <IDENT>+
 | |
|             "<string> | <ident-not-generic-family> <ident>*",
 | |
| 
 | |
|         "<filter-function-list>": "[ <filter-function> | <uri> ]+",
 | |
| 
 | |
|         // https://www.w3.org/TR/2014/WD-css-flexbox-1-20140325/#flex-property
 | |
|         "<flex>":
 | |
|             "none | [ <flex-grow> <flex-shrink>? || <flex-basis> ]",
 | |
| 
 | |
|         "<font-family>": "[ <generic-family> | <family-name> ]#",
 | |
| 
 | |
|         "<font-shorthand>":
 | |
|             "[ <font-style> || <font-variant-css21> || " +
 | |
|             "<font-weight> || <font-stretch> ]? <font-size> " +
 | |
|             "[ / <line-height> ]? <font-family>",
 | |
| 
 | |
|         "<font-variant-alternates>":
 | |
|             // stylistic(<feature-value-name>)
 | |
|             "stylistic() || " +
 | |
|             "historical-forms || " +
 | |
|             // styleset(<feature-value-name> #)
 | |
|             "styleset() || " +
 | |
|             // character-variant(<feature-value-name> #)
 | |
|             "character-variant() || " +
 | |
|             // swash(<feature-value-name>)
 | |
|             "swash() || " +
 | |
|             // ornaments(<feature-value-name>)
 | |
|             "ornaments() || " +
 | |
|             // annotation(<feature-value-name>)
 | |
|             "annotation()",
 | |
| 
 | |
|         "<font-variant-ligatures>":
 | |
|             // <common-lig-values>
 | |
|             "[ common-ligatures | no-common-ligatures ] || " +
 | |
|             // <discretionary-lig-values>
 | |
|             "[ discretionary-ligatures | no-discretionary-ligatures ] || " +
 | |
|             // <historical-lig-values>
 | |
|             "[ historical-ligatures | no-historical-ligatures ] || " +
 | |
|             // <contextual-alt-values>
 | |
|             "[ contextual | no-contextual ]",
 | |
| 
 | |
|         "<font-variant-numeric>":
 | |
|             // <numeric-figure-values>
 | |
|             "[ lining-nums | oldstyle-nums ] || " +
 | |
|             // <numeric-spacing-values>
 | |
|             "[ proportional-nums | tabular-nums ] || " +
 | |
|             // <numeric-fraction-values>
 | |
|             "[ diagonal-fractions | stacked-fractions ] || " +
 | |
|             "ordinal || slashed-zero",
 | |
| 
 | |
|         "<font-variant-east-asian>":
 | |
|             // <east-asian-variant-values>
 | |
|             "[ jis78 | jis83 | jis90 | jis04 | simplified | traditional ] || " +
 | |
|             // <east-asian-width-values>
 | |
|             "[ full-width | proportional-width ] || " +
 | |
|             "ruby",
 | |
| 
 | |
|         // Note that <color> here is "as defined in the SVG spec", which
 | |
|         // is more restrictive that the <color> defined in the CSS spec.
 | |
|         // none | currentColor | <color> [<icccolor>]? |
 | |
|         // <funciri> [ none | currentColor | <color> [<icccolor>]? ]?
 | |
|         "<paint>": "<paint-basic> | <uri> <paint-basic>?",
 | |
| 
 | |
|         // Helper definition for <paint> above.
 | |
|         "<paint-basic>": "none | currentColor | <color-svg> <icccolor>?",
 | |
| 
 | |
|         "<position>":
 | |
|             // Because our `alt` combinator is ordered, we need to test these
 | |
|             // in order from longest possible match to shortest.
 | |
|             "[ center | [ left | right ] [ <percentage> | <length> ]? ] && " +
 | |
|             "[ center | [ top | bottom ] [ <percentage> | <length> ]? ]" +
 | |
|             " | " +
 | |
|             "[ left | center | right | <percentage> | <length> ] " +
 | |
|             "[ top | center | bottom | <percentage> | <length> ]" +
 | |
|             " | " +
 | |
|             "[ left | center | right | top | bottom | <percentage> | <length> ]",
 | |
| 
 | |
|         "<repeat-style>":
 | |
|             "repeat-x | repeat-y | [ repeat | space | round | no-repeat ]{1,2}",
 | |
| 
 | |
|         "<shadow>":
 | |
|         //inset? && [ <length>{2,4} && <color>? ]
 | |
|         Matcher.many([true /* length is required */],
 | |
|                      Matcher.cast("<length>").braces(2, 4), "inset", "<color>"),
 | |
| 
 | |
|         "<text-decoration-color>":
 | |
|            "<color>",
 | |
| 
 | |
|         "<text-decoration-line>":
 | |
|             "none | [ underline || overline || line-through || blink ]",
 | |
| 
 | |
|         "<text-decoration-style>":
 | |
|             "solid | double | dotted | dashed | wavy",
 | |
| 
 | |
|         "<will-change>":
 | |
|             "auto | <animateable-feature>#",
 | |
| 
 | |
|         "<x-one-radius>":
 | |
|             //[ <length> | <percentage> ] [ <length> | <percentage> ]?
 | |
|             "[ <length> | <percentage> ]{1,2}"
 | |
|     }
 | |
| });
 | |
| 
 | |
| Object.keys(ValidationTypes.simple).forEach(function(nt) {
 | |
|     var rule = ValidationTypes.simple[nt];
 | |
|     if (typeof rule === "string") {
 | |
|         ValidationTypes.simple[nt] = function(part) {
 | |
|             return ValidationTypes.isLiteral(part, rule);
 | |
|         };
 | |
|     }
 | |
| });
 | |
| 
 | |
| Object.keys(ValidationTypes.complex).forEach(function(nt) {
 | |
|     var rule = ValidationTypes.complex[nt];
 | |
|     if (typeof rule === "string") {
 | |
|         ValidationTypes.complex[nt] = Matcher.parse(rule);
 | |
|     }
 | |
| });
 | |
| 
 | |
| // Because this is defined relative to other complex validation types,
 | |
| // we need to define it *after* the rest of the types are initialized.
 | |
| ValidationTypes.complex["<font-variant>"] =
 | |
|     Matcher.oror({ expand: "<font-variant-ligatures>" },
 | |
|                  { expand: "<font-variant-alternates>" },
 | |
|                  "<font-variant-caps>",
 | |
|                  { expand: "<font-variant-numeric>" },
 | |
|                  { expand: "<font-variant-east-asian>" });
 | |
| 
 | |
| },{"./Matcher":3}],22:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = {
 | |
|     Colors            : require("./Colors"),
 | |
|     Combinator        : require("./Combinator"),
 | |
|     Parser            : require("./Parser"),
 | |
|     PropertyName      : require("./PropertyName"),
 | |
|     PropertyValue     : require("./PropertyValue"),
 | |
|     PropertyValuePart : require("./PropertyValuePart"),
 | |
|     Matcher           : require("./Matcher"),
 | |
|     MediaFeature      : require("./MediaFeature"),
 | |
|     MediaQuery        : require("./MediaQuery"),
 | |
|     Selector          : require("./Selector"),
 | |
|     SelectorPart      : require("./SelectorPart"),
 | |
|     SelectorSubPart   : require("./SelectorSubPart"),
 | |
|     Specificity       : require("./Specificity"),
 | |
|     TokenStream       : require("./TokenStream"),
 | |
|     Tokens            : require("./Tokens"),
 | |
|     ValidationError   : require("./ValidationError")
 | |
| };
 | |
| 
 | |
| },{"./Colors":1,"./Combinator":2,"./Matcher":3,"./MediaFeature":4,"./MediaQuery":5,"./Parser":6,"./PropertyName":8,"./PropertyValue":9,"./PropertyValuePart":11,"./Selector":13,"./SelectorPart":14,"./SelectorSubPart":15,"./Specificity":16,"./TokenStream":17,"./Tokens":18,"./ValidationError":20}],23:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = EventTarget;
 | |
| 
 | |
| /**
 | |
|  * 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 = Object.create(null);
 | |
| }
 | |
| 
 | |
| 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;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
| 
 | |
|         }
 | |
|     }
 | |
| };
 | |
| 
 | |
| },{}],24:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = StringReader;
 | |
| 
 | |
| /**
 | |
|  * 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(/(\r\n?|\n)/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.peek();
 | |
| 
 | |
|         while (c !== null && filter(c)) {
 | |
|             buffer += this.read();
 | |
|             c = this.peek();
 | |
|         }
 | |
| 
 | |
|         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} matcher 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.slice(0, matcher.length) === matcher) {
 | |
|                 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;
 | |
|     }
 | |
| 
 | |
| };
 | |
| 
 | |
| },{}],25:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = SyntaxError;
 | |
| 
 | |
| /**
 | |
|  * 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) {
 | |
|     Error.call(this);
 | |
|     this.name = this.constructor.name;
 | |
| 
 | |
|     /**
 | |
|      * 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 = Object.create(Error.prototype); // jshint ignore:line
 | |
| SyntaxError.prototype.constructor = SyntaxError; // jshint ignore:line
 | |
| 
 | |
| },{}],26:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = SyntaxUnit;
 | |
| 
 | |
| /**
 | |
|  * 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;
 | |
|     }
 | |
| 
 | |
| };
 | |
| 
 | |
| },{}],27:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = TokenStreamBase;
 | |
| 
 | |
| var StringReader = require("./StringReader");
 | |
| var SyntaxError = require("./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 = new StringReader(input ? input.toString() : "");
 | |
| 
 | |
|     /**
 | |
|      * 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     = Object.create(null),
 | |
|         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.
 | |
|      * @return {void}
 | |
|      * @method mustMatch
 | |
|      */
 | |
|     mustMatch: function(tokenTypes) {
 | |
| 
 | |
|         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,
 | |
|             i           =0,
 | |
|             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.");
 | |
|         }
 | |
|     }
 | |
| 
 | |
| };
 | |
| 
 | |
| 
 | |
| },{"./StringReader":24,"./SyntaxError":25}],28:[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = {
 | |
|     StringReader    : require("./StringReader"),
 | |
|     SyntaxError     : require("./SyntaxError"),
 | |
|     SyntaxUnit      : require("./SyntaxUnit"),
 | |
|     EventTarget     : require("./EventTarget"),
 | |
|     TokenStreamBase : require("./TokenStreamBase")
 | |
| };
 | |
| 
 | |
| },{"./EventTarget":23,"./StringReader":24,"./SyntaxError":25,"./SyntaxUnit":26,"./TokenStreamBase":27}],"parserlib":[function(require,module,exports){
 | |
| "use strict";
 | |
| 
 | |
| module.exports = {
 | |
|     css  : require("./css"),
 | |
|     util : require("./util")
 | |
| };
 | |
| 
 | |
| },{"./css":22,"./util":28}]},{},[]);
 | |
| 
 | |
| return require('parserlib');
 | |
| })();
 | |
| var clone = (function() {
 | |
| 'use strict';
 | |
| 
 | |
| var nativeMap;
 | |
| try {
 | |
|   nativeMap = Map;
 | |
| } catch(_) {
 | |
|   // maybe a reference error because no `Map`. Give it a dummy value that no
 | |
|   // value will ever be an instanceof.
 | |
|   nativeMap = function() {};
 | |
| }
 | |
| 
 | |
| var nativeSet;
 | |
| try {
 | |
|   nativeSet = Set;
 | |
| } catch(_) {
 | |
|   nativeSet = function() {};
 | |
| }
 | |
| 
 | |
| var nativePromise;
 | |
| try {
 | |
|   nativePromise = Promise;
 | |
| } catch(_) {
 | |
|   nativePromise = function() {};
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Clones (copies) an Object using deep copying.
 | |
|  *
 | |
|  * This function supports circular references by default, but if you are certain
 | |
|  * there are no circular references in your object, you can save some CPU time
 | |
|  * by calling clone(obj, false).
 | |
|  *
 | |
|  * Caution: if `circular` is false and `parent` contains circular references,
 | |
|  * your program may enter an infinite loop and crash.
 | |
|  *
 | |
|  * @param `parent` - the object to be cloned
 | |
|  * @param `circular` - set to true if the object to be cloned may contain
 | |
|  *    circular references. (optional - true by default)
 | |
|  * @param `depth` - set to a number if the object is only to be cloned to
 | |
|  *    a particular depth. (optional - defaults to Infinity)
 | |
|  * @param `prototype` - sets the prototype to be used when cloning an object.
 | |
|  *    (optional - defaults to parent prototype).
 | |
|  * @param `includeNonEnumerable` - set to true if the non-enumerable properties
 | |
|  *    should be cloned as well. Non-enumerable properties on the prototype
 | |
|  *    chain will be ignored. (optional - false by default)
 | |
| */
 | |
| function clone(parent, circular, depth, prototype, includeNonEnumerable) {
 | |
|   if (typeof circular === 'object') {
 | |
|     depth = circular.depth;
 | |
|     prototype = circular.prototype;
 | |
|     includeNonEnumerable = circular.includeNonEnumerable;
 | |
|     circular = circular.circular;
 | |
|   }
 | |
|   // maintain two arrays for circular references, where corresponding parents
 | |
|   // and children have the same index
 | |
|   var allParents = [];
 | |
|   var allChildren = [];
 | |
| 
 | |
|   var useBuffer = typeof Buffer != 'undefined';
 | |
| 
 | |
|   if (typeof circular == 'undefined')
 | |
|     circular = true;
 | |
| 
 | |
|   if (typeof depth == 'undefined')
 | |
|     depth = Infinity;
 | |
| 
 | |
|   // recurse this function so we don't reset allParents and allChildren
 | |
|   function _clone(parent, depth) {
 | |
|     // cloning null always returns null
 | |
|     if (parent === null)
 | |
|       return null;
 | |
| 
 | |
|     if (depth === 0)
 | |
|       return parent;
 | |
| 
 | |
|     var child;
 | |
|     var proto;
 | |
|     if (typeof parent != 'object') {
 | |
|       return parent;
 | |
|     }
 | |
| 
 | |
|     if (parent instanceof nativeMap) {
 | |
|       child = new nativeMap();
 | |
|     } else if (parent instanceof nativeSet) {
 | |
|       child = new nativeSet();
 | |
|     } else if (parent instanceof nativePromise) {
 | |
|       child = new nativePromise(function (resolve, reject) {
 | |
|         parent.then(function(value) {
 | |
|           resolve(_clone(value, depth - 1));
 | |
|         }, function(err) {
 | |
|           reject(_clone(err, depth - 1));
 | |
|         });
 | |
|       });
 | |
|     } else if (clone.__isArray(parent)) {
 | |
|       child = [];
 | |
|     } else if (clone.__isRegExp(parent)) {
 | |
|       child = new RegExp(parent.source, __getRegExpFlags(parent));
 | |
|       if (parent.lastIndex) child.lastIndex = parent.lastIndex;
 | |
|     } else if (clone.__isDate(parent)) {
 | |
|       child = new Date(parent.getTime());
 | |
|     } else if (useBuffer && Buffer.isBuffer(parent)) {
 | |
|       child = new Buffer(parent.length);
 | |
|       parent.copy(child);
 | |
|       return child;
 | |
|     } else if (parent instanceof Error) {
 | |
|       child = Object.create(parent);
 | |
|     } else {
 | |
|       if (typeof prototype == 'undefined') {
 | |
|         proto = Object.getPrototypeOf(parent);
 | |
|         child = Object.create(proto);
 | |
|       }
 | |
|       else {
 | |
|         child = Object.create(prototype);
 | |
|         proto = prototype;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (circular) {
 | |
|       var index = allParents.indexOf(parent);
 | |
| 
 | |
|       if (index != -1) {
 | |
|         return allChildren[index];
 | |
|       }
 | |
|       allParents.push(parent);
 | |
|       allChildren.push(child);
 | |
|     }
 | |
| 
 | |
|     if (parent instanceof nativeMap) {
 | |
|       var keyIterator = parent.keys();
 | |
|       while(true) {
 | |
|         var next = keyIterator.next();
 | |
|         if (next.done) {
 | |
|           break;
 | |
|         }
 | |
|         var keyChild = _clone(next.value, depth - 1);
 | |
|         var valueChild = _clone(parent.get(next.value), depth - 1);
 | |
|         child.set(keyChild, valueChild);
 | |
|       }
 | |
|     }
 | |
|     if (parent instanceof nativeSet) {
 | |
|       var iterator = parent.keys();
 | |
|       while(true) {
 | |
|         var next = iterator.next();
 | |
|         if (next.done) {
 | |
|           break;
 | |
|         }
 | |
|         var entryChild = _clone(next.value, depth - 1);
 | |
|         child.add(entryChild);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     for (var i in parent) {
 | |
|       var attrs;
 | |
|       if (proto) {
 | |
|         attrs = Object.getOwnPropertyDescriptor(proto, i);
 | |
|       }
 | |
| 
 | |
|       if (attrs && attrs.set == null) {
 | |
|         continue;
 | |
|       }
 | |
|       child[i] = _clone(parent[i], depth - 1);
 | |
|     }
 | |
| 
 | |
|     if (Object.getOwnPropertySymbols) {
 | |
|       var symbols = Object.getOwnPropertySymbols(parent);
 | |
|       for (var i = 0; i < symbols.length; i++) {
 | |
|         // Don't need to worry about cloning a symbol because it is a primitive,
 | |
|         // like a number or string.
 | |
|         var symbol = symbols[i];
 | |
|         var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
 | |
|         if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
 | |
|           continue;
 | |
|         }
 | |
|         child[symbol] = _clone(parent[symbol], depth - 1);
 | |
|         if (!descriptor.enumerable) {
 | |
|           Object.defineProperty(child, symbol, {
 | |
|             enumerable: false
 | |
|           });
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (includeNonEnumerable) {
 | |
|       var allPropertyNames = Object.getOwnPropertyNames(parent);
 | |
|       for (var i = 0; i < allPropertyNames.length; i++) {
 | |
|         var propertyName = allPropertyNames[i];
 | |
|         var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
 | |
|         if (descriptor && descriptor.enumerable) {
 | |
|           continue;
 | |
|         }
 | |
|         child[propertyName] = _clone(parent[propertyName], depth - 1);
 | |
|         Object.defineProperty(child, propertyName, {
 | |
|           enumerable: false
 | |
|         });
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return child;
 | |
|   }
 | |
| 
 | |
|   return _clone(parent, depth);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Simple flat clone using prototype, accepts only objects, usefull for property
 | |
|  * override on FLAT configuration object (no nested props).
 | |
|  *
 | |
|  * USE WITH CAUTION! This may not behave as you wish if you do not know how this
 | |
|  * works.
 | |
|  */
 | |
| clone.clonePrototype = function clonePrototype(parent) {
 | |
|   if (parent === null)
 | |
|     return null;
 | |
| 
 | |
|   var c = function () {};
 | |
|   c.prototype = parent;
 | |
|   return new c();
 | |
| };
 | |
| 
 | |
| // private utility functions
 | |
| 
 | |
| function __objToStr(o) {
 | |
|   return Object.prototype.toString.call(o);
 | |
| }
 | |
| clone.__objToStr = __objToStr;
 | |
| 
 | |
| function __isDate(o) {
 | |
|   return typeof o === 'object' && __objToStr(o) === '[object Date]';
 | |
| }
 | |
| clone.__isDate = __isDate;
 | |
| 
 | |
| function __isArray(o) {
 | |
|   return typeof o === 'object' && __objToStr(o) === '[object Array]';
 | |
| }
 | |
| clone.__isArray = __isArray;
 | |
| 
 | |
| function __isRegExp(o) {
 | |
|   return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
 | |
| }
 | |
| clone.__isRegExp = __isRegExp;
 | |
| 
 | |
| function __getRegExpFlags(re) {
 | |
|   var flags = '';
 | |
|   if (re.global) flags += 'g';
 | |
|   if (re.ignoreCase) flags += 'i';
 | |
|   if (re.multiline) flags += 'm';
 | |
|   return flags;
 | |
| }
 | |
| clone.__getRegExpFlags = __getRegExpFlags;
 | |
| 
 | |
| return clone;
 | |
| })();
 | |
| 
 | |
| if (typeof module === 'object' && module.exports) {
 | |
|   module.exports = clone;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Main CSSLint object.
 | |
|  * @class CSSLint
 | |
|  * @static
 | |
|  * @extends parserlib.util.EventTarget
 | |
|  */
 | |
| 
 | |
| /* global parserlib, clone, Reporter */
 | |
| /* exported CSSLint */
 | |
| 
 | |
| var CSSLint = (function() {
 | |
|     "use strict";
 | |
| 
 | |
|     var rules           = [],
 | |
|         formatters      = [],
 | |
|         embeddedRuleset = /\/\*\s*csslint([^\*]*)\*\//,
 | |
|         api             = new parserlib.util.EventTarget();
 | |
| 
 | |
|     api.version = "1.0.4";
 | |
| 
 | |
|     //-------------------------------------------------------------------------
 | |
|     // 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;
 | |
|         });
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Returns a ruleset configuration object with all current rules.
 | |
|      * @return A ruleset object.
 | |
|      * @method getRuleset
 | |
|      */
 | |
|     api.getRuleset = function() {
 | |
|         var ruleset = {},
 | |
|             i = 0,
 | |
|             len = rules.length;
 | |
| 
 | |
|         while (i < len) {
 | |
|             ruleset[rules[i++].id] = 1;    // by default, everything is a warning
 | |
|         }
 | |
| 
 | |
|         return ruleset;
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Returns a ruleset object based on embedded rules.
 | |
|      * @param {String} text A string of css containing embedded rules.
 | |
|      * @param {Object} ruleset A ruleset object to modify.
 | |
|      * @return {Object} A ruleset object.
 | |
|      * @method getEmbeddedRuleset
 | |
|      */
 | |
|     function applyEmbeddedRuleset(text, ruleset) {
 | |
|         var valueMap,
 | |
|             embedded = text && text.match(embeddedRuleset),
 | |
|             rules = embedded && embedded[1];
 | |
| 
 | |
|         if (rules) {
 | |
|             valueMap = {
 | |
|                 "true": 2,  // true is error
 | |
|                 "": 1,      // blank is warning
 | |
|                 "false": 0, // false is ignore
 | |
| 
 | |
|                 "2": 2,     // explicit error
 | |
|                 "1": 1,     // explicit warning
 | |
|                 "0": 0      // explicit ignore
 | |
|             };
 | |
| 
 | |
|             rules.toLowerCase().split(",").forEach(function(rule) {
 | |
|                 var pair = rule.split(":"),
 | |
|                     property = pair[0] || "",
 | |
|                     value = pair[1] || "";
 | |
| 
 | |
|                 ruleset[property.trim()] = valueMap[value.trim()];
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         return ruleset;
 | |
|     }
 | |
| 
 | |
|     //-------------------------------------------------------------------------
 | |
|     // 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,
 | |
|             reporter,
 | |
|             lines,
 | |
|             allow = {},
 | |
|             ignore = [],
 | |
|             report,
 | |
|             parser = new parserlib.css.Parser({
 | |
|                 starHack: true,
 | |
|                 ieFilters: true,
 | |
|                 underscoreHack: true,
 | |
|                 strict: false
 | |
|             });
 | |
| 
 | |
|         // normalize line endings
 | |
|         lines = text.replace(/\n\r?/g, "$split$").split("$split$");
 | |
| 
 | |
|         // find 'allow' comments
 | |
|         CSSLint.Util.forEach(lines, function (line, lineno) {
 | |
|             var allowLine = line && line.match(/\/\*[ \t]*csslint[ \t]+allow:[ \t]*([^\*]*)\*\//i),
 | |
|                 allowRules = allowLine && allowLine[1],
 | |
|                 allowRuleset = {};
 | |
| 
 | |
|             if (allowRules) {
 | |
|                 allowRules.toLowerCase().split(",").forEach(function(allowRule) {
 | |
|                     allowRuleset[allowRule.trim()] = true;
 | |
|                 });
 | |
|                 if (Object.keys(allowRuleset).length > 0) {
 | |
|                     allow[lineno + 1] = allowRuleset;
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         var ignoreStart = null,
 | |
|             ignoreEnd = null;
 | |
|         CSSLint.Util.forEach(lines, function (line, lineno) {
 | |
|             // Keep oldest, "unclosest" ignore:start
 | |
|             if (ignoreStart === null && line.match(/\/\*[ \t]*csslint[ \t]+ignore:start[ \t]*\*\//i)) {
 | |
|                 ignoreStart = lineno;
 | |
|             }
 | |
| 
 | |
|             if (line.match(/\/\*[ \t]*csslint[ \t]+ignore:end[ \t]*\*\//i)) {
 | |
|                 ignoreEnd = lineno;
 | |
|             }
 | |
| 
 | |
|             if (ignoreStart !== null && ignoreEnd !== null) {
 | |
|                 ignore.push([ignoreStart, ignoreEnd]);
 | |
|                 ignoreStart = ignoreEnd = null;
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         // Close remaining ignore block, if any
 | |
|         if (ignoreStart !== null) {
 | |
|             ignore.push([ignoreStart, lines.length]);
 | |
|         }
 | |
| 
 | |
|         if (!ruleset) {
 | |
|             ruleset = this.getRuleset();
 | |
|         }
 | |
| 
 | |
|         if (embeddedRuleset.test(text)) {
 | |
|             // defensively copy so that caller's version does not get modified
 | |
|             ruleset = clone(ruleset);
 | |
|             ruleset = applyEmbeddedRuleset(text, ruleset);
 | |
|         }
 | |
| 
 | |
|         reporter = new Reporter(lines, ruleset, allow, ignore);
 | |
| 
 | |
|         ruleset.errors = 2;       // always report parsing errors as errors
 | |
|         for (i in ruleset) {
 | |
|             if (ruleset.hasOwnProperty(i) && ruleset[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,
 | |
|             ruleset     : reporter.ruleset,
 | |
|             allow       : reporter.allow,
 | |
|             ignore      : reporter.ignore
 | |
|         };
 | |
| 
 | |
|         // 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;
 | |
| 
 | |
| })();
 | |
| 
 | |
| /**
 | |
|  * 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.
 | |
|  * @param {Object} explicitly allowed lines
 | |
|  * @param {[][]} ingore list of line ranges to be ignored
 | |
|  */
 | |
| function Reporter(lines, ruleset, allow, ignore) {
 | |
|     "use strict";
 | |
| 
 | |
|     /**
 | |
|      * 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;
 | |
| 
 | |
|     /**
 | |
|      * Lines with specific rule messages to leave out of the report.
 | |
|      * @property allow
 | |
|      * @type Object
 | |
|      */
 | |
|     this.allow = allow;
 | |
|     if (!this.allow) {
 | |
|         this.allow = {};
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Linesets not to include in the report.
 | |
|      * @property ignore
 | |
|      * @type [][]
 | |
|      */
 | |
|     this.ignore = ignore;
 | |
|     if (!this.ignore) {
 | |
|         this.ignore = [];
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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) {
 | |
|         "use strict";
 | |
|         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) {
 | |
|         "use strict";
 | |
|         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) {
 | |
|         "use strict";
 | |
| 
 | |
|         // Check if rule violation should be allowed
 | |
|         if (this.allow.hasOwnProperty(line) && this.allow[line].hasOwnProperty(rule.id)) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         var ignore = false;
 | |
|         CSSLint.Util.forEach(this.ignore, function (range) {
 | |
|             if (range[0] <= line && line <= range[1]) {
 | |
|                 ignore = true;
 | |
|             }
 | |
|         });
 | |
|         if (ignore) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         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) {
 | |
|         "use strict";
 | |
|         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) {
 | |
|         "use strict";
 | |
|         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) {
 | |
|         "use strict";
 | |
|         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) {
 | |
|         "use strict";
 | |
|         this.stats[name] = value;
 | |
|     }
 | |
| };
 | |
| 
 | |
| // expose for testing purposes
 | |
| CSSLint._Reporter = Reporter;
 | |
| 
 | |
| /*
 | |
|  * Utility functions that make life easier.
 | |
|  */
 | |
| CSSLint.Util = {
 | |
|     /*
 | |
|      * Adds all properties from supplier onto receiver,
 | |
|      * overwriting if the same name already exists on
 | |
|      * receiver.
 | |
|      * @param {Object} The object to receive the properties.
 | |
|      * @param {Object} The object to provide the properties.
 | |
|      * @return {Object} The receiver
 | |
|      */
 | |
|     mix: function(receiver, supplier) {
 | |
|         "use strict";
 | |
|         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) {
 | |
|         "use strict";
 | |
|         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) {
 | |
|         "use strict";
 | |
|         if (values.forEach) {
 | |
|             return values.forEach(func);
 | |
|         } else {
 | |
|             for (var i=0, len=values.length; i < len; i++) {
 | |
|                 func(values[i], i, values);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * 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.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-adjoining-classes",
 | |
|     browsers: "IE6",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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("Adjoining classes: "+selectors[i].text, part.line, part.col, rule);
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * 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.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Beware-of-box-model-size",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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,
 | |
|             boxSizing = false;
 | |
| 
 | |
|         function startRule() {
 | |
|             properties = {};
 | |
|             boxSizing = false;
 | |
|         }
 | |
| 
 | |
|         function endRule() {
 | |
|             var prop, value;
 | |
| 
 | |
|             if (!boxSizing) {
 | |
|                 if (properties.height) {
 | |
|                     for (prop in heightProperties) {
 | |
|                         if (heightProperties.hasOwnProperty(prop) && properties[prop]) {
 | |
|                             value = properties[prop].value;
 | |
|                             // special case for padding
 | |
|                             if (!(prop === "padding" && value.parts.length === 2 && 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]) {
 | |
|                             value = properties[prop].value;
 | |
| 
 | |
|                             if (!(prop === "padding" && value.parts.length === 2 && 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("startviewport", 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.toString() === "none")) {
 | |
|                     properties[name] = {
 | |
|                         line: event.property.line,
 | |
|                         col: event.property.col,
 | |
|                         value: event.value
 | |
|                     };
 | |
|                 }
 | |
|             } else {
 | |
|                 if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)) {
 | |
|                     properties[name] = 1;
 | |
|                 } else if (name === "box-sizing") {
 | |
|                     boxSizing = true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", endRule);
 | |
|         parser.addListener("endfontface", endRule);
 | |
|         parser.addListener("endpage", endRule);
 | |
|         parser.addListener("endpagemargin", endRule);
 | |
|         parser.addListener("endkeyframerule", endRule);
 | |
|         parser.addListener("endviewport", endRule);
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * 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.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-box-sizing",
 | |
|     browsers: "IE6, IE7",
 | |
|     tags: ["Compatibility"],
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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: Use the bulletproof @font-face syntax to avoid 404's in old IE
 | |
|  * (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax)
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "bulletproof-font-face",
 | |
|     name: "Use the bulletproof @font-face syntax",
 | |
|     desc: "Use the bulletproof @font-face syntax to avoid 404's in old IE (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax).",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Bulletproof-font-face",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this,
 | |
|             fontFaceRule = false,
 | |
|             firstSrc = true,
 | |
|             ruleFailed = false,
 | |
|             line, col;
 | |
| 
 | |
|         // Mark the start of a @font-face declaration so we only test properties inside it
 | |
|         parser.addListener("startfontface", function() {
 | |
|             fontFaceRule = true;
 | |
|         });
 | |
| 
 | |
|         parser.addListener("property", function(event) {
 | |
|             // If we aren't inside an @font-face declaration then just return
 | |
|             if (!fontFaceRule) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             var propertyName = event.property.toString().toLowerCase(),
 | |
|                 value = event.value.toString();
 | |
| 
 | |
|             // Set the line and col numbers for use in the endfontface listener
 | |
|             line = event.line;
 | |
|             col = event.col;
 | |
| 
 | |
|             // This is the property that we care about, we can ignore the rest
 | |
|             if (propertyName === "src") {
 | |
|                 var regex = /^\s?url\(['"].+\.eot\?.*['"]\)\s*format\(['"]embedded-opentype['"]\).*$/i;
 | |
| 
 | |
|                 // We need to handle the advanced syntax with two src properties
 | |
|                 if (!value.match(regex) && firstSrc) {
 | |
|                     ruleFailed = true;
 | |
|                     firstSrc = false;
 | |
|                 } else if (value.match(regex) && !firstSrc) {
 | |
|                     ruleFailed = false;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
| 
 | |
|         });
 | |
| 
 | |
|         // Back to normal rules that we don't need to test
 | |
|         parser.addListener("endfontface", function() {
 | |
|             fontFaceRule = false;
 | |
| 
 | |
|             if (ruleFailed) {
 | |
|                 reporter.report("@font-face declaration doesn't follow the fontspring bulletproof syntax.", line, col, rule);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * Rule: Include all compatible vendor prefixes to reach a wider
 | |
|  * range of users.
 | |
|  */
 | |
| 
 | |
| 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.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Require-compatible-vendor-prefixes",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function (parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this,
 | |
|             compatiblePrefixes,
 | |
|             properties,
 | |
|             prop,
 | |
|             variations,
 | |
|             prefixed,
 | |
|             i,
 | |
|             len,
 | |
|             inKeyFrame = false,
 | |
|             arrayPush = Array.prototype.push,
 | |
|             applyTo = [];
 | |
| 
 | |
|         // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details
 | |
|         compatiblePrefixes = {
 | |
|             "animation"                  : "webkit",
 | |
|             "animation-delay"            : "webkit",
 | |
|             "animation-direction"        : "webkit",
 | |
|             "animation-duration"         : "webkit",
 | |
|             "animation-fill-mode"        : "webkit",
 | |
|             "animation-iteration-count"  : "webkit",
 | |
|             "animation-name"             : "webkit",
 | |
|             "animation-play-state"       : "webkit",
 | |
|             "animation-timing-function"  : "webkit",
 | |
|             "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",
 | |
|             "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"                 : "",
 | |
|             "box-shadow"                 : "",
 | |
|             "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 ms",
 | |
|             "transform-origin"           : "webkit ms",
 | |
|             "transition"                 : "",
 | |
|             "transition-delay"           : "",
 | |
|             "transition-duration"        : "",
 | |
|             "transition-property"        : "",
 | |
|             "transition-timing-function" : "",
 | |
|             "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("startkeyframes", function (event) {
 | |
|             inKeyFrame = event.prefix || true;
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endkeyframes", function () {
 | |
|             inKeyFrame = false;
 | |
|         });
 | |
| 
 | |
|         parser.addListener("property", function (event) {
 | |
|             var name = event.property;
 | |
|             if (CSSLint.Util.indexOf(applyTo, name.text) > -1) {
 | |
| 
 | |
|                 // e.g., -moz-transform is okay to be alone in @-moz-keyframes
 | |
|                 if (!inKeyFrame || typeof inKeyFrame !== "string" ||
 | |
|                         name.text.indexOf("-" + inKeyFrame + "-") !== 0) {
 | |
|                     properties.push(name);
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", function () {
 | |
|             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-*
 | |
|  */
 | |
| 
 | |
| 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.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Require-properties-appropriate-for-display",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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("startviewport", 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);
 | |
|         parser.addListener("endviewport", endRule);
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * Rule: Disallow duplicate background-images (using url).
 | |
|  */
 | |
| 
 | |
| 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.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-background-images",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "duplicate-properties",
 | |
|     name: "Disallow duplicate properties",
 | |
|     desc: "Duplicate properties must appear one after the other.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-properties",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this,
 | |
|             properties,
 | |
|             lastProperty;
 | |
| 
 | |
|         function startRule() {
 | |
|             properties = {};
 | |
|         }
 | |
| 
 | |
|         parser.addListener("startrule", startRule);
 | |
|         parser.addListener("startfontface", startRule);
 | |
|         parser.addListener("startpage", startRule);
 | |
|         parser.addListener("startpagemargin", startRule);
 | |
|         parser.addListener("startkeyframerule", startRule);
 | |
|         parser.addListener("startviewport", 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.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "empty-rules",
 | |
|     name: "Disallow empty rules",
 | |
|     desc: "Rules without any properties specified should be removed.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-empty-rules",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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.)
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "errors",
 | |
|     name: "Parsing Errors",
 | |
|     desc: "This rule looks for recoverable syntax errors.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this;
 | |
| 
 | |
|         parser.addListener("error", function(event) {
 | |
|             reporter.error(event.message, event.line, event.col, rule);
 | |
|         });
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| 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.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Require-fallback-colors",
 | |
|     browsers: "IE6,IE7,IE8",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this,
 | |
|             lastProperty,
 | |
|             propertiesToCheck = {
 | |
|                 color: 1,
 | |
|                 background: 1,
 | |
|                 "border-color": 1,
 | |
|                 "border-top-color": 1,
 | |
|                 "border-right-color": 1,
 | |
|                 "border-bottom-color": 1,
 | |
|                 "border-left-color": 1,
 | |
|                 border: 1,
 | |
|                 "border-top": 1,
 | |
|                 "border-right": 1,
 | |
|                 "border-bottom": 1,
 | |
|                 "border-left": 1,
 | |
|                 "background-color": 1
 | |
|             };
 | |
| 
 | |
|         function startRule() {
 | |
|             lastProperty = null;
 | |
|         }
 | |
| 
 | |
|         parser.addListener("startrule", startRule);
 | |
|         parser.addListener("startfontface", startRule);
 | |
|         parser.addListener("startpage", startRule);
 | |
|         parser.addListener("startpagemargin", startRule);
 | |
|         parser.addListener("startkeyframerule", startRule);
 | |
|         parser.addListener("startviewport", 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.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "floats",
 | |
|     name: "Disallow too many floats",
 | |
|     desc: "This rule tests if the float property is used too many times",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-too-many-floats",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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.
 | |
|  */
 | |
| 
 | |
| 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.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-web-fonts",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "font-sizes",
 | |
|     name: "Disallow too many font sizes",
 | |
|     desc: "Checks the number of font-size declarations.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-font-size-declarations",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this,
 | |
|             count = 0;
 | |
| 
 | |
|         // check for use of "font-size"
 | |
|         parser.addListener("property", function(event) {
 | |
|             if (event.property.toString() === "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.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "gradients",
 | |
|     name: "Require all gradient definitions",
 | |
|     desc: "When using a vendor-prefixed gradient, make sure to use them all.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Require-all-gradient-definitions",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this,
 | |
|             gradients;
 | |
| 
 | |
|         parser.addListener("startrule", function() {
 | |
|             gradients = {
 | |
|                 moz: 0,
 | |
|                 webkit: 0,
 | |
|                 oldWebkit: 0,
 | |
|                 o: 0
 | |
|             };
 | |
|         });
 | |
| 
 | |
|         parser.addListener("property", function(event) {
 | |
| 
 | |
|             if (/\-(moz|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.o) {
 | |
|                 missing.push("Opera 11.1+");
 | |
|             }
 | |
| 
 | |
|             if (missing.length && missing.length < 4) {
 | |
|                 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.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "ids",
 | |
|     name: "Disallow IDs in selectors",
 | |
|     desc: "Selectors should not contain IDs.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-IDs-in-selectors",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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: IE6-9 supports up to 31 stylesheet import.
 | |
|  * Reference:
 | |
|  * http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/internet-explorer-stylesheet-rule-selector-import-sheet-limit-maximum.aspx
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "import-ie-limit",
 | |
|     name: "@import limit on IE6-IE9",
 | |
|     desc: "IE6-9 supports up to 31 @import per stylesheet",
 | |
|     browsers: "IE6, IE7, IE8, IE9",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this,
 | |
|             MAX_IMPORT_COUNT = 31,
 | |
|             count = 0;
 | |
| 
 | |
|         function startPage() {
 | |
|             count = 0;
 | |
|         }
 | |
| 
 | |
|         parser.addListener("startpage", startPage);
 | |
| 
 | |
|         parser.addListener("import", function() {
 | |
|             count++;
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endstylesheet", function() {
 | |
|             if (count > MAX_IMPORT_COUNT) {
 | |
|                 reporter.rollupError(
 | |
|                     "Too many @import rules (" + count + "). IE6-9 supports up to 31 import per stylesheet.",
 | |
|                     rule
 | |
|                 );
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * Rule: Don't use @import, use <link> instead.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "import",
 | |
|     name: "Disallow @import",
 | |
|     desc: "Don't use @import, use <link> instead.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-%40import",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "important",
 | |
|     name: "Disallow !important",
 | |
|     desc: "Be careful when using !important declaration",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-%21important",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "known-properties",
 | |
|     name: "Require use of known properties",
 | |
|     desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Require-use-of-known-properties",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this;
 | |
| 
 | |
|         parser.addListener("property", function(event) {
 | |
| 
 | |
|             // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib)
 | |
|             if (event.invalid) {
 | |
|                 reporter.report(event.invalid.message, event.line, event.col, rule);
 | |
|             }
 | |
| 
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * Rule: All properties should be in alphabetical order.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "order-alphabetical",
 | |
|     name: "Alphabetical order",
 | |
|     desc: "Assure properties are in alphabetical order",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this,
 | |
|             properties;
 | |
| 
 | |
|         var startRule = function () {
 | |
|             properties = [];
 | |
|         };
 | |
| 
 | |
|         var endRule = function(event) {
 | |
|             var currentProperties = properties.join(","),
 | |
|                 expectedProperties = properties.sort().join(",");
 | |
| 
 | |
|             if (currentProperties !== expectedProperties) {
 | |
|                 reporter.report("Rule doesn't have all its properties in alphabetical order.", event.line, event.col, rule);
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         parser.addListener("startrule", startRule);
 | |
|         parser.addListener("startfontface", startRule);
 | |
|         parser.addListener("startpage", startRule);
 | |
|         parser.addListener("startpagemargin", startRule);
 | |
|         parser.addListener("startkeyframerule", startRule);
 | |
|         parser.addListener("startviewport", startRule);
 | |
| 
 | |
|         parser.addListener("property", function(event) {
 | |
|             var name = event.property.text,
 | |
|                 lowerCasePrefixLessName = name.toLowerCase().replace(/^-.*?-/, "");
 | |
| 
 | |
|             properties.push(lowerCasePrefixLessName);
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", endRule);
 | |
|         parser.addListener("endfontface", endRule);
 | |
|         parser.addListener("endpage", endRule);
 | |
|         parser.addListener("endpagemargin", endRule);
 | |
|         parser.addListener("endkeyframerule", endRule);
 | |
|         parser.addListener("endviewport", endRule);
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * 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.
 | |
|  */
 | |
| 
 | |
| 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.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-outline%3Anone",
 | |
|     browsers: "All",
 | |
|     tags: ["Accessibility"],
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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() {
 | |
|             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("startviewport", startRule);
 | |
| 
 | |
|         parser.addListener("property", function(event) {
 | |
|             var name = event.property.text.toLowerCase(),
 | |
|                 value = event.value;
 | |
| 
 | |
|             if (lastRule) {
 | |
|                 lastRule.propCount++;
 | |
|                 if (name === "outline" && (value.toString() === "none" || value.toString() === "0")) {
 | |
|                     lastRule.outline = true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", endRule);
 | |
|         parser.addListener("endfontface", endRule);
 | |
|         parser.addListener("endpage", endRule);
 | |
|         parser.addListener("endpagemargin", endRule);
 | |
|         parser.addListener("endkeyframerule", endRule);
 | |
|         parser.addListener("endviewport", endRule);
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * Rule: Don't use classes or IDs with elements (a.foo or a#foo).
 | |
|  */
 | |
| 
 | |
| 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).",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-overqualified-elements",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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).
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "qualified-headings",
 | |
|     name: "Disallow qualified headings",
 | |
|     desc: "Headings should not be qualified (namespaced).",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-qualified-headings",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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.
 | |
|  */
 | |
| 
 | |
| 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.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-selectors-that-look-like-regular-expressions",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "rules-count",
 | |
|     name: "Rules Count",
 | |
|     desc: "Track how many rules there are.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var count = 0;
 | |
| 
 | |
|         // count each rule
 | |
|         parser.addListener("startrule", function() {
 | |
|             count++;
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endstylesheet", function() {
 | |
|             reporter.stat("rule-count", count);
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * Rule: Warn people with approaching the IE 4095 limit
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "selector-max-approaching",
 | |
|     name: "Warn when approaching the 4095 selector limit for IE",
 | |
|     desc: "Will warn when selector count is >= 3800 selectors.",
 | |
|     browsers: "IE",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this, count = 0;
 | |
| 
 | |
|         parser.addListener("startrule", function(event) {
 | |
|             count += event.selectors.length;
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endstylesheet", function() {
 | |
|             if (count >= 3800) {
 | |
|                 reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * Rule: Warn people past the IE 4095 limit
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "selector-max",
 | |
|     name: "Error when past the 4095 selector limit for IE",
 | |
|     desc: "Will error when selector count is > 4095.",
 | |
|     browsers: "IE",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this, count = 0;
 | |
| 
 | |
|         parser.addListener("startrule", function(event) {
 | |
|             count += event.selectors.length;
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endstylesheet", function() {
 | |
|             if (count > 4095) {
 | |
|                 reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * Rule: Avoid new-line characters in selectors.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "selector-newline",
 | |
|     name: "Disallow new-line characters in selectors",
 | |
|     desc: "New-line characters in selectors are usually a forgotten comma and not a descendant combinator.",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this;
 | |
| 
 | |
|         function startRule(event) {
 | |
|             var i, len, selector, p, n, pLen, part, part2, type, currentLine, nextLine,
 | |
|                 selectors = event.selectors;
 | |
| 
 | |
|             for (i = 0, len = selectors.length; i < len; i++) {
 | |
|                 selector = selectors[i];
 | |
|                 for (p = 0, pLen = selector.parts.length; p < pLen; p++) {
 | |
|                     for (n = p + 1; n < pLen; n++) {
 | |
|                         part = selector.parts[p];
 | |
|                         part2 = selector.parts[n];
 | |
|                         type = part.type;
 | |
|                         currentLine = part.line;
 | |
|                         nextLine = part2.line;
 | |
| 
 | |
|                         if (type === "descendant" && nextLine > currentLine) {
 | |
|                             reporter.report("newline character found in selector (forgot a comma?)", currentLine, selectors[i].parts[0].col, rule);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         parser.addListener("startrule", startRule);
 | |
| 
 | |
|     }
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * Rule: Use shorthand properties where possible.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "shorthand",
 | |
|     name: "Require shorthand properties",
 | |
|     desc: "Use shorthand properties where possible.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Require-shorthand-properties",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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() {
 | |
|             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();
 | |
| 
 | |
|             if (propertiesToCheck[name]) {
 | |
|                 properties[name] = 1;
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", endRule);
 | |
|         parser.addListener("endfontface", endRule);
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * Rule: Don't use properties with a star prefix.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| 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)",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-star-hack",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "text-indent",
 | |
|     name: "Disallow negative text-indent",
 | |
|     desc: "Checks for text indent less than -99px",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-negative-text-indent",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this,
 | |
|             textIndent,
 | |
|             direction;
 | |
| 
 | |
| 
 | |
|         function startRule() {
 | |
|             textIndent = false;
 | |
|             direction = "inherit";
 | |
|         }
 | |
| 
 | |
|         // event handler for end of rules
 | |
|         function endRule() {
 | |
|             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.toString() === "ltr") {
 | |
|                 direction = "ltr";
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         parser.addListener("endrule", endRule);
 | |
|         parser.addListener("endfontface", endRule);
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * Rule: Don't use properties with a underscore prefix.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "underscore-property-hack",
 | |
|     name: "Disallow properties with an underscore prefix",
 | |
|     desc: "Checks for the underscore property hack (targets IE6)",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-underscore-hack",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "unique-headings",
 | |
|     name: "Headings should only be defined once",
 | |
|     desc: "Headings should be defined only once.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Headings-should-only-be-defined-once",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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() {
 | |
|             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.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "universal-selector",
 | |
|     name: "Disallow universal selector",
 | |
|     desc: "The universal selector (*) is known to be slow.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-universal-selector",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         var rule = this;
 | |
| 
 | |
|         parser.addListener("startrule", function(event) {
 | |
|             var selectors = event.selectors,
 | |
|                 selector,
 | |
|                 part,
 | |
|                 i;
 | |
| 
 | |
|             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.
 | |
|  */
 | |
| 
 | |
| CSSLint.addRule({
 | |
| 
 | |
|     // rule information
 | |
|     id: "unqualified-attributes",
 | |
|     name: "Disallow unqualified attribute selectors",
 | |
|     desc: "Unqualified attribute selectors are known to be slow.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-unqualified-attribute-selectors",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
| 
 | |
|         var rule = this;
 | |
| 
 | |
|         parser.addListener("startrule", function(event) {
 | |
| 
 | |
|             var selectors = event.selectors,
 | |
|                 selectorContainsClassOrId = false,
 | |
|                 selector,
 | |
|                 part,
 | |
|                 modifier,
 | |
|                 i, 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 === "class" || modifier.type === "id") {
 | |
|                             selectorContainsClassOrId = true;
 | |
|                             break;
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     if (!selectorContainsClassOrId) {
 | |
|                         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.
 | |
|  */
 | |
| 
 | |
| 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.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Require-standard-property-with-vendor-prefix",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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"
 | |
|             };
 | |
| 
 | |
|         // event handler for beginning of rules
 | |
|         function startRule() {
 | |
|             properties = {};
 | |
|             num = 1;
 | |
|         }
 | |
| 
 | |
|         // event handler for end of rules
 | |
|         function endRule() {
 | |
|             var prop,
 | |
|                 i,
 | |
|                 len,
 | |
|                 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("startviewport", 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);
 | |
|         parser.addListener("endviewport", endRule);
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| /*
 | |
|  * Rule: You don't need to specify units when a value is 0.
 | |
|  */
 | |
| 
 | |
| 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.",
 | |
|     url: "https://github.com/CSSLint/csslint/wiki/Disallow-units-for-zero-values",
 | |
|     browsers: "All",
 | |
| 
 | |
|     // initialization
 | |
|     init: function(parser, reporter) {
 | |
|         "use strict";
 | |
|         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++;
 | |
|             }
 | |
| 
 | |
|         });
 | |
| 
 | |
|     }
 | |
| 
 | |
| });
 | |
| 
 | |
| (function() {
 | |
|     "use strict";
 | |
| 
 | |
|     /**
 | |
|      * 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) {
 | |
|                     // 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("");
 | |
|         }
 | |
|     });
 | |
| 
 | |
| }());
 | |
| 
 | |
| 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() {
 | |
|         "use strict";
 | |
|         return "";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Return content to be printed after all file results.
 | |
|      * @return {String} to append after all results
 | |
|      */
 | |
|     endFormat: function() {
 | |
|         "use strict";
 | |
|         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) {
 | |
|         "use strict";
 | |
|         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) {
 | |
|             if (message.rollup) {
 | |
|                 output += filename + ": " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n";
 | |
|             } else {
 | |
|                 output += filename + ": line " + message.line +
 | |
|                     ", col " + message.col + ", " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n";
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         return output;
 | |
|     }
 | |
| });
 | |
| 
 | |
| 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() {
 | |
|         "use strict";
 | |
|         return "<?xml version=\"1.0\" encoding=\"utf-8\"?><csslint>";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Return closing root XML tag.
 | |
|      * @return {String} to append after all results
 | |
|      */
 | |
|     endFormat: function() {
 | |
|         "use strict";
 | |
|         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*/) {
 | |
|         "use strict";
 | |
|         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) {
 | |
|                 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("");
 | |
|     }
 | |
| });
 | |
| 
 | |
| /* globals JSON: true */
 | |
| 
 | |
| CSSLint.addFormatter({
 | |
|     // format information
 | |
|     id: "json",
 | |
|     name: "JSON",
 | |
| 
 | |
|     /**
 | |
|      * Return content to be printed before all file results.
 | |
|      * @return {String} to prepend before all results
 | |
|      */
 | |
|     startFormat: function() {
 | |
|         "use strict";
 | |
|         this.json = [];
 | |
|         return "";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Return content to be printed after all file results.
 | |
|      * @return {String} to append after all results
 | |
|      */
 | |
|     endFormat: function() {
 | |
|         "use strict";
 | |
|         var ret = "";
 | |
|         if (this.json.length > 0) {
 | |
|             if (this.json.length === 1) {
 | |
|                 ret = JSON.stringify(this.json[0]);
 | |
|             } else {
 | |
|                 ret = JSON.stringify(this.json);
 | |
|             }
 | |
|         }
 | |
|         return ret;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * 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 (Unused)
 | |
|      * @return {String} output for results
 | |
|      */
 | |
|     formatResults: function(results, filename, options) {
 | |
|         "use strict";
 | |
|         if (results.messages.length > 0 || !options.quiet) {
 | |
|             this.json.push({
 | |
|                 filename: filename,
 | |
|                 messages: results.messages,
 | |
|                 stats: results.stats
 | |
|             });
 | |
|         }
 | |
|         return "";
 | |
|     }
 | |
| });
 | |
| 
 | |
| CSSLint.addFormatter({
 | |
|     // format information
 | |
|     id: "junit-xml",
 | |
|     name: "JUNIT XML format",
 | |
| 
 | |
|     /**
 | |
|      * Return opening root XML tag.
 | |
|      * @return {String} to prepend before all results
 | |
|      */
 | |
|     startFormat: function() {
 | |
|         "use strict";
 | |
|         return "<?xml version=\"1.0\" encoding=\"utf-8\"?><testsuites>";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Return closing root XML tag.
 | |
|      * @return {String} to append after all results
 | |
|      */
 | |
|     endFormat: function() {
 | |
|         "use strict";
 | |
|         return "</testsuites>";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * 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*/) {
 | |
|         "use strict";
 | |
| 
 | |
|         var messages = results.messages,
 | |
|             output = [],
 | |
|             tests = {
 | |
|                 "error": 0,
 | |
|                 "failure": 0
 | |
|             };
 | |
| 
 | |
|         /**
 | |
|          * Generate a source string for a rule.
 | |
|          * JUNIT 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, "");
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * 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 >
 | |
|          *
 | |
|          * @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, ">");
 | |
| 
 | |
|         };
 | |
| 
 | |
|         if (messages.length > 0) {
 | |
| 
 | |
|             messages.forEach(function (message) {
 | |
| 
 | |
|                 // since junit has no warning class
 | |
|                 // all issues as errors
 | |
|                 var type = message.type === "warning" ? "error" : message.type;
 | |
| 
 | |
|                 // ignore rollups for now
 | |
|                 if (!message.rollup) {
 | |
| 
 | |
|                     // build the test case separately, once joined
 | |
|                     // we'll add it to a custom array filtered by type
 | |
|                     output.push("<testcase time=\"0\" name=\"" + generateSource(message.rule) + "\">");
 | |
|                     output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\"><![CDATA[" + message.line + ":" + message.col + ":" + escapeSpecialCharacters(message.evidence) + "]]></" + type + ">");
 | |
|                     output.push("</testcase>");
 | |
| 
 | |
|                     tests[type] += 1;
 | |
| 
 | |
|                 }
 | |
| 
 | |
|             });
 | |
| 
 | |
|             output.unshift("<testsuite time=\"0\" tests=\"" + messages.length + "\" skipped=\"0\" errors=\"" + tests.error + "\" failures=\"" + tests.failure + "\" package=\"net.csslint\" name=\"" + filename + "\">");
 | |
|             output.push("</testsuite>");
 | |
| 
 | |
|         }
 | |
| 
 | |
|         return output.join("");
 | |
| 
 | |
|     }
 | |
| });
 | |
| 
 | |
| 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() {
 | |
|         "use strict";
 | |
|         return "<?xml version=\"1.0\" encoding=\"utf-8\"?><lint>";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Return closing root XML tag.
 | |
|      * @return {String} to append after all results
 | |
|      */
 | |
|     endFormat: function() {
 | |
|         "use strict";
 | |
|         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*/) {
 | |
|         "use strict";
 | |
|         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) {
 | |
|                 if (message.rollup) {
 | |
|                     output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
 | |
|                 } else {
 | |
|                     var rule = "";
 | |
|                     if (message.rule && message.rule.id) {
 | |
|                         rule = "rule=\"" + escapeSpecialCharacters(message.rule.id) + "\" ";
 | |
|                     }
 | |
|                     output.push("<issue " + rule + "line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" +
 | |
|                         " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
 | |
|                 }
 | |
|             });
 | |
|             output.push("</file>");
 | |
|         }
 | |
| 
 | |
|         return output.join("");
 | |
|     }
 | |
| });
 | |
| 
 | |
| 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() {
 | |
|         "use strict";
 | |
|         return "";
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Return content to be printed after all file results.
 | |
|      * @return {String} to append after all results
 | |
|      */
 | |
|     endFormat: function() {
 | |
|         "use strict";
 | |
|         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) {
 | |
|         "use strict";
 | |
|         var messages = results.messages,
 | |
|             output = "";
 | |
|         options = options || {};
 | |
| 
 | |
|         if (messages.length === 0) {
 | |
|             return options.quiet ? "" : "\n\ncsslint: No errors in " + filename + ".";
 | |
|         }
 | |
| 
 | |
|         output = "\n\ncsslint: There ";
 | |
|         if (messages.length === 1) {
 | |
|             output += "is 1 problem";
 | |
|         } else {
 | |
|             output += "are " + messages.length + " problems";
 | |
|         }
 | |
|         output += " 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;
 | |
|     }
 | |
| });
 | |
| 
 | |
| return CSSLint;
 | |
| })(); |