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