PageRenderTime 58ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/ace/mode/css/csslint.js

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