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