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