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