diff options
Diffstat (limited to 'share/frontend/imthresh/deps/opt/ace/worker-css.js')
| -rw-r--r-- | share/frontend/imthresh/deps/opt/ace/worker-css.js | 10898 |
1 files changed, 10898 insertions, 0 deletions
diff --git a/share/frontend/imthresh/deps/opt/ace/worker-css.js b/share/frontend/imthresh/deps/opt/ace/worker-css.js new file mode 100644 index 0000000..4ebca4b --- /dev/null +++ b/share/frontend/imthresh/deps/opt/ace/worker-css.js @@ -0,0 +1,10898 @@ +"no use strict"; + +var console = { + log: function(msg) { + postMessage({type: "log", data: msg}); + } +}; +var window = { + console: console +}; + +var normalizeModule = function(parentId, moduleName) { + // normalize plugin requires + if (moduleName.indexOf("!") !== -1) { + var chunks = moduleName.split("!"); + return normalizeModule(parentId, chunks[0]) + "!" + normalizeModule(parentId, chunks[1]); + } + // normalize relative requires + if (moduleName.charAt(0) == ".") { + var base = parentId.split("/").slice(0, -1).join("/"); + var moduleName = base + "/" + moduleName; + + while(moduleName.indexOf(".") !== -1 && previous != moduleName) { + var previous = moduleName; + var moduleName = moduleName.replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, ""); + } + } + + return moduleName; +}; + +var require = function(parentId, id) { + var id = normalizeModule(parentId, id); + + var module = require.modules[id]; + if (module) { + if (!module.initialized) { + module.exports = module.factory().exports; + module.initialized = true; + } + return module.exports; + } + + var chunks = id.split("/"); + chunks[0] = require.tlns[chunks[0]] || chunks[0]; + var path = chunks.join("/") + ".js"; + + require.id = id; + importScripts(path); + return require(parentId, id); +}; + +require.modules = {}; +require.tlns = {}; + +var define = function(id, deps, factory) { + if (arguments.length == 2) { + factory = deps; + } else if (arguments.length == 1) { + factory = id; + id = require.id; + } + + if (id.indexOf("text!") === 0) + return; + + var req = function(deps, factory) { + return require(id, deps, factory); + }; + + require.modules[id] = { + factory: function() { + var module = { + exports: {} + }; + var returnExports = factory(req, module.exports, module); + if (returnExports) + module.exports = returnExports; + return module; + } + }; +}; + +function initBaseUrls(topLevelNamespaces) { + require.tlns = topLevelNamespaces; +} + +function initSender() { + + var EventEmitter = require(null, "ace/lib/event_emitter").EventEmitter; + var oop = require(null, "ace/lib/oop"); + + var Sender = function() {}; + + (function() { + + oop.implement(this, EventEmitter); + + this.callback = function(data, callbackId) { + postMessage({ + type: "call", + id: callbackId, + data: data + }); + }; + + this.emit = function(name, data) { + postMessage({ + type: "event", + name: name, + data: data + }); + }; + + }).call(Sender.prototype); + + return new Sender(); +} + +var main; +var sender; + +onmessage = function(e) { + var msg = e.data; + if (msg.command) { + main[msg.command].apply(main, msg.args); + } + else if (msg.init) { + initBaseUrls(msg.tlns); + require(null, "ace/lib/fixoldbrowsers"); + sender = initSender(); + var clazz = require(null, msg.module)[msg.classname]; + main = new clazz(sender); + } + else if (msg.event && sender) { + sender._emit(msg.event, msg.data); + } +}; +// vim:set ts=4 sts=4 sw=4 st: +// -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License +// -- tlrobinson Tom Robinson Copyright (C) 2009-2010 MIT License (Narwhal Project) +// -- dantman Daniel Friesen Copyright(C) 2010 XXX No License Specified +// -- fschaefer Florian Schäfer Copyright (C) 2010 MIT License +// -- Irakli Gozalishvili Copyright (C) 2010 MIT License + +/*! + Copyright (c) 2009, 280 North Inc. http://280north.com/ + MIT License. http://github.com/280north/narwhal/blob/master/README.md +*/ + +define('ace/lib/fixoldbrowsers', ['require', 'exports', 'module' , 'ace/lib/regexp', 'ace/lib/es5-shim'], function(require, exports, module) { + + +require("./regexp"); +require("./es5-shim"); + +}); + +define('ace/lib/regexp', ['require', 'exports', 'module' ], function(require, exports, module) { + + + //--------------------------------- + // Private variables + //--------------------------------- + + var real = { + exec: RegExp.prototype.exec, + test: RegExp.prototype.test, + match: String.prototype.match, + replace: String.prototype.replace, + split: String.prototype.split + }, + compliantExecNpcg = real.exec.call(/()??/, "")[1] === undefined, // check `exec` handling of nonparticipating capturing groups + compliantLastIndexIncrement = function () { + var x = /^/g; + real.test.call(x, ""); + return !x.lastIndex; + }(); + + if (compliantLastIndexIncrement && compliantExecNpcg) + return; + + //--------------------------------- + // Overriden native methods + //--------------------------------- + + // Adds named capture support (with backreferences returned as `result.name`), and fixes two + // cross-browser issues per ES3: + // - Captured values for nonparticipating capturing groups should be returned as `undefined`, + // rather than the empty string. + // - `lastIndex` should not be incremented after zero-length matches. + RegExp.prototype.exec = function (str) { + var match = real.exec.apply(this, arguments), + name, r2; + if ( typeof(str) == 'string' && match) { + // Fix browsers whose `exec` methods don't consistently return `undefined` for + // nonparticipating capturing groups + if (!compliantExecNpcg && match.length > 1 && indexOf(match, "") > -1) { + r2 = RegExp(this.source, real.replace.call(getNativeFlags(this), "g", "")); + // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed + // matching due to characters outside the match + real.replace.call(str.slice(match.index), r2, function () { + for (var i = 1; i < arguments.length - 2; i++) { + if (arguments[i] === undefined) + match[i] = undefined; + } + }); + } + // Attach named capture properties + if (this._xregexp && this._xregexp.captureNames) { + for (var i = 1; i < match.length; i++) { + name = this._xregexp.captureNames[i - 1]; + if (name) + match[name] = match[i]; + } + } + // Fix browsers that increment `lastIndex` after zero-length matches + if (!compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index)) + this.lastIndex--; + } + return match; + }; + + // Don't override `test` if it won't change anything + if (!compliantLastIndexIncrement) { + // Fix browser bug in native method + RegExp.prototype.test = function (str) { + // Use the native `exec` to skip some processing overhead, even though the overriden + // `exec` would take care of the `lastIndex` fix + var match = real.exec.call(this, str); + // Fix browsers that increment `lastIndex` after zero-length matches + if (match && this.global && !match[0].length && (this.lastIndex > match.index)) + this.lastIndex--; + return !!match; + }; + } + + //--------------------------------- + // Private helper functions + //--------------------------------- + + function getNativeFlags (regex) { + return (regex.global ? "g" : "") + + (regex.ignoreCase ? "i" : "") + + (regex.multiline ? "m" : "") + + (regex.extended ? "x" : "") + // Proposed for ES4; included in AS3 + (regex.sticky ? "y" : ""); + }; + + function indexOf (array, item, from) { + if (Array.prototype.indexOf) // Use the native array method if available + return array.indexOf(item, from); + for (var i = from || 0; i < array.length; i++) { + if (array[i] === item) + return i; + } + return -1; + }; + +}); +// vim: ts=4 sts=4 sw=4 expandtab +// -- kriskowal Kris Kowal Copyright (C) 2009-2011 MIT License +// -- tlrobinson Tom Robinson Copyright (C) 2009-2010 MIT License (Narwhal Project) +// -- dantman Daniel Friesen Copyright (C) 2010 XXX TODO License or CLA +// -- fschaefer Florian Schäfer Copyright (C) 2010 MIT License +// -- Gozala Irakli Gozalishvili Copyright (C) 2010 MIT License +// -- kitcambridge Kit Cambridge Copyright (C) 2011 MIT License +// -- kossnocorp Sasha Koss XXX TODO License or CLA +// -- bryanforbes Bryan Forbes XXX TODO License or CLA +// -- killdream Quildreen Motta Copyright (C) 2011 MIT Licence +// -- michaelficarra Michael Ficarra Copyright (C) 2011 3-clause BSD License +// -- sharkbrainguy Gerard Paapu Copyright (C) 2011 MIT License +// -- bbqsrc Brendan Molloy (C) 2011 Creative Commons Zero (public domain) +// -- iwyg XXX TODO License or CLA +// -- DomenicDenicola Domenic Denicola Copyright (C) 2011 MIT License +// -- xavierm02 Montillet Xavier XXX TODO License or CLA +// -- Raynos Raynos XXX TODO License or CLA +// -- samsonjs Sami Samhuri Copyright (C) 2010 MIT License +// -- rwldrn Rick Waldron Copyright (C) 2011 MIT License +// -- lexer Alexey Zakharov XXX TODO License or CLA + +/*! + Copyright (c) 2009, 280 North Inc. http://280north.com/ + MIT License. http://github.com/280north/narwhal/blob/master/README.md +*/ + +define('ace/lib/es5-shim', ['require', 'exports', 'module' ], function(require, exports, module) { + +/* + * Brings an environment as close to ECMAScript 5 compliance + * as is possible with the facilities of erstwhile engines. + * + * Annotated ES5: http://es5.github.com/ (specific links below) + * ES5 Spec: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf + * + * @module + */ + +/*whatsupdoc*/ + +// +// Function +// ======== +// + +// ES-5 15.3.4.5 +// http://es5.github.com/#x15.3.4.5 + +if (!Function.prototype.bind) { + Function.prototype.bind = function bind(that) { // .length is 1 + // 1. Let Target be the this value. + var target = this; + // 2. If IsCallable(Target) is false, throw a TypeError exception. + if (typeof target != "function") + throw new TypeError(); // TODO message + // 3. Let A be a new (possibly empty) internal list of all of the + // argument values provided after thisArg (arg1, arg2 etc), in order. + // XXX slicedArgs will stand in for "A" if used + var args = slice.call(arguments, 1); // for normal call + // 4. Let F be a new native ECMAScript object. + // 11. Set the [[Prototype]] internal property of F to the standard + // built-in Function prototype object as specified in 15.3.3.1. + // 12. Set the [[Call]] internal property of F as described in + // 15.3.4.5.1. + // 13. Set the [[Construct]] internal property of F as described in + // 15.3.4.5.2. + // 14. Set the [[HasInstance]] internal property of F as described in + // 15.3.4.5.3. + var bound = function () { + + if (this instanceof bound) { + // 15.3.4.5.2 [[Construct]] + // When the [[Construct]] internal method of a function object, + // F that was created using the bind function is called with a + // list of arguments ExtraArgs, the following steps are taken: + // 1. Let target be the value of F's [[TargetFunction]] + // internal property. + // 2. If target has no [[Construct]] internal method, a + // TypeError exception is thrown. + // 3. Let boundArgs be the value of F's [[BoundArgs]] internal + // property. + // 4. Let args be a new list containing the same values as the + // list boundArgs in the same order followed by the same + // values as the list ExtraArgs in the same order. + // 5. Return the result of calling the [[Construct]] internal + // method of target providing args as the arguments. + + var F = function(){}; + F.prototype = target.prototype; + var self = new F; + + var result = target.apply( + self, + args.concat(slice.call(arguments)) + ); + if (result !== null && Object(result) === result) + return result; + return self; + + } else { + // 15.3.4.5.1 [[Call]] + // When the [[Call]] internal method of a function object, F, + // which was created using the bind function is called with a + // this value and a list of arguments ExtraArgs, the following + // steps are taken: + // 1. Let boundArgs be the value of F's [[BoundArgs]] internal + // property. + // 2. Let boundThis be the value of F's [[BoundThis]] internal + // property. + // 3. Let target be the value of F's [[TargetFunction]] internal + // property. + // 4. Let args be a new list containing the same values as the + // list boundArgs in the same order followed by the same + // values as the list ExtraArgs in the same order. + // 5. Return the result of calling the [[Call]] internal method + // of target providing boundThis as the this value and + // providing args as the arguments. + + // equiv: target.call(this, ...boundArgs, ...args) + return target.apply( + that, + args.concat(slice.call(arguments)) + ); + + } + + }; + // XXX bound.length is never writable, so don't even try + // + // 15. If the [[Class]] internal property of Target is "Function", then + // a. Let L be the length property of Target minus the length of A. + // b. Set the length own property of F to either 0 or L, whichever is + // larger. + // 16. Else set the length own property of F to 0. + // 17. Set the attributes of the length own property of F to the values + // specified in 15.3.5.1. + + // TODO + // 18. Set the [[Extensible]] internal property of F to true. + + // TODO + // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3). + // 20. Call the [[DefineOwnProperty]] internal method of F with + // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]: + // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and + // false. + // 21. Call the [[DefineOwnProperty]] internal method of F with + // arguments "arguments", PropertyDescriptor {[[Get]]: thrower, + // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, + // and false. + + // TODO + // NOTE Function objects created using Function.prototype.bind do not + // have a prototype property or the [[Code]], [[FormalParameters]], and + // [[Scope]] internal properties. + // XXX can't delete prototype in pure-js. + + // 22. Return F. + return bound; + }; +} + +// Shortcut to an often accessed properties, in order to avoid multiple +// dereference that costs universally. +// _Please note: Shortcuts are defined after `Function.prototype.bind` as we +// us it in defining shortcuts. +var call = Function.prototype.call; +var prototypeOfArray = Array.prototype; +var prototypeOfObject = Object.prototype; +var slice = prototypeOfArray.slice; +var toString = call.bind(prototypeOfObject.toString); +var owns = call.bind(prototypeOfObject.hasOwnProperty); + +// If JS engine supports accessors creating shortcuts. +var defineGetter; +var defineSetter; +var lookupGetter; +var lookupSetter; +var supportsAccessors; +if ((supportsAccessors = owns(prototypeOfObject, "__defineGetter__"))) { + defineGetter = call.bind(prototypeOfObject.__defineGetter__); + defineSetter = call.bind(prototypeOfObject.__defineSetter__); + lookupGetter = call.bind(prototypeOfObject.__lookupGetter__); + lookupSetter = call.bind(prototypeOfObject.__lookupSetter__); +} + +// +// Array +// ===== +// + +// ES5 15.4.3.2 +// http://es5.github.com/#x15.4.3.2 +// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray +if (!Array.isArray) { + Array.isArray = function isArray(obj) { + return toString(obj) == "[object Array]"; + }; +} + +// The IsCallable() check in the Array functions +// has been replaced with a strict check on the +// internal class of the object to trap cases where +// the provided function was actually a regular +// expression literal, which in V8 and +// JavaScriptCore is a typeof "function". Only in +// V8 are regular expression literals permitted as +// reduce parameters, so it is desirable in the +// general case for the shim to match the more +// strict and common behavior of rejecting regular +// expressions. + +// ES5 15.4.4.18 +// http://es5.github.com/#x15.4.4.18 +// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/forEach +if (!Array.prototype.forEach) { + Array.prototype.forEach = function forEach(fun /*, thisp*/) { + var self = toObject(this), + thisp = arguments[1], + i = 0, + length = self.length >>> 0; + + // If no callback function or if callback is not a callable function + if (toString(fun) != "[object Function]") { + throw new TypeError(); // TODO message + } + + while (i < length) { + if (i in self) { + // Invoke the callback function with call, passing arguments: + // context, property value, property key, thisArg object context + fun.call(thisp, self[i], i, self); + } + i++; + } + }; +} + +// ES5 15.4.4.19 +// http://es5.github.com/#x15.4.4.19 +// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map +if (!Array.prototype.map) { + Array.prototype.map = function map(fun /*, thisp*/) { + var self = toObject(this), + length = self.length >>> 0, + result = Array(length), + thisp = arguments[1]; + + // If no callback function or if callback is not a callable function + if (toString(fun) != "[object Function]") { + throw new TypeError(); // TODO message + } + + for (var i = 0; i < length; i++) { + if (i in self) + result[i] = fun.call(thisp, self[i], i, self); + } + return result; + }; +} + +// ES5 15.4.4.20 +// http://es5.github.com/#x15.4.4.20 +// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter +if (!Array.prototype.filter) { + Array.prototype.filter = function filter(fun /*, thisp */) { + var self = toObject(this), + length = self.length >>> 0, + result = [], + thisp = arguments[1]; + + // If no callback function or if callback is not a callable function + if (toString(fun) != "[object Function]") { + throw new TypeError(); // TODO message + } + + for (var i = 0; i < length; i++) { + if (i in self && fun.call(thisp, self[i], i, self)) + result.push(self[i]); + } + return result; + }; +} + +// ES5 15.4.4.16 +// http://es5.github.com/#x15.4.4.16 +// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every +if (!Array.prototype.every) { + Array.prototype.every = function every(fun /*, thisp */) { + var self = toObject(this), + length = self.length >>> 0, + thisp = arguments[1]; + + // If no callback function or if callback is not a callable function + if (toString(fun) != "[object Function]") { + throw new TypeError(); // TODO message + } + + for (var i = 0; i < length; i++) { + if (i in self && !fun.call(thisp, self[i], i, self)) + return false; + } + return true; + }; +} + +// ES5 15.4.4.17 +// http://es5.github.com/#x15.4.4.17 +// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some +if (!Array.prototype.some) { + Array.prototype.some = function some(fun /*, thisp */) { + var self = toObject(this), + length = self.length >>> 0, + thisp = arguments[1]; + + // If no callback function or if callback is not a callable function + if (toString(fun) != "[object Function]") { + throw new TypeError(); // TODO message + } + + for (var i = 0; i < length; i++) { + if (i in self && fun.call(thisp, self[i], i, self)) + return true; + } + return false; + }; +} + +// ES5 15.4.4.21 +// http://es5.github.com/#x15.4.4.21 +// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce +if (!Array.prototype.reduce) { + Array.prototype.reduce = function reduce(fun /*, initial*/) { + var self = toObject(this), + length = self.length >>> 0; + + // If no callback function or if callback is not a callable function + if (toString(fun) != "[object Function]") { + throw new TypeError(); // TODO message + } + + // no value to return if no initial value and an empty array + if (!length && arguments.length == 1) + throw new TypeError(); // TODO message + + var i = 0; + var result; + if (arguments.length >= 2) { + result = arguments[1]; + } else { + do { + if (i in self) { + result = self[i++]; + break; + } + + // if array contains no values, no initial value to return + if (++i >= length) + throw new TypeError(); // TODO message + } while (true); + } + + for (; i < length; i++) { + if (i in self) + result = fun.call(void 0, result, self[i], i, self); + } + + return result; + }; +} + +// ES5 15.4.4.22 +// http://es5.github.com/#x15.4.4.22 +// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight +if (!Array.prototype.reduceRight) { + Array.prototype.reduceRight = function reduceRight(fun /*, initial*/) { + var self = toObject(this), + length = self.length >>> 0; + + // If no callback function or if callback is not a callable function + if (toString(fun) != "[object Function]") { + throw new TypeError(); // TODO message + } + + // no value to return if no initial value, empty array + if (!length && arguments.length == 1) + throw new TypeError(); // TODO message + + var result, i = length - 1; + if (arguments.length >= 2) { + result = arguments[1]; + } else { + do { + if (i in self) { + result = self[i--]; + break; + } + + // if array contains no values, no initial value to return + if (--i < 0) + throw new TypeError(); // TODO message + } while (true); + } + + do { + if (i in this) + result = fun.call(void 0, result, self[i], i, self); + } while (i--); + + return result; + }; +} + +// ES5 15.4.4.14 +// http://es5.github.com/#x15.4.4.14 +// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function indexOf(sought /*, fromIndex */ ) { + var self = toObject(this), + length = self.length >>> 0; + + if (!length) + return -1; + + var i = 0; + if (arguments.length > 1) + i = toInteger(arguments[1]); + + // handle negative indices + i = i >= 0 ? i : Math.max(0, length + i); + for (; i < length; i++) { + if (i in self && self[i] === sought) { + return i; + } + } + return -1; + }; +} + +// ES5 15.4.4.15 +// http://es5.github.com/#x15.4.4.15 +// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf +if (!Array.prototype.lastIndexOf) { + Array.prototype.lastIndexOf = function lastIndexOf(sought /*, fromIndex */) { + var self = toObject(this), + length = self.length >>> 0; + + if (!length) + return -1; + var i = length - 1; + if (arguments.length > 1) + i = Math.min(i, toInteger(arguments[1])); + // handle negative indices + i = i >= 0 ? i : length - Math.abs(i); + for (; i >= 0; i--) { + if (i in self && sought === self[i]) + return i; + } + return -1; + }; +} + +// +// Object +// ====== +// + +// ES5 15.2.3.2 +// http://es5.github.com/#x15.2.3.2 +if (!Object.getPrototypeOf) { + // https://github.com/kriskowal/es5-shim/issues#issue/2 + // http://ejohn.org/blog/objectgetprototypeof/ + // recommended by fschaefer on github + Object.getPrototypeOf = function getPrototypeOf(object) { + return object.__proto__ || ( + object.constructor ? + object.constructor.prototype : + prototypeOfObject + ); + }; +} + +// ES5 15.2.3.3 +// http://es5.github.com/#x15.2.3.3 +if (!Object.getOwnPropertyDescriptor) { + var ERR_NON_OBJECT = "Object.getOwnPropertyDescriptor called on a " + + "non-object: "; + Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(object, property) { + if ((typeof object != "object" && typeof object != "function") || object === null) + throw new TypeError(ERR_NON_OBJECT + object); + // If object does not owns property return undefined immediately. + if (!owns(object, property)) + return; + + var descriptor, getter, setter; + + // If object has a property then it's for sure both `enumerable` and + // `configurable`. + descriptor = { enumerable: true, configurable: true }; + + // If JS engine supports accessor properties then property may be a + // getter or setter. + if (supportsAccessors) { + // Unfortunately `__lookupGetter__` will return a getter even + // if object has own non getter property along with a same named + // inherited getter. To avoid misbehavior we temporary remove + // `__proto__` so that `__lookupGetter__` will return getter only + // if it's owned by an object. + var prototype = object.__proto__; + object.__proto__ = prototypeOfObject; + + var getter = lookupGetter(object, property); + var setter = lookupSetter(object, property); + + // Once we have getter and setter we can put values back. + object.__proto__ = prototype; + + if (getter || setter) { + if (getter) descriptor.get = getter; + if (setter) descriptor.set = setter; + + // If it was accessor property we're done and return here + // in order to avoid adding `value` to the descriptor. + return descriptor; + } + } + + // If we got this far we know that object has an own property that is + // not an accessor so we set it as a value and return descriptor. + descriptor.value = object[property]; + return descriptor; + }; +} + +// ES5 15.2.3.4 +// http://es5.github.com/#x15.2.3.4 +if (!Object.getOwnPropertyNames) { + Object.getOwnPropertyNames = function getOwnPropertyNames(object) { + return Object.keys(object); + }; +} + +// ES5 15.2.3.5 +// http://es5.github.com/#x15.2.3.5 +if (!Object.create) { + Object.create = function create(prototype, properties) { + var object; + if (prototype === null) { + object = { "__proto__": null }; + } else { + if (typeof prototype != "object") + throw new TypeError("typeof prototype["+(typeof prototype)+"] != 'object'"); + var Type = function () {}; + Type.prototype = prototype; + object = new Type(); + // IE has no built-in implementation of `Object.getPrototypeOf` + // neither `__proto__`, but this manually setting `__proto__` will + // guarantee that `Object.getPrototypeOf` will work as expected with + // objects created using `Object.create` + object.__proto__ = prototype; + } + if (properties !== void 0) + Object.defineProperties(object, properties); + return object; + }; +} + +// ES5 15.2.3.6 +// http://es5.github.com/#x15.2.3.6 + +// Patch for WebKit and IE8 standard mode +// Designed by hax <hax.github.com> +// related issue: https://github.com/kriskowal/es5-shim/issues#issue/5 +// IE8 Reference: +// http://msdn.microsoft.com/en-us/library/dd282900.aspx +// http://msdn.microsoft.com/en-us/library/dd229916.aspx +// WebKit Bugs: +// https://bugs.webkit.org/show_bug.cgi?id=36423 + +function doesDefinePropertyWork(object) { + try { + Object.defineProperty(object, "sentinel", {}); + return "sentinel" in object; + } catch (exception) { + // returns falsy + } +} + +// check whether defineProperty works if it's given. Otherwise, +// shim partially. +if (Object.defineProperty) { + var definePropertyWorksOnObject = doesDefinePropertyWork({}); + var definePropertyWorksOnDom = typeof document == "undefined" || + doesDefinePropertyWork(document.createElement("div")); + if (!definePropertyWorksOnObject || !definePropertyWorksOnDom) { + var definePropertyFallback = Object.defineProperty; + } +} + +if (!Object.defineProperty || definePropertyFallback) { + var ERR_NON_OBJECT_DESCRIPTOR = "Property description must be an object: "; + var ERR_NON_OBJECT_TARGET = "Object.defineProperty called on non-object: " + var ERR_ACCESSORS_NOT_SUPPORTED = "getters & setters can not be defined " + + "on this javascript engine"; + + Object.defineProperty = function defineProperty(object, property, descriptor) { + if ((typeof object != "object" && typeof object != "function") || object === null) + throw new TypeError(ERR_NON_OBJECT_TARGET + object); + if ((typeof descriptor != "object" && typeof descriptor != "function") || descriptor === null) + throw new TypeError(ERR_NON_OBJECT_DESCRIPTOR + descriptor); + + // make a valiant attempt to use the real defineProperty + // for I8's DOM elements. + if (definePropertyFallback) { + try { + return definePropertyFallback.call(Object, object, property, descriptor); + } catch (exception) { + // try the shim if the real one doesn't work + } + } + + // If it's a data property. + if (owns(descriptor, "value")) { + // fail silently if "writable", "enumerable", or "configurable" + // are requested but not supported + /* + // alternate approach: + if ( // can't implement these features; allow false but not true + !(owns(descriptor, "writable") ? descriptor.writable : true) || + !(owns(descriptor, "enumerable") ? descriptor.enumerable : true) || + !(owns(descriptor, "configurable") ? descriptor.configurable : true) + ) + throw new RangeError( + "This implementation of Object.defineProperty does not " + + "support configurable, enumerable, or writable." + ); + */ + + if (supportsAccessors && (lookupGetter(object, property) || + lookupSetter(object, property))) + { + // As accessors are supported only on engines implementing + // `__proto__` we can safely override `__proto__` while defining + // a property to make sure that we don't hit an inherited + // accessor. + var prototype = object.__proto__; + object.__proto__ = prototypeOfObject; + // Deleting a property anyway since getter / setter may be + // defined on object itself. + delete object[property]; + object[property] = descriptor.value; + // Setting original `__proto__` back now. + object.__proto__ = prototype; + } else { + object[property] = descriptor.value; + } + } else { + if (!supportsAccessors) + throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED); + // If we got that far then getters and setters can be defined !! + if (owns(descriptor, "get")) + defineGetter(object, property, descriptor.get); + if (owns(descriptor, "set")) + defineSetter(object, property, descriptor.set); + } + + return object; + }; +} + +// ES5 15.2.3.7 +// http://es5.github.com/#x15.2.3.7 +if (!Object.defineProperties) { + Object.defineProperties = function defineProperties(object, properties) { + for (var property in properties) { + if (owns(properties, property)) + Object.defineProperty(object, property, properties[property]); + } + return object; + }; +} + +// ES5 15.2.3.8 +// http://es5.github.com/#x15.2.3.8 +if (!Object.seal) { + Object.seal = function seal(object) { + // this is misleading and breaks feature-detection, but + // allows "securable" code to "gracefully" degrade to working + // but insecure code. + return object; + }; +} + +// ES5 15.2.3.9 +// http://es5.github.com/#x15.2.3.9 +if (!Object.freeze) { + Object.freeze = function freeze(object) { + // this is misleading and breaks feature-detection, but + // allows "securable" code to "gracefully" degrade to working + // but insecure code. + return object; + }; +} + +// detect a Rhino bug and patch it +try { + Object.freeze(function () {}); +} catch (exception) { + Object.freeze = (function freeze(freezeObject) { + return function freeze(object) { + if (typeof object == "function") { + return object; + } else { + return freezeObject(object); + } + }; + })(Object.freeze); +} + +// ES5 15.2.3.10 +// http://es5.github.com/#x15.2.3.10 +if (!Object.preventExtensions) { + Object.preventExtensions = function preventExtensions(object) { + // this is misleading and breaks feature-detection, but + // allows "securable" code to "gracefully" degrade to working + // but insecure code. + return object; + }; +} + +// ES5 15.2.3.11 +// http://es5.github.com/#x15.2.3.11 +if (!Object.isSealed) { + Object.isSealed = function isSealed(object) { + return false; + }; +} + +// ES5 15.2.3.12 +// http://es5.github.com/#x15.2.3.12 +if (!Object.isFrozen) { + Object.isFrozen = function isFrozen(object) { + return false; + }; +} + +// ES5 15.2.3.13 +// http://es5.github.com/#x15.2.3.13 +if (!Object.isExtensible) { + Object.isExtensible = function isExtensible(object) { + // 1. If Type(O) is not Object throw a TypeError exception. + if (Object(object) === object) { + throw new TypeError(); // TODO message + } + // 2. Return the Boolean value of the [[Extensible]] internal property of O. + var name = ''; + while (owns(object, name)) { + name += '?'; + } + object[name] = true; + var returnValue = owns(object, name); + delete object[name]; + return returnValue; + }; +} + +// ES5 15.2.3.14 +// http://es5.github.com/#x15.2.3.14 +if (!Object.keys) { + // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation + var hasDontEnumBug = true, + dontEnums = [ + "toString", + "toLocaleString", + "valueOf", + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable", + "constructor" + ], + dontEnumsLength = dontEnums.length; + + for (var key in {"toString": null}) + hasDontEnumBug = false; + + Object.keys = function keys(object) { + + if ((typeof object != "object" && typeof object != "function") || object === null) + throw new TypeError("Object.keys called on a non-object"); + + var keys = []; + for (var name in object) { + if (owns(object, name)) { + keys.push(name); + } + } + + if (hasDontEnumBug) { + for (var i = 0, ii = dontEnumsLength; i < ii; i++) { + var dontEnum = dontEnums[i]; + if (owns(object, dontEnum)) { + keys.push(dontEnum); + } + } + } + + return keys; + }; + +} + +// +// Date +// ==== +// + +// ES5 15.9.5.43 +// http://es5.github.com/#x15.9.5.43 +// This function returns a String value represent the instance in time +// represented by this Date object. The format of the String is the Date Time +// string format defined in 15.9.1.15. All fields are present in the String. +// The time zone is always UTC, denoted by the suffix Z. If the time value of +// this object is not a finite Number a RangeError exception is thrown. +if (!Date.prototype.toISOString || (new Date(-62198755200000).toISOString().indexOf('-000001') === -1)) { + Date.prototype.toISOString = function toISOString() { + var result, length, value, year; + if (!isFinite(this)) + throw new RangeError; + + // the date time string format is specified in 15.9.1.15. + result = [this.getUTCMonth() + 1, this.getUTCDate(), + this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds()]; + year = this.getUTCFullYear(); + year = (year < 0 ? '-' : (year > 9999 ? '+' : '')) + ('00000' + Math.abs(year)).slice(0 <= year && year <= 9999 ? -4 : -6); + + length = result.length; + while (length--) { + value = result[length]; + // pad months, days, hours, minutes, and seconds to have two digits. + if (value < 10) + result[length] = "0" + value; + } + // pad milliseconds to have three digits. + return year + "-" + result.slice(0, 2).join("-") + "T" + result.slice(2).join(":") + "." + + ("000" + this.getUTCMilliseconds()).slice(-3) + "Z"; + } +} + +// ES5 15.9.4.4 +// http://es5.github.com/#x15.9.4.4 +if (!Date.now) { + Date.now = function now() { + return new Date().getTime(); + }; +} + +// ES5 15.9.5.44 +// http://es5.github.com/#x15.9.5.44 +// This function provides a String representation of a Date object for use by +// JSON.stringify (15.12.3). +if (!Date.prototype.toJSON) { + Date.prototype.toJSON = function toJSON(key) { + // When the toJSON method is called with argument key, the following + // steps are taken: + + // 1. Let O be the result of calling ToObject, giving it the this + // value as its argument. + // 2. Let tv be ToPrimitive(O, hint Number). + // 3. If tv is a Number and is not finite, return null. + // XXX + // 4. Let toISO be the result of calling the [[Get]] internal method of + // O with argument "toISOString". + // 5. If IsCallable(toISO) is false, throw a TypeError exception. + if (typeof this.toISOString != "function") + throw new TypeError(); // TODO message + // 6. Return the result of calling the [[Call]] internal method of + // toISO with O as the this value and an empty argument list. + return this.toISOString(); + + // NOTE 1 The argument is ignored. + + // NOTE 2 The toJSON function is intentionally generic; it does not + // require that its this value be a Date object. Therefore, it can be + // transferred to other kinds of objects for use as a method. However, + // it does require that any such object have a toISOString method. An + // object is free to use the argument key to filter its + // stringification. + }; +} + +// ES5 15.9.4.2 +// http://es5.github.com/#x15.9.4.2 +// based on work shared by Daniel Friesen (dantman) +// http://gist.github.com/303249 +if (Date.parse("+275760-09-13T00:00:00.000Z") !== 8.64e15) { + // XXX global assignment won't work in embeddings that use + // an alternate object for the context. + Date = (function(NativeDate) { + + // Date.length === 7 + var Date = function Date(Y, M, D, h, m, s, ms) { + var length = arguments.length; + if (this instanceof NativeDate) { + var date = length == 1 && String(Y) === Y ? // isString(Y) + // We explicitly pass it through parse: + new NativeDate(Date.parse(Y)) : + // We have to manually make calls depending on argument + // length here + length >= 7 ? new NativeDate(Y, M, D, h, m, s, ms) : + length >= 6 ? new NativeDate(Y, M, D, h, m, s) : + length >= 5 ? new NativeDate(Y, M, D, h, m) : + length >= 4 ? new NativeDate(Y, M, D, h) : + length >= 3 ? new NativeDate(Y, M, D) : + length >= 2 ? new NativeDate(Y, M) : + length >= 1 ? new NativeDate(Y) : + new NativeDate(); + // Prevent mixups with unfixed Date object + date.constructor = Date; + return date; + } + return NativeDate.apply(this, arguments); + }; + + // 15.9.1.15 Date Time String Format. + var isoDateExpression = new RegExp("^" + + "(\\d{4}|[\+\-]\\d{6})" + // four-digit year capture or sign + 6-digit extended year + "(?:-(\\d{2})" + // optional month capture + "(?:-(\\d{2})" + // optional day capture + "(?:" + // capture hours:minutes:seconds.milliseconds + "T(\\d{2})" + // hours capture + ":(\\d{2})" + // minutes capture + "(?:" + // optional :seconds.milliseconds + ":(\\d{2})" + // seconds capture + "(?:\\.(\\d{3}))?" + // milliseconds capture + ")?" + + "(?:" + // capture UTC offset component + "Z|" + // UTC capture + "(?:" + // offset specifier +/-hours:minutes + "([-+])" + // sign capture + "(\\d{2})" + // hours offset capture + ":(\\d{2})" + // minutes offset capture + ")" + + ")?)?)?)?" + + "$"); + + // Copy any custom methods a 3rd party library may have added + for (var key in NativeDate) + Date[key] = NativeDate[key]; + + // Copy "native" methods explicitly; they may be non-enumerable + Date.now = NativeDate.now; + Date.UTC = NativeDate.UTC; + Date.prototype = NativeDate.prototype; + Date.prototype.constructor = Date; + + // Upgrade Date.parse to handle simplified ISO 8601 strings + Date.parse = function parse(string) { + var match = isoDateExpression.exec(string); + if (match) { + match.shift(); // kill match[0], the full match + // parse months, days, hours, minutes, seconds, and milliseconds + for (var i = 1; i < 7; i++) { + // provide default values if necessary + match[i] = +(match[i] || (i < 3 ? 1 : 0)); + // match[1] is the month. Months are 0-11 in JavaScript + // `Date` objects, but 1-12 in ISO notation, so we + // decrement. + if (i == 1) + match[i]--; + } + + // parse the UTC offset component + var minuteOffset = +match.pop(), hourOffset = +match.pop(), sign = match.pop(); + + // compute the explicit time zone offset if specified + var offset = 0; + if (sign) { + // detect invalid offsets and return early + if (hourOffset > 23 || minuteOffset > 59) + return NaN; + + // express the provided time zone offset in minutes. The offset is + // negative for time zones west of UTC; positive otherwise. + offset = (hourOffset * 60 + minuteOffset) * 6e4 * (sign == "+" ? -1 : 1); + } + + // Date.UTC for years between 0 and 99 converts year to 1900 + year + // The Gregorian calendar has a 400-year cycle, so + // to Date.UTC(year + 400, .... ) - 12622780800000 == Date.UTC(year, ...), + // where 12622780800000 - number of milliseconds in Gregorian calendar 400 years + var year = +match[0]; + if (0 <= year && year <= 99) { + match[0] = year + 400; + return NativeDate.UTC.apply(this, match) + offset - 12622780800000; + } + + // compute a new UTC date value, accounting for the optional offset + return NativeDate.UTC.apply(this, match) + offset; + } + return NativeDate.parse.apply(this, arguments); + }; + + return Date; + })(Date); +} + +// +// String +// ====== +// + +// ES5 15.5.4.20 +// http://es5.github.com/#x15.5.4.20 +var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" + + "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" + + "\u2029\uFEFF"; +if (!String.prototype.trim || ws.trim()) { + // http://blog.stevenlevithan.com/archives/faster-trim-javascript + // http://perfectionkills.com/whitespace-deviations/ + ws = "[" + ws + "]"; + var trimBeginRegexp = new RegExp("^" + ws + ws + "*"), + trimEndRegexp = new RegExp(ws + ws + "*$"); + String.prototype.trim = function trim() { + return String(this).replace(trimBeginRegexp, "").replace(trimEndRegexp, ""); + }; +} + +// +// Util +// ====== +// + +// ES5 9.4 +// http://es5.github.com/#x9.4 +// http://jsperf.com/to-integer +var toInteger = function (n) { + n = +n; + if (n !== n) // isNaN + n = 0; + else if (n !== 0 && n !== (1/0) && n !== -(1/0)) + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + return n; +}; + +var prepareString = "a"[0] != "a", + // ES5 9.9 + // http://es5.github.com/#x9.9 + toObject = function (o) { + if (o == null) { // this matches both null and undefined + throw new TypeError(); // TODO message + } + // If the implementation doesn't support by-index access of + // string characters (ex. IE < 7), split the string + if (prepareString && typeof o == "string" && o) { + return o.split(""); + } + return Object(o); + }; +}); + +define('ace/lib/event_emitter', ['require', 'exports', 'module' ], function(require, exports, module) { + + +var EventEmitter = {}; + +EventEmitter._emit = +EventEmitter._dispatchEvent = function(eventName, e) { + this._eventRegistry = this._eventRegistry || {}; + this._defaultHandlers = this._defaultHandlers || {}; + + var listeners = this._eventRegistry[eventName] || []; + var defaultHandler = this._defaultHandlers[eventName]; + if (!listeners.length && !defaultHandler) + return; + + e = e || {}; + e.type = eventName; + + if (!e.stopPropagation) { + e.stopPropagation = function() { + this.propagationStopped = true; + }; + } + + if (!e.preventDefault) { + e.preventDefault = function() { + this.defaultPrevented = true; + }; + } + + for (var i=0; i<listeners.length; i++) { + listeners[i](e); + if (e.propagationStopped) + break; + } + + if (defaultHandler && !e.defaultPrevented) + return defaultHandler(e); +}; + +EventEmitter.setDefaultHandler = function(eventName, callback) { + this._defaultHandlers = this._defaultHandlers || {}; + + if (this._defaultHandlers[eventName]) + throw new Error("The default handler for '" + eventName + "' is already set"); + + this._defaultHandlers[eventName] = callback; +}; + +EventEmitter.on = +EventEmitter.addEventListener = function(eventName, callback) { + this._eventRegistry = this._eventRegistry || {}; + + var listeners = this._eventRegistry[eventName]; + if (!listeners) + var listeners = this._eventRegistry[eventName] = []; + + if (listeners.indexOf(callback) == -1) + listeners.push(callback); +}; + +EventEmitter.removeListener = +EventEmitter.removeEventListener = function(eventName, callback) { + this._eventRegistry = this._eventRegistry || {}; + + var listeners = this._eventRegistry[eventName]; + if (!listeners) + return; + + var index = listeners.indexOf(callback); + if (index !== -1) + listeners.splice(index, 1); +}; + +EventEmitter.removeAllListeners = function(eventName) { + if (this._eventRegistry) this._eventRegistry[eventName] = []; +}; + +exports.EventEmitter = EventEmitter; + +}); + +define('ace/lib/oop', ['require', 'exports', 'module' ], function(require, exports, module) { + + +exports.inherits = (function() { + var tempCtor = function() {}; + return function(ctor, superCtor) { + tempCtor.prototype = superCtor.prototype; + ctor.super_ = superCtor.prototype; + ctor.prototype = new tempCtor(); + ctor.prototype.constructor = ctor; + }; +}()); + +exports.mixin = function(obj, mixin) { + for (var key in mixin) { + obj[key] = mixin[key]; + } +}; + +exports.implement = function(proto, mixin) { + exports.mixin(proto, mixin); +}; + +}); + +define('ace/mode/css_worker', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/worker/mirror', 'ace/mode/css/csslint'], function(require, exports, module) { + + +var oop = require("../lib/oop"); +var Mirror = require("../worker/mirror").Mirror; +var CSSLint = require("./css/csslint").CSSLint; + +var Worker = exports.Worker = function(sender) { + Mirror.call(this, sender); + this.setTimeout(200); +}; + +oop.inherits(Worker, Mirror); + +(function() { + + this.onUpdate = function() { + var value = this.doc.getValue(); + + var result = CSSLint.verify(value); + this.sender.emit("csslint", result.messages.map(function(msg) { + delete msg.rule; + return msg; + })); + }; + +}).call(Worker.prototype); + +}); +define('ace/worker/mirror', ['require', 'exports', 'module' , 'ace/document', 'ace/lib/lang'], function(require, exports, module) { + + +var Document = require("../document").Document; +var lang = require("../lib/lang"); + +var Mirror = exports.Mirror = function(sender) { + this.sender = sender; + var doc = this.doc = new Document(""); + + var deferredUpdate = this.deferredUpdate = lang.deferredCall(this.onUpdate.bind(this)); + + var _self = this; + sender.on("change", function(e) { + doc.applyDeltas([e.data]); + deferredUpdate.schedule(_self.$timeout); + }); +}; + +(function() { + + this.$timeout = 500; + + this.setTimeout = function(timeout) { + this.$timeout = timeout; + }; + + this.setValue = function(value) { + this.doc.setValue(value); + this.deferredUpdate.schedule(this.$timeout); + }; + + this.getValue = function(callbackId) { + this.sender.callback(this.doc.getValue(), callbackId); + }; + + this.onUpdate = function() { + // abstract method + }; + +}).call(Mirror.prototype); + +}); + +define('ace/document', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/event_emitter', 'ace/range', 'ace/anchor'], function(require, exports, module) { + + +var oop = require("./lib/oop"); +var EventEmitter = require("./lib/event_emitter").EventEmitter; +var Range = require("./range").Range; +var Anchor = require("./anchor").Anchor; + + /** + * new Document([text]) + * - text (String | Array): The starting text + * + * Creates a new `Document`. If `text` is included, the `Document` contains those strings; otherwise, it's empty. + * + **/ +var Document = function(text) { + this.$lines = []; + + // There has to be one line at least in the document. If you pass an empty + // string to the insert function, nothing will happen. Workaround. + if (text.length == 0) { + this.$lines = [""]; + } else if (Array.isArray(text)) { + this.insertLines(0, text); + } else { + this.insert({row: 0, column:0}, text); + } +}; + +(function() { + + oop.implement(this, EventEmitter); + this.setValue = function(text) { + var len = this.getLength(); + this.remove(new Range(0, 0, len, this.getLine(len-1).length)); + this.insert({row: 0, column:0}, text); + }; + this.getValue = function() { + return this.getAllLines().join(this.getNewLineCharacter()); + }; + this.createAnchor = function(row, column) { + return new Anchor(this, row, column); + }; + + // check for IE split bug + if ("aaa".split(/a/).length == 0) + this.$split = function(text) { + return text.replace(/\r\n|\r/g, "\n").split("\n"); + } + else + this.$split = function(text) { + return text.split(/\r\n|\r|\n/); + }; + this.$detectNewLine = function(text) { + var match = text.match(/^.*?(\r\n|\r|\n)/m); + if (match) { + this.$autoNewLine = match[1]; + } else { + this.$autoNewLine = "\n"; + } + }; + this.getNewLineCharacter = function() { + switch (this.$newLineMode) { + case "windows": + return "\r\n"; + + case "unix": + return "\n"; + + case "auto": + return this.$autoNewLine; + } + }; + + this.$autoNewLine = "\n"; + this.$newLineMode = "auto"; + this.setNewLineMode = function(newLineMode) { + if (this.$newLineMode === newLineMode) + return; + + this.$newLineMode = newLineMode; + }; + this.getNewLineMode = function() { + return this.$newLineMode; + }; + this.isNewLine = function(text) { + return (text == "\r\n" || text == "\r" || text == "\n"); + }; + this.getLine = function(row) { + return this.$lines[row] || ""; + }; + this.getLines = function(firstRow, lastRow) { + return this.$lines.slice(firstRow, lastRow + 1); + }; + this.getAllLines = function() { + return this.getLines(0, this.getLength()); + }; + this.getLength = function() { + return this.$lines.length; + }; + this.getTextRange = function(range) { + if (range.start.row == range.end.row) { + return this.$lines[range.start.row].substring(range.start.column, + range.end.column); + } + else { + var lines = this.getLines(range.start.row+1, range.end.row-1); + lines.unshift((this.$lines[range.start.row] || "").substring(range.start.column)); + lines.push((this.$lines[range.end.row] || "").substring(0, range.end.column)); + return lines.join(this.getNewLineCharacter()); + } + }; + this.$clipPosition = function(position) { + var length = this.getLength(); + if (position.row >= length) { + position.row = Math.max(0, length - 1); + position.column = this.getLine(length-1).length; + } + return position; + }; + this.insert = function(position, text) { + if (!text || text.length === 0) + return position; + + position = this.$clipPosition(position); + + // only detect new lines if the document has no line break yet + if (this.getLength() <= 1) + this.$detectNewLine(text); + + var lines = this.$split(text); + var firstLine = lines.splice(0, 1)[0]; + var lastLine = lines.length == 0 ? null : lines.splice(lines.length - 1, 1)[0]; + + position = this.insertInLine(position, firstLine); + if (lastLine !== null) { + position = this.insertNewLine(position); // terminate first line + position = this.insertLines(position.row, lines); + position = this.insertInLine(position, lastLine || ""); + } + return position; + }; + this.insertLines = function(row, lines) { + if (lines.length == 0) + return {row: row, column: 0}; + + // apply doesn't work for big arrays (smallest threshold is on safari 0xFFFF) + // to circumvent that we have to break huge inserts into smaller chunks here + if (lines.length > 0xFFFF) { + var end = this.insertLines(row, lines.slice(0xFFFF)); + lines = lines.slice(0, 0xFFFF); + } + + var args = [row, 0]; + args.push.apply(args, lines); + this.$lines.splice.apply(this.$lines, args); + + var range = new Range(row, 0, row + lines.length, 0); + var delta = { + action: "insertLines", + range: range, + lines: lines + }; + this._emit("change", { data: delta }); + return end || range.end; + }; + this.insertNewLine = function(position) { + position = this.$clipPosition(position); + var line = this.$lines[position.row] || ""; + + this.$lines[position.row] = line.substring(0, position.column); + this.$lines.splice(position.row + 1, 0, line.substring(position.column, line.length)); + + var end = { + row : position.row + 1, + column : 0 + }; + + var delta = { + action: "insertText", + range: Range.fromPoints(position, end), + text: this.getNewLineCharacter() + }; + this._emit("change", { data: delta }); + + return end; + }; + this.insertInLine = function(position, text) { + if (text.length == 0) + return position; + + var line = this.$lines[position.row] || ""; + + this.$lines[position.row] = line.substring(0, position.column) + text + + line.substring(position.column); + + var end = { + row : position.row, + column : position.column + text.length + }; + + var delta = { + action: "insertText", + range: Range.fromPoints(position, end), + text: text + }; + this._emit("change", { data: delta }); + + return end; + }; + this.remove = function(range) { + // clip to document + range.start = this.$clipPosition(range.start); + range.end = this.$clipPosition(range.end); + + if (range.isEmpty()) + return range.start; + + var firstRow = range.start.row; + var lastRow = range.end.row; + + if (range.isMultiLine()) { + var firstFullRow = range.start.column == 0 ? firstRow : firstRow + 1; + var lastFullRow = lastRow - 1; + + if (range.end.column > 0) + this.removeInLine(lastRow, 0, range.end.column); + + if (lastFullRow >= firstFullRow) + this.removeLines(firstFullRow, lastFullRow); + + if (firstFullRow != firstRow) { + this.removeInLine(firstRow, range.start.column, this.getLine(firstRow).length); + this.removeNewLine(range.start.row); + } + } + else { + this.removeInLine(firstRow, range.start.column, range.end.column); + } + return range.start; + }; + this.removeInLine = function(row, startColumn, endColumn) { + if (startColumn == endColumn) + return; + + var range = new Range(row, startColumn, row, endColumn); + var line = this.getLine(row); + var removed = line.substring(startColumn, endColumn); + var newLine = line.substring(0, startColumn) + line.substring(endColumn, line.length); + this.$lines.splice(row, 1, newLine); + + var delta = { + action: "removeText", + range: range, + text: removed + }; + this._emit("change", { data: delta }); + return range.start; + }; + this.removeLines = function(firstRow, lastRow) { + var range = new Range(firstRow, 0, lastRow + 1, 0); + var removed = this.$lines.splice(firstRow, lastRow - firstRow + 1); + + var delta = { + action: "removeLines", + range: range, + nl: this.getNewLineCharacter(), + lines: removed + }; + this._emit("change", { data: delta }); + return removed; + }; + this.removeNewLine = function(row) { + var firstLine = this.getLine(row); + var secondLine = this.getLine(row+1); + + var range = new Range(row, firstLine.length, row+1, 0); + var line = firstLine + secondLine; + + this.$lines.splice(row, 2, line); + + var delta = { + action: "removeText", + range: range, + text: this.getNewLineCharacter() + }; + this._emit("change", { data: delta }); + }; + this.replace = function(range, text) { + if (text.length == 0 && range.isEmpty()) + return range.start; + + // Shortcut: If the text we want to insert is the same as it is already + // in the document, we don't have to replace anything. + if (text == this.getTextRange(range)) + return range.end; + + this.remove(range); + if (text) { + var end = this.insert(range.start, text); + } + else { + end = range.start; + } + + return end; + }; + this.applyDeltas = function(deltas) { + for (var i=0; i<deltas.length; i++) { + var delta = deltas[i]; + var range = Range.fromPoints(delta.range.start, delta.range.end); + + if (delta.action == "insertLines") + this.insertLines(range.start.row, delta.lines); + else if (delta.action == "insertText") + this.insert(range.start, delta.text); + else if (delta.action == "removeLines") + this.removeLines(range.start.row, range.end.row - 1); + else if (delta.action == "removeText") + this.remove(range); + } + }; + this.revertDeltas = function(deltas) { + for (var i=deltas.length-1; i>=0; i--) { + var delta = deltas[i]; + + var range = Range.fromPoints(delta.range.start, delta.range.end); + + if (delta.action == "insertLines") + this.removeLines(range.start.row, range.end.row - 1); + else if (delta.action == "insertText") + this.remove(range); + else if (delta.action == "removeLines") + this.insertLines(range.start.row, delta.lines); + else if (delta.action == "removeText") + this.insert(range.start, delta.text); + } + }; + +}).call(Document.prototype); + +exports.Document = Document; +}); + +define('ace/range', ['require', 'exports', 'module' ], function(require, exports, module) { + + +/** + * class Range + * + * This object is used in various places to indicate a region within the editor. To better visualize how this works, imagine a rectangle. Each quadrant of the rectangle is analogus to a range, as ranges contain a starting row and starting column, and an ending row, and ending column. + * + **/ + +/** + * new Range(startRow, startColumn, endRow, endColumn) + * - startRow (Number): The starting row + * - startColumn (Number): The starting column + * - endRow (Number): The ending row + * - endColumn (Number): The ending column + * + * Creates a new `Range` object with the given starting and ending row and column points. + * + **/ +var Range = function(startRow, startColumn, endRow, endColumn) { + this.start = { + row: startRow, + column: startColumn + }; + + this.end = { + row: endRow, + column: endColumn + }; +}; + +(function() { + /** + * Range.isEqual(range) -> Boolean + * - range (Range): A range to check against + * + * Returns `true` if and only if the starting row and column, and ending tow and column, are equivalent to those given by `range`. + * + **/ + this.isEqual = function(range) { + return this.start.row == range.start.row && + this.end.row == range.end.row && + this.start.column == range.start.column && + this.end.column == range.end.column + }; + this.toString = function() { + return ("Range: [" + this.start.row + "/" + this.start.column + + "] -> [" + this.end.row + "/" + this.end.column + "]"); + }; + + this.contains = function(row, column) { + return this.compare(row, column) == 0; + }; + this.compareRange = function(range) { + var cmp, + end = range.end, + start = range.start; + + cmp = this.compare(end.row, end.column); + if (cmp == 1) { + cmp = this.compare(start.row, start.column); + if (cmp == 1) { + return 2; + } else if (cmp == 0) { + return 1; + } else { + return 0; + } + } else if (cmp == -1) { + return -2; + } else { + cmp = this.compare(start.row, start.column); + if (cmp == -1) { + return -1; + } else if (cmp == 1) { + return 42; + } else { + return 0; + } + } + } + + /** related to: Range.compare + * Range.comparePoint(p) -> Number + * - p (Range): A point to compare with + * + (Number): This method returns one of the following numbers:<br/> + * * `0` if the two points are exactly equal<br/> + * * `-1` if `p.row` is less then the calling range<br/> + * * `1` if `p.row` is greater than the calling range<br/> + * <br/> + * If the starting row of the calling range is equal to `p.row`, and:<br/> + * * `p.column` is greater than or equal to the calling range's starting column, this returns `0`<br/> + * * Otherwise, it returns -1<br/> + *<br/> + * If the ending row of the calling range is equal to `p.row`, and:<br/> + * * `p.column` is less than or equal to the calling range's ending column, this returns `0`<br/> + * * Otherwise, it returns 1<br/> + * + * Checks the row and column points of `p` with the row and column points of the calling range. + * + * + * + **/ + this.comparePoint = function(p) { + return this.compare(p.row, p.column); + } + + /** related to: Range.comparePoint + * Range.containsRange(range) -> Boolean + * - range (Range): A range to compare with + * + * Checks the start and end points of `range` and compares them to the calling range. Returns `true` if the `range` is contained within the caller's range. + * + **/ + this.containsRange = function(range) { + return this.comparePoint(range.start) == 0 && this.comparePoint(range.end) == 0; + } + + /** + * Range.intersects(range) -> Boolean + * - range (Range): A range to compare with + * + * Returns `true` if passed in `range` intersects with the one calling this method. + * + **/ + this.intersects = function(range) { + var cmp = this.compareRange(range); + return (cmp == -1 || cmp == 0 || cmp == 1); + } + + /** + * Range.isEnd(row, column) -> Boolean + * - row (Number): A row point to compare with + * - column (Number): A column point to compare with + * + * Returns `true` if the caller's ending row point is the same as `row`, and if the caller's ending column is the same as `column`. + * + **/ + this.isEnd = function(row, column) { + return this.end.row == row && this.end.column == column; + } + + /** + * Range.isStart(row, column) -> Boolean + * - row (Number): A row point to compare with + * - column (Number): A column point to compare with + * + * Returns `true` if the caller's starting row point is the same as `row`, and if the caller's starting column is the same as `column`. + * + **/ + this.isStart = function(row, column) { + return this.start.row == row && this.start.column == column; + } + + /** + * Range.setStart(row, column) + * - row (Number): A row point to set + * - column (Number): A column point to set + * + * Sets the starting row and column for the range. + * + **/ + this.setStart = function(row, column) { + if (typeof row == "object") { + this.start.column = row.column; + this.start.row = row.row; + } else { + this.start.row = row; + this.start.column = column; + } + } + + /** + * Range.setEnd(row, column) + * - row (Number): A row point to set + * - column (Number): A column point to set + * + * Sets the starting row and column for the range. + * + **/ + this.setEnd = function(row, column) { + if (typeof row == "object") { + this.end.column = row.column; + this.end.row = row.row; + } else { + this.end.row = row; + this.end.column = column; + } + } + + /** related to: Range.compare + * Range.inside(row, column) -> Boolean + * - row (Number): A row point to compare with + * - column (Number): A column point to compare with + * + * Returns `true` if the `row` and `column` are within the given range. + * + **/ + this.inside = function(row, column) { + if (this.compare(row, column) == 0) { + if (this.isEnd(row, column) || this.isStart(row, column)) { + return false; + } else { + return true; + } + } + return false; + } + + /** related to: Range.compare + * Range.insideStart(row, column) -> Boolean + * - row (Number): A row point to compare with + * - column (Number): A column point to compare with + * + * Returns `true` if the `row` and `column` are within the given range's starting points. + * + **/ + this.insideStart = function(row, column) { + if (this.compare(row, column) == 0) { + if (this.isEnd(row, column)) { + return false; + } else { + return true; + } + } + return false; + } + + /** related to: Range.compare + * Range.insideEnd(row, column) -> Boolean + * - row (Number): A row point to compare with + * - column (Number): A column point to compare with + * + * Returns `true` if the `row` and `column` are within the given range's ending points. + * + **/ + this.insideEnd = function(row, column) { + if (this.compare(row, column) == 0) { + if (this.isStart(row, column)) { + return false; + } else { + return true; + } + } + return false; + } + + /** + * Range.compare(row, column) -> Number + * - row (Number): A row point to compare with + * - column (Number): A column point to compare with + * + (Number): This method returns one of the following numbers:<br/> + * * `0` if the two points are exactly equal <br/> + * * `-1` if `p.row` is less then the calling range <br/> + * * `1` if `p.row` is greater than the calling range <br/> + * <br/> + * If the starting row of the calling range is equal to `p.row`, and: <br/> + * * `p.column` is greater than or equal to the calling range's starting column, this returns `0`<br/> + * * Otherwise, it returns -1<br/> + * <br/> + * If the ending row of the calling range is equal to `p.row`, and: <br/> + * * `p.column` is less than or equal to the calling range's ending column, this returns `0` <br/> + * * Otherwise, it returns 1 + * + * Checks the row and column points with the row and column points of the calling range. + * + * + **/ + this.compare = function(row, column) { + if (!this.isMultiLine()) { + if (row === this.start.row) { + return column < this.start.column ? -1 : (column > this.end.column ? 1 : 0); + }; + } + + if (row < this.start.row) + return -1; + + if (row > this.end.row) + return 1; + + if (this.start.row === row) + return column >= this.start.column ? 0 : -1; + + if (this.end.row === row) + return column <= this.end.column ? 0 : 1; + + return 0; + }; + this.compareStart = function(row, column) { + if (this.start.row == row && this.start.column == column) { + return -1; + } else { + return this.compare(row, column); + } + } + + /** + * Range.compareEnd(row, column) -> Number + * - row (Number): A row point to compare with + * - column (Number): A column point to compare with + * + (Number): This method returns one of the following numbers:<br/> + * * `0` if the two points are exactly equal<br/> + * * `-1` if `p.row` is less then the calling range<br/> + * * `1` if `p.row` is greater than the calling range, or if `isEnd` is `true.<br/> + * <br/> + * If the starting row of the calling range is equal to `p.row`, and:<br/> + * * `p.column` is greater than or equal to the calling range's starting column, this returns `0`<br/> + * * Otherwise, it returns -1<br/> + *<br/> + * If the ending row of the calling range is equal to `p.row`, and:<br/> + * * `p.column` is less than or equal to the calling range's ending column, this returns `0`<br/> + * * Otherwise, it returns 1 + * + * Checks the row and column points with the row and column points of the calling range. + * + * + **/ + this.compareEnd = function(row, column) { + if (this.end.row == row && this.end.column == column) { + return 1; + } else { + return this.compare(row, column); + } + } + + /** + * Range.compareInside(row, column) -> Number + * - row (Number): A row point to compare with + * - column (Number): A column point to compare with + * + (Number): This method returns one of the following numbers:<br/> + * * `1` if the ending row of the calling range is equal to `row`, and the ending column of the calling range is equal to `column`<br/> + * * `-1` if the starting row of the calling range is equal to `row`, and the starting column of the calling range is equal to `column`<br/> + * <br/> + * Otherwise, it returns the value after calling [[Range.compare `compare()`]]. + * + * Checks the row and column points with the row and column points of the calling range. + * + * + * + **/ + this.compareInside = function(row, column) { + if (this.end.row == row && this.end.column == column) { + return 1; + } else if (this.start.row == row && this.start.column == column) { + return -1; + } else { + return this.compare(row, column); + } + } + + /** + * Range.clipRows(firstRow, lastRow) -> Range + * - firstRow (Number): The starting row + * - lastRow (Number): The ending row + * + * Returns the part of the current `Range` that occurs within the boundaries of `firstRow` and `lastRow` as a new `Range` object. + * + **/ + this.clipRows = function(firstRow, lastRow) { + if (this.end.row > lastRow) { + var end = { + row: lastRow+1, + column: 0 + }; + } + + if (this.start.row > lastRow) { + var start = { + row: lastRow+1, + column: 0 + }; + } + + if (this.start.row < firstRow) { + var start = { + row: firstRow, + column: 0 + }; + } + + if (this.end.row < firstRow) { + var end = { + row: firstRow, + column: 0 + }; + } + return Range.fromPoints(start || this.start, end || this.end); + }; + this.extend = function(row, column) { + var cmp = this.compare(row, column); + + if (cmp == 0) + return this; + else if (cmp == -1) + var start = {row: row, column: column}; + else + var end = {row: row, column: column}; + + return Range.fromPoints(start || this.start, end || this.end); + }; + + this.isEmpty = function() { + return (this.start.row == this.end.row && this.start.column == this.end.column); + }; + this.isMultiLine = function() { + return (this.start.row !== this.end.row); + }; + this.clone = function() { + return Range.fromPoints(this.start, this.end); + }; + this.collapseRows = function() { + if (this.end.column == 0) + return new Range(this.start.row, 0, Math.max(this.start.row, this.end.row-1), 0) + else + return new Range(this.start.row, 0, this.end.row, 0) + }; + this.toScreenRange = function(session) { + var screenPosStart = + session.documentToScreenPosition(this.start); + var screenPosEnd = + session.documentToScreenPosition(this.end); + + return new Range( + screenPosStart.row, screenPosStart.column, + screenPosEnd.row, screenPosEnd.column + ); + }; + +}).call(Range.prototype); +Range.fromPoints = function(start, end) { + return new Range(start.row, start.column, end.row, end.column); +}; + +exports.Range = Range; +}); + +define('ace/anchor', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/event_emitter'], function(require, exports, module) { + + +var oop = require("./lib/oop"); +var EventEmitter = require("./lib/event_emitter").EventEmitter; + +/** + * new Anchor(doc, row, column) + * - doc (Document): The document to associate with the anchor + * - row (Number): The starting row position + * - column (Number): The starting column position + * + * Creates a new `Anchor` and associates it with a document. + * + **/ + +var Anchor = exports.Anchor = function(doc, row, column) { + this.document = doc; + + if (typeof column == "undefined") + this.setPosition(row.row, row.column); + else + this.setPosition(row, column); + + this.$onChange = this.onChange.bind(this); + doc.on("change", this.$onChange); +}; + +(function() { + + oop.implement(this, EventEmitter); + + this.getPosition = function() { + return this.$clipPositionToDocument(this.row, this.column); + }; + + this.getDocument = function() { + return this.document; + }; + + this.onChange = function(e) { + var delta = e.data; + var range = delta.range; + + if (range.start.row == range.end.row && range.start.row != this.row) + return; + + if (range.start.row > this.row) + return; + + if (range.start.row == this.row && range.start.column > this.column) + return; + + var row = this.row; + var column = this.column; + + if (delta.action === "insertText") { + if (range.start.row === row && range.start.column <= column) { + if (range.start.row === range.end.row) { + column += range.end.column - range.start.column; + } + else { + column -= range.start.column; + row += range.end.row - range.start.row; + } + } + else if (range.start.row !== range.end.row && range.start.row < row) { + row += range.end.row - range.start.row; + } + } else if (delta.action === "insertLines") { + if (range.start.row <= row) { + row += range.end.row - range.start.row; + } + } + else if (delta.action == "removeText") { + if (range.start.row == row && range.start.column < column) { + if (range.end.column >= column) + column = range.start.column; + else + column = Math.max(0, column - (range.end.column - range.start.column)); + + } else if (range.start.row !== range.end.row && range.start.row < row) { + if (range.end.row == row) { + column = Math.max(0, column - range.end.column) + range.start.column; + } + row -= (range.end.row - range.start.row); + } + else if (range.end.row == row) { + row -= range.end.row - range.start.row; + column = Math.max(0, column - range.end.column) + range.start.column; + } + } else if (delta.action == "removeLines") { + if (range.start.row <= row) { + if (range.end.row <= row) + row -= range.end.row - range.start.row; + else { + row = range.start.row; + column = 0; + } + } + } + + this.setPosition(row, column, true); + }; + + this.setPosition = function(row, column, noClip) { + var pos; + if (noClip) { + pos = { + row: row, + column: column + }; + } + else { + pos = this.$clipPositionToDocument(row, column); + } + + if (this.row == pos.row && this.column == pos.column) + return; + + var old = { + row: this.row, + column: this.column + }; + + this.row = pos.row; + this.column = pos.column; + this._emit("change", { + old: old, + value: pos + }); + }; + + this.detach = function() { + this.document.removeEventListener("change", this.$onChange); + }; + + this.$clipPositionToDocument = function(row, column) { + var pos = {}; + + if (row >= this.document.getLength()) { + pos.row = Math.max(0, this.document.getLength() - 1); + pos.column = this.document.getLine(pos.row).length; + } + else if (row < 0) { + pos.row = 0; + pos.column = 0; + } + else { + pos.row = row; + pos.column = Math.min(this.document.getLine(pos.row).length, Math.max(0, column)); + } + + if (column < 0) + pos.column = 0; + + return pos; + }; + +}).call(Anchor.prototype); + +}); + +define('ace/lib/lang', ['require', 'exports', 'module' ], function(require, exports, module) { + + +exports.stringReverse = function(string) { + return string.split("").reverse().join(""); +}; + +exports.stringRepeat = function (string, count) { + return new Array(count + 1).join(string); +}; + +var trimBeginRegexp = /^\s\s*/; +var trimEndRegexp = /\s\s*$/; + +exports.stringTrimLeft = function (string) { + return string.replace(trimBeginRegexp, ''); +}; + +exports.stringTrimRight = function (string) { + return string.replace(trimEndRegexp, ''); +}; + +exports.copyObject = function(obj) { + var copy = {}; + for (var key in obj) { + copy[key] = obj[key]; + } + return copy; +}; + +exports.copyArray = function(array){ + var copy = []; + for (var i=0, l=array.length; i<l; i++) { + if (array[i] && typeof array[i] == "object") + copy[i] = this.copyObject( array[i] ); + else + copy[i] = array[i]; + } + return copy; +}; + +exports.deepCopy = function (obj) { + if (typeof obj != "object") { + return obj; + } + + var copy = obj.constructor(); + for (var key in obj) { + if (typeof obj[key] == "object") { + copy[key] = this.deepCopy(obj[key]); + } else { + copy[key] = obj[key]; + } + } + return copy; +}; + +exports.arrayToMap = function(arr) { + var map = {}; + for (var i=0; i<arr.length; i++) { + map[arr[i]] = 1; + } + return map; + +}; +exports.arrayRemove = function(array, value) { + for (var i = 0; i <= array.length; i++) { + if (value === array[i]) { + array.splice(i, 1); + } + } +}; + +exports.escapeRegExp = function(str) { + return str.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1'); +}; + +exports.getMatchOffsets = function(string, regExp) { + var matches = []; + + string.replace(regExp, function(str) { + matches.push({ + offset: arguments[arguments.length-2], + length: str.length + }); + }); + + return matches; +}; + + +exports.deferredCall = function(fcn) { + + var timer = null; + var callback = function() { + timer = null; + fcn(); + }; + + var deferred = function(timeout) { + deferred.cancel(); + timer = setTimeout(callback, timeout || 0); + return deferred; + }; + + deferred.schedule = deferred; + + deferred.call = function() { + this.cancel(); + fcn(); + return deferred; + }; + + deferred.cancel = function() { + clearTimeout(timer); + timer = null; + return deferred; + }; + + return deferred; +}; + +}); +define('ace/mode/css/csslint', ['require', 'exports', 'module' ], function(require, exports, module) { +/*! +CSSLint +Copyright (c) 2011 Nicole Sullivan and Nicholas C. Zakas. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ +/* Build time: 2-March-2012 02:47:11 */ + +/*! +Parser-Lib +Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ +/* Version v0.1.6, Build time: 2-March-2012 02:44:32 */ +var parserlib = {}; +(function(){ + + +/** + * A generic base to inherit from for any object + * that needs event handling. + * @class EventTarget + * @constructor + */ +function EventTarget(){ + + /** + * The array of listeners for various events. + * @type Object + * @property _listeners + * @private + */ + this._listeners = {}; +} + +EventTarget.prototype = { + + //restore constructor + constructor: EventTarget, + + /** + * Adds a listener for a given event type. + * @param {String} type The type of event to add a listener for. + * @param {Function} listener The function to call when the event occurs. + * @return {void} + * @method addListener + */ + addListener: function(type, listener){ + if (!this._listeners[type]){ + this._listeners[type] = []; + } + + this._listeners[type].push(listener); + }, + + /** + * Fires an event based on the passed-in object. + * @param {Object|String} event An object with at least a 'type' attribute + * or a string indicating the event name. + * @return {void} + * @method fire + */ + fire: function(event){ + if (typeof event == "string"){ + event = { type: event }; + } + if (typeof event.target != "undefined"){ + event.target = this; + } + + if (typeof event.type == "undefined"){ + throw new Error("Event object missing 'type' property."); + } + + if (this._listeners[event.type]){ + + //create a copy of the array and use that so listeners can't chane + var listeners = this._listeners[event.type].concat(); + for (var i=0, len=listeners.length; i < len; i++){ + listeners[i].call(this, event); + } + } + }, + + /** + * Removes a listener for a given event type. + * @param {String} type The type of event to remove a listener from. + * @param {Function} listener The function to remove from the event. + * @return {void} + * @method removeListener + */ + removeListener: function(type, listener){ + if (this._listeners[type]){ + var listeners = this._listeners[type]; + for (var i=0, len=listeners.length; i < len; i++){ + if (listeners[i] === listener){ + listeners.splice(i, 1); + break; + } + } + + + } + } +}; +function StringReader(text){ + + /** + * The input text with line endings normalized. + * @property _input + * @type String + * @private + */ + this._input = text.replace(/\n\r?/g, "\n"); + this._line = 1; + this._col = 1; + this._cursor = 0; +} + +StringReader.prototype = { + + //restore constructor + constructor: StringReader, + + //------------------------------------------------------------------------- + // Position info + //------------------------------------------------------------------------- + + /** + * Returns the column of the character to be read next. + * @return {int} The column of the character to be read next. + * @method getCol + */ + getCol: function(){ + return this._col; + }, + + /** + * Returns the row of the character to be read next. + * @return {int} The row of the character to be read next. + * @method getLine + */ + getLine: function(){ + return this._line ; + }, + + /** + * Determines if you're at the end of the input. + * @return {Boolean} True if there's no more input, false otherwise. + * @method eof + */ + eof: function(){ + return (this._cursor == this._input.length); + }, + + //------------------------------------------------------------------------- + // Basic reading + //------------------------------------------------------------------------- + + /** + * Reads the next character without advancing the cursor. + * @param {int} count How many characters to look ahead (default is 1). + * @return {String} The next character or null if there is no next character. + * @method peek + */ + peek: function(count){ + var c = null; + count = (typeof count == "undefined" ? 1 : count); + + //if we're not at the end of the input... + if (this._cursor < this._input.length){ + + //get character and increment cursor and column + c = this._input.charAt(this._cursor + count - 1); + } + + return c; + }, + + /** + * Reads the next character from the input and adjusts the row and column + * accordingly. + * @return {String} The next character or null if there is no next character. + * @method read + */ + read: function(){ + var c = null; + + //if we're not at the end of the input... + if (this._cursor < this._input.length){ + + //if the last character was a newline, increment row count + //and reset column count + if (this._input.charAt(this._cursor) == "\n"){ + this._line++; + this._col=1; + } else { + this._col++; + } + + //get character and increment cursor and column + c = this._input.charAt(this._cursor++); + } + + return c; + }, + + //------------------------------------------------------------------------- + // Misc + //------------------------------------------------------------------------- + + /** + * Saves the current location so it can be returned to later. + * @method mark + * @return {void} + */ + mark: function(){ + this._bookmark = { + cursor: this._cursor, + line: this._line, + col: this._col + }; + }, + + reset: function(){ + if (this._bookmark){ + this._cursor = this._bookmark.cursor; + this._line = this._bookmark.line; + this._col = this._bookmark.col; + delete this._bookmark; + } + }, + + //------------------------------------------------------------------------- + // Advanced reading + //------------------------------------------------------------------------- + + /** + * Reads up to and including the given string. Throws an error if that + * string is not found. + * @param {String} pattern The string to read. + * @return {String} The string when it is found. + * @throws Error when the string pattern is not found. + * @method readTo + */ + readTo: function(pattern){ + + var buffer = "", + c; + while (buffer.length < pattern.length || buffer.lastIndexOf(pattern) != buffer.length - pattern.length){ + c = this.read(); + if (c){ + buffer += c; + } else { + throw new Error("Expected \"" + pattern + "\" at line " + this._line + ", col " + this._col + "."); + } + } + + return buffer; + + }, + + /** + * Reads characters while each character causes the given + * filter function to return true. The function is passed + * in each character and either returns true to continue + * reading or false to stop. + * @param {Function} filter The function to read on each character. + * @return {String} The string made up of all characters that passed the + * filter check. + * @method readWhile + */ + readWhile: function(filter){ + + var buffer = "", + c = this.read(); + + while(c !== null && filter(c)){ + buffer += c; + c = this.read(); + } + + return buffer; + + }, + + /** + * Reads characters that match either text or a regular expression and + * returns those characters. If a match is found, the row and column + * are adjusted; if no match is found, the reader's state is unchanged. + * reading or false to stop. + * @param {String|RegExp} matchter If a string, then the literal string + * value is searched for. If a regular expression, then any string + * matching the pattern is search for. + * @return {String} The string made up of all characters that matched or + * null if there was no match. + * @method readMatch + */ + readMatch: function(matcher){ + + var source = this._input.substring(this._cursor), + value = null; + + //if it's a string, just do a straight match + if (typeof matcher == "string"){ + if (source.indexOf(matcher) === 0){ + value = this.readCount(matcher.length); + } + } else if (matcher instanceof RegExp){ + if (matcher.test(source)){ + value = this.readCount(RegExp.lastMatch.length); + } + } + + return value; + }, + + + /** + * Reads a given number of characters. If the end of the input is reached, + * it reads only the remaining characters and does not throw an error. + * @param {int} count The number of characters to read. + * @return {String} The string made up the read characters. + * @method readCount + */ + readCount: function(count){ + var buffer = ""; + + while(count--){ + buffer += this.read(); + } + + return buffer; + } + +}; +function SyntaxError(message, line, col){ + + /** + * The column at which the error occurred. + * @type int + * @property col + */ + this.col = col; + this.line = line; + this.message = message; + +} + +//inherit from Error +SyntaxError.prototype = new Error(); +function SyntaxUnit(text, line, col, type){ + + + /** + * The column of text on which the unit resides. + * @type int + * @property col + */ + this.col = col; + this.line = line; + this.text = text; + this.type = type; +} + +/** + * Create a new syntax unit based solely on the given token. + * Convenience method for creating a new syntax unit when + * it represents a single token instead of multiple. + * @param {Object} token The token object to represent. + * @return {parserlib.util.SyntaxUnit} The object representing the token. + * @static + * @method fromToken + */ +SyntaxUnit.fromToken = function(token){ + return new SyntaxUnit(token.value, token.startLine, token.startCol); +}; + +SyntaxUnit.prototype = { + + //restore constructor + constructor: SyntaxUnit, + + /** + * Returns the text representation of the unit. + * @return {String} The text representation of the unit. + * @method valueOf + */ + valueOf: function(){ + return this.toString(); + }, + + /** + * Returns the text representation of the unit. + * @return {String} The text representation of the unit. + * @method toString + */ + toString: function(){ + return this.text; + } + +}; + +/** + * Generic TokenStream providing base functionality. + * @class TokenStreamBase + * @namespace parserlib.util + * @constructor + * @param {String|StringReader} input The text to tokenize or a reader from + * which to read the input. + */ +function TokenStreamBase(input, tokenData){ + + /** + * The string reader for easy access to the text. + * @type StringReader + * @property _reader + * @private + */ + this._reader = input ? new StringReader(input.toString()) : null; + this._token = null; + this._tokenData = tokenData; + this._lt = []; + this._ltIndex = 0; + + this._ltIndexCache = []; +} + +/** + * Accepts an array of token information and outputs + * an array of token data containing key-value mappings + * and matching functions that the TokenStream needs. + * @param {Array} tokens An array of token descriptors. + * @return {Array} An array of processed token data. + * @method createTokenData + * @static + */ +TokenStreamBase.createTokenData = function(tokens){ + + var nameMap = [], + typeMap = {}, + tokenData = tokens.concat([]), + i = 0, + len = tokenData.length+1; + + tokenData.UNKNOWN = -1; + tokenData.unshift({name:"EOF"}); + + for (; i < len; i++){ + nameMap.push(tokenData[i].name); + tokenData[tokenData[i].name] = i; + if (tokenData[i].text){ + typeMap[tokenData[i].text] = i; + } + } + + tokenData.name = function(tt){ + return nameMap[tt]; + }; + + tokenData.type = function(c){ + return typeMap[c]; + }; + + return tokenData; +}; + +TokenStreamBase.prototype = { + + //restore constructor + constructor: TokenStreamBase, + + //------------------------------------------------------------------------- + // Matching methods + //------------------------------------------------------------------------- + + /** + * Determines if the next token matches the given token type. + * If so, that token is consumed; if not, the token is placed + * back onto the token stream. You can pass in any number of + * token types and this will return true if any of the token + * types is found. + * @param {int|int[]} tokenTypes Either a single token type or an array of + * token types that the next token might be. If an array is passed, + * it's assumed that the token can be any of these. + * @param {variant} channel (Optional) The channel to read from. If not + * provided, reads from the default (unnamed) channel. + * @return {Boolean} True if the token type matches, false if not. + * @method match + */ + match: function(tokenTypes, channel){ + + //always convert to an array, makes things easier + if (!(tokenTypes instanceof Array)){ + tokenTypes = [tokenTypes]; + } + + var tt = this.get(channel), + i = 0, + len = tokenTypes.length; + + while(i < len){ + if (tt == tokenTypes[i++]){ + return true; + } + } + + //no match found, put the token back + this.unget(); + return false; + }, + + /** + * Determines if the next token matches the given token type. + * If so, that token is consumed; if not, an error is thrown. + * @param {int|int[]} tokenTypes Either a single token type or an array of + * token types that the next token should be. If an array is passed, + * it's assumed that the token must be one of these. + * @param {variant} channel (Optional) The channel to read from. If not + * provided, reads from the default (unnamed) channel. + * @return {void} + * @method mustMatch + */ + mustMatch: function(tokenTypes, channel){ + + var token; + + //always convert to an array, makes things easier + if (!(tokenTypes instanceof Array)){ + tokenTypes = [tokenTypes]; + } + + if (!this.match.apply(this, arguments)){ + token = this.LT(1); + throw new SyntaxError("Expected " + this._tokenData[tokenTypes[0]].name + + " at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); + } + }, + + //------------------------------------------------------------------------- + // Consuming methods + //------------------------------------------------------------------------- + + /** + * Keeps reading from the token stream until either one of the specified + * token types is found or until the end of the input is reached. + * @param {int|int[]} tokenTypes Either a single token type or an array of + * token types that the next token should be. If an array is passed, + * it's assumed that the token must be one of these. + * @param {variant} channel (Optional) The channel to read from. If not + * provided, reads from the default (unnamed) channel. + * @return {void} + * @method advance + */ + advance: function(tokenTypes, channel){ + + while(this.LA(0) !== 0 && !this.match(tokenTypes, channel)){ + this.get(); + } + + return this.LA(0); + }, + + /** + * Consumes the next token from the token stream. + * @return {int} The token type of the token that was just consumed. + * @method get + */ + get: function(channel){ + + var tokenInfo = this._tokenData, + reader = this._reader, + value, + i =0, + len = tokenInfo.length, + found = false, + token, + info; + + //check the lookahead buffer first + if (this._lt.length && this._ltIndex >= 0 && this._ltIndex < this._lt.length){ + + i++; + this._token = this._lt[this._ltIndex++]; + info = tokenInfo[this._token.type]; + + //obey channels logic + while((info.channel !== undefined && channel !== info.channel) && + this._ltIndex < this._lt.length){ + this._token = this._lt[this._ltIndex++]; + info = tokenInfo[this._token.type]; + i++; + } + + //here be dragons + if ((info.channel === undefined || channel === info.channel) && + this._ltIndex <= this._lt.length){ + this._ltIndexCache.push(i); + return this._token.type; + } + } + + //call token retriever method + token = this._getToken(); + + //if it should be hidden, don't save a token + if (token.type > -1 && !tokenInfo[token.type].hide){ + + //apply token channel + token.channel = tokenInfo[token.type].channel; + + //save for later + this._token = token; + this._lt.push(token); + + //save space that will be moved (must be done before array is truncated) + this._ltIndexCache.push(this._lt.length - this._ltIndex + i); + + //keep the buffer under 5 items + if (this._lt.length > 5){ + this._lt.shift(); + } + + //also keep the shift buffer under 5 items + if (this._ltIndexCache.length > 5){ + this._ltIndexCache.shift(); + } + + //update lookahead index + this._ltIndex = this._lt.length; + } + + /* + * Skip to the next token if: + * 1. The token type is marked as hidden. + * 2. The token type has a channel specified and it isn't the current channel. + */ + info = tokenInfo[token.type]; + if (info && + (info.hide || + (info.channel !== undefined && channel !== info.channel))){ + return this.get(channel); + } else { + //return just the type + return token.type; + } + }, + + /** + * Looks ahead a certain number of tokens and returns the token type at + * that position. This will throw an error if you lookahead past the + * end of input, past the size of the lookahead buffer, or back past + * the first token in the lookahead buffer. + * @param {int} The index of the token type to retrieve. 0 for the + * current token, 1 for the next, -1 for the previous, etc. + * @return {int} The token type of the token in the given position. + * @method LA + */ + LA: function(index){ + var total = index, + tt; + if (index > 0){ + //TODO: Store 5 somewhere + if (index > 5){ + throw new Error("Too much lookahead."); + } + + //get all those tokens + while(total){ + tt = this.get(); + total--; + } + + //unget all those tokens + while(total < index){ + this.unget(); + total++; + } + } else if (index < 0){ + + if(this._lt[this._ltIndex+index]){ + tt = this._lt[this._ltIndex+index].type; + } else { + throw new Error("Too much lookbehind."); + } + + } else { + tt = this._token.type; + } + + return tt; + + }, + + /** + * Looks ahead a certain number of tokens and returns the token at + * that position. This will throw an error if you lookahead past the + * end of input, past the size of the lookahead buffer, or back past + * the first token in the lookahead buffer. + * @param {int} The index of the token type to retrieve. 0 for the + * current token, 1 for the next, -1 for the previous, etc. + * @return {Object} The token of the token in the given position. + * @method LA + */ + LT: function(index){ + + //lookahead first to prime the token buffer + this.LA(index); + + //now find the token, subtract one because _ltIndex is already at the next index + return this._lt[this._ltIndex+index-1]; + }, + + /** + * Returns the token type for the next token in the stream without + * consuming it. + * @return {int} The token type of the next token in the stream. + * @method peek + */ + peek: function(){ + return this.LA(1); + }, + + /** + * Returns the actual token object for the last consumed token. + * @return {Token} The token object for the last consumed token. + * @method token + */ + token: function(){ + return this._token; + }, + + /** + * Returns the name of the token for the given token type. + * @param {int} tokenType The type of token to get the name of. + * @return {String} The name of the token or "UNKNOWN_TOKEN" for any + * invalid token type. + * @method tokenName + */ + tokenName: function(tokenType){ + if (tokenType < 0 || tokenType > this._tokenData.length){ + return "UNKNOWN_TOKEN"; + } else { + return this._tokenData[tokenType].name; + } + }, + + /** + * Returns the token type value for the given token name. + * @param {String} tokenName The name of the token whose value should be returned. + * @return {int} The token type value for the given token name or -1 + * for an unknown token. + * @method tokenName + */ + tokenType: function(tokenName){ + return this._tokenData[tokenName] || -1; + }, + + /** + * Returns the last consumed token to the token stream. + * @method unget + */ + unget: function(){ + //if (this._ltIndex > -1){ + if (this._ltIndexCache.length){ + this._ltIndex -= this._ltIndexCache.pop();//--; + this._token = this._lt[this._ltIndex - 1]; + } else { + throw new Error("Too much lookahead."); + } + } + +}; + + + + +parserlib.util = { +StringReader: StringReader, +SyntaxError : SyntaxError, +SyntaxUnit : SyntaxUnit, +EventTarget : EventTarget, +TokenStreamBase : TokenStreamBase +}; +})(); +/* Version v0.1.6, Build time: 2-March-2012 02:44:32 */ +(function(){ +var EventTarget = parserlib.util.EventTarget, +TokenStreamBase = parserlib.util.TokenStreamBase, +StringReader = parserlib.util.StringReader, +SyntaxError = parserlib.util.SyntaxError, +SyntaxUnit = parserlib.util.SyntaxUnit; + + +var Colors = { + aliceblue :"#f0f8ff", + antiquewhite :"#faebd7", + aqua :"#00ffff", + aquamarine :"#7fffd4", + azure :"#f0ffff", + beige :"#f5f5dc", + bisque :"#ffe4c4", + black :"#000000", + blanchedalmond :"#ffebcd", + blue :"#0000ff", + blueviolet :"#8a2be2", + brown :"#a52a2a", + burlywood :"#deb887", + cadetblue :"#5f9ea0", + chartreuse :"#7fff00", + chocolate :"#d2691e", + coral :"#ff7f50", + cornflowerblue :"#6495ed", + cornsilk :"#fff8dc", + crimson :"#dc143c", + cyan :"#00ffff", + darkblue :"#00008b", + darkcyan :"#008b8b", + darkgoldenrod :"#b8860b", + darkgray :"#a9a9a9", + darkgreen :"#006400", + darkkhaki :"#bdb76b", + darkmagenta :"#8b008b", + darkolivegreen :"#556b2f", + darkorange :"#ff8c00", + darkorchid :"#9932cc", + darkred :"#8b0000", + darksalmon :"#e9967a", + darkseagreen :"#8fbc8f", + darkslateblue :"#483d8b", + darkslategray :"#2f4f4f", + darkturquoise :"#00ced1", + darkviolet :"#9400d3", + deeppink :"#ff1493", + deepskyblue :"#00bfff", + dimgray :"#696969", + dodgerblue :"#1e90ff", + firebrick :"#b22222", + floralwhite :"#fffaf0", + forestgreen :"#228b22", + fuchsia :"#ff00ff", + gainsboro :"#dcdcdc", + ghostwhite :"#f8f8ff", + gold :"#ffd700", + goldenrod :"#daa520", + gray :"#808080", + green :"#008000", + greenyellow :"#adff2f", + honeydew :"#f0fff0", + hotpink :"#ff69b4", + indianred :"#cd5c5c", + indigo :"#4b0082", + ivory :"#fffff0", + khaki :"#f0e68c", + lavender :"#e6e6fa", + lavenderblush :"#fff0f5", + lawngreen :"#7cfc00", + lemonchiffon :"#fffacd", + lightblue :"#add8e6", + lightcoral :"#f08080", + lightcyan :"#e0ffff", + lightgoldenrodyellow :"#fafad2", + lightgray :"#d3d3d3", + lightgreen :"#90ee90", + lightpink :"#ffb6c1", + lightsalmon :"#ffa07a", + lightseagreen :"#20b2aa", + lightskyblue :"#87cefa", + lightslategray :"#778899", + lightsteelblue :"#b0c4de", + lightyellow :"#ffffe0", + lime :"#00ff00", + limegreen :"#32cd32", + linen :"#faf0e6", + magenta :"#ff00ff", + maroon :"#800000", + mediumaquamarine:"#66cdaa", + mediumblue :"#0000cd", + mediumorchid :"#ba55d3", + mediumpurple :"#9370d8", + mediumseagreen :"#3cb371", + mediumslateblue :"#7b68ee", + mediumspringgreen :"#00fa9a", + mediumturquoise :"#48d1cc", + mediumvioletred :"#c71585", + midnightblue :"#191970", + mintcream :"#f5fffa", + mistyrose :"#ffe4e1", + moccasin :"#ffe4b5", + navajowhite :"#ffdead", + navy :"#000080", + oldlace :"#fdf5e6", + olive :"#808000", + olivedrab :"#6b8e23", + orange :"#ffa500", + orangered :"#ff4500", + orchid :"#da70d6", + palegoldenrod :"#eee8aa", + palegreen :"#98fb98", + paleturquoise :"#afeeee", + palevioletred :"#d87093", + papayawhip :"#ffefd5", + peachpuff :"#ffdab9", + peru :"#cd853f", + pink :"#ffc0cb", + plum :"#dda0dd", + powderblue :"#b0e0e6", + purple :"#800080", + red :"#ff0000", + rosybrown :"#bc8f8f", + royalblue :"#4169e1", + saddlebrown :"#8b4513", + salmon :"#fa8072", + sandybrown :"#f4a460", + seagreen :"#2e8b57", + seashell :"#fff5ee", + sienna :"#a0522d", + silver :"#c0c0c0", + skyblue :"#87ceeb", + slateblue :"#6a5acd", + slategray :"#708090", + snow :"#fffafa", + springgreen :"#00ff7f", + steelblue :"#4682b4", + tan :"#d2b48c", + teal :"#008080", + thistle :"#d8bfd8", + tomato :"#ff6347", + turquoise :"#40e0d0", + violet :"#ee82ee", + wheat :"#f5deb3", + white :"#ffffff", + whitesmoke :"#f5f5f5", + yellow :"#ffff00", + yellowgreen :"#9acd32" +}; +/** + * Represents a selector combinator (whitespace, +, >). + * @namespace parserlib.css + * @class Combinator + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} text The text representation of the unit. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function Combinator(text, line, col){ + + SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE); + this.type = "unknown"; + + //pretty simple + if (/^\s+$/.test(text)){ + this.type = "descendant"; + } else if (text == ">"){ + this.type = "child"; + } else if (text == "+"){ + this.type = "adjacent-sibling"; + } else if (text == "~"){ + this.type = "sibling"; + } + +} + +Combinator.prototype = new SyntaxUnit(); +Combinator.prototype.constructor = Combinator; +/** + * Represents a media feature, such as max-width:500. + * @namespace parserlib.css + * @class MediaFeature + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {SyntaxUnit} name The name of the feature. + * @param {SyntaxUnit} value The value of the feature or null if none. + */ +function MediaFeature(name, value){ + + SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol, Parser.MEDIA_FEATURE_TYPE); + this.name = name; + this.value = value; +} + +MediaFeature.prototype = new SyntaxUnit(); +MediaFeature.prototype.constructor = MediaFeature; +/** + * Represents an individual media query. + * @namespace parserlib.css + * @class MediaQuery + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} modifier The modifier "not" or "only" (or null). + * @param {String} mediaType The type of media (i.e., "print"). + * @param {Array} parts Array of selectors parts making up this selector. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function MediaQuery(modifier, mediaType, features, line, col){ + + SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType + " " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE); + this.modifier = modifier; + this.mediaType = mediaType; + this.features = features; + +} + +MediaQuery.prototype = new SyntaxUnit(); +MediaQuery.prototype.constructor = MediaQuery; + +/** + * A CSS3 parser. + * @namespace parserlib.css + * @class Parser + * @constructor + * @param {Object} options (Optional) Various options for the parser: + * starHack (true|false) to allow IE6 star hack as valid, + * underscoreHack (true|false) to interpret leading underscores + * as IE6-7 targeting for known properties, ieFilters (true|false) + * to indicate that IE < 8 filters should be accepted and not throw + * syntax errors. + */ +function Parser(options){ + + //inherit event functionality + EventTarget.call(this); + + + this.options = options || {}; + + this._tokenStream = null; +} + +//Static constants +Parser.DEFAULT_TYPE = 0; +Parser.COMBINATOR_TYPE = 1; +Parser.MEDIA_FEATURE_TYPE = 2; +Parser.MEDIA_QUERY_TYPE = 3; +Parser.PROPERTY_NAME_TYPE = 4; +Parser.PROPERTY_VALUE_TYPE = 5; +Parser.PROPERTY_VALUE_PART_TYPE = 6; +Parser.SELECTOR_TYPE = 7; +Parser.SELECTOR_PART_TYPE = 8; +Parser.SELECTOR_SUB_PART_TYPE = 9; + +Parser.prototype = function(){ + + var proto = new EventTarget(), //new prototype + prop, + additions = { + + //restore constructor + constructor: Parser, + + //instance constants - yuck + DEFAULT_TYPE : 0, + COMBINATOR_TYPE : 1, + MEDIA_FEATURE_TYPE : 2, + MEDIA_QUERY_TYPE : 3, + PROPERTY_NAME_TYPE : 4, + PROPERTY_VALUE_TYPE : 5, + PROPERTY_VALUE_PART_TYPE : 6, + SELECTOR_TYPE : 7, + SELECTOR_PART_TYPE : 8, + SELECTOR_SUB_PART_TYPE : 9, + + //----------------------------------------------------------------- + // Grammar + //----------------------------------------------------------------- + + _stylesheet: function(){ + + /* + * stylesheet + * : [ CHARSET_SYM S* STRING S* ';' ]? + * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* + * [ namespace [S|CDO|CDC]* ]* + * [ [ ruleset | media | page | font_face | keyframes ] [S|CDO|CDC]* ]* + * ; + */ + + var tokenStream = this._tokenStream, + charset = null, + count, + token, + tt; + + this.fire("startstylesheet"); + + //try to read character set + this._charset(); + + this._skipCruft(); + + //try to read imports - may be more than one + while (tokenStream.peek() == Tokens.IMPORT_SYM){ + this._import(); + this._skipCruft(); + } + + //try to read namespaces - may be more than one + while (tokenStream.peek() == Tokens.NAMESPACE_SYM){ + this._namespace(); + this._skipCruft(); + } + + //get the next token + tt = tokenStream.peek(); + + //try to read the rest + while(tt > Tokens.EOF){ + + try { + + switch(tt){ + case Tokens.MEDIA_SYM: + this._media(); + this._skipCruft(); + break; + case Tokens.PAGE_SYM: + this._page(); + this._skipCruft(); + break; + case Tokens.FONT_FACE_SYM: + this._font_face(); + this._skipCruft(); + break; + case Tokens.KEYFRAMES_SYM: + this._keyframes(); + this._skipCruft(); + break; + case Tokens.UNKNOWN_SYM: //unknown @ rule + tokenStream.get(); + if (!this.options.strict){ + + //fire error event + this.fire({ + type: "error", + error: null, + message: "Unknown @ rule: " + tokenStream.LT(0).value + ".", + line: tokenStream.LT(0).startLine, + col: tokenStream.LT(0).startCol + }); + + //skip braces + count=0; + while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) == Tokens.LBRACE){ + count++; //keep track of nesting depth + } + + while(count){ + tokenStream.advance([Tokens.RBRACE]); + count--; + } + + } else { + //not a syntax error, rethrow it + throw new SyntaxError("Unknown @ rule.", tokenStream.LT(0).startLine, tokenStream.LT(0).startCol); + } + break; + case Tokens.S: + this._readWhitespace(); + break; + default: + if(!this._ruleset()){ + + //error handling for known issues + switch(tt){ + case Tokens.CHARSET_SYM: + token = tokenStream.LT(1); + this._charset(false); + throw new SyntaxError("@charset not allowed here.", token.startLine, token.startCol); + case Tokens.IMPORT_SYM: + token = tokenStream.LT(1); + this._import(false); + throw new SyntaxError("@import not allowed here.", token.startLine, token.startCol); + case Tokens.NAMESPACE_SYM: + token = tokenStream.LT(1); + this._namespace(false); + throw new SyntaxError("@namespace not allowed here.", token.startLine, token.startCol); + default: + tokenStream.get(); //get the last token + this._unexpectedToken(tokenStream.token()); + } + + } + } + } catch(ex) { + if (ex instanceof SyntaxError && !this.options.strict){ + this.fire({ + type: "error", + error: ex, + message: ex.message, + line: ex.line, + col: ex.col + }); + } else { + throw ex; + } + } + + tt = tokenStream.peek(); + } + + if (tt != Tokens.EOF){ + this._unexpectedToken(tokenStream.token()); + } + + this.fire("endstylesheet"); + }, + + _charset: function(emit){ + var tokenStream = this._tokenStream, + charset, + token, + line, + col; + + if (tokenStream.match(Tokens.CHARSET_SYM)){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.STRING); + + token = tokenStream.token(); + charset = token.value; + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.SEMICOLON); + + if (emit !== false){ + this.fire({ + type: "charset", + charset:charset, + line: line, + col: col + }); + } + } + }, + + _import: function(emit){ + /* + * import + * : IMPORT_SYM S* + * [STRING|URI] S* media_query_list? ';' S* + */ + + var tokenStream = this._tokenStream, + tt, + uri, + importToken, + mediaList = []; + + //read import symbol + tokenStream.mustMatch(Tokens.IMPORT_SYM); + importToken = tokenStream.token(); + this._readWhitespace(); + + tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); + + //grab the URI value + uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1"); + + this._readWhitespace(); + + mediaList = this._media_query_list(); + + //must end with a semicolon + tokenStream.mustMatch(Tokens.SEMICOLON); + this._readWhitespace(); + + if (emit !== false){ + this.fire({ + type: "import", + uri: uri, + media: mediaList, + line: importToken.startLine, + col: importToken.startCol + }); + } + + }, + + _namespace: function(emit){ + /* + * namespace + * : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* + */ + + var tokenStream = this._tokenStream, + line, + col, + prefix, + uri; + + //read import symbol + tokenStream.mustMatch(Tokens.NAMESPACE_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + this._readWhitespace(); + + //it's a namespace prefix - no _namespace_prefix() method because it's just an IDENT + if (tokenStream.match(Tokens.IDENT)){ + prefix = tokenStream.token().value; + this._readWhitespace(); + } + + tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); + + //grab the URI value + uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1"); + + this._readWhitespace(); + + //must end with a semicolon + tokenStream.mustMatch(Tokens.SEMICOLON); + this._readWhitespace(); + + if (emit !== false){ + this.fire({ + type: "namespace", + prefix: prefix, + uri: uri, + line: line, + col: col + }); + } + + }, + + _media: function(){ + /* + * media + * : MEDIA_SYM S* media_query_list S* '{' S* ruleset* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col, + mediaList;// = []; + + //look for @media + tokenStream.mustMatch(Tokens.MEDIA_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this._readWhitespace(); + + mediaList = this._media_query_list(); + + tokenStream.mustMatch(Tokens.LBRACE); + this._readWhitespace(); + + this.fire({ + type: "startmedia", + media: mediaList, + line: line, + col: col + }); + + while(true) { + if (tokenStream.peek() == Tokens.PAGE_SYM){ + this._page(); + } else if (!this._ruleset()){ + break; + } + } + + tokenStream.mustMatch(Tokens.RBRACE); + this._readWhitespace(); + + this.fire({ + type: "endmedia", + media: mediaList, + line: line, + col: col + }); + }, + + + //CSS3 Media Queries + _media_query_list: function(){ + /* + * media_query_list + * : S* [media_query [ ',' S* media_query ]* ]? + * ; + */ + var tokenStream = this._tokenStream, + mediaList = []; + + + this._readWhitespace(); + + if (tokenStream.peek() == Tokens.IDENT || tokenStream.peek() == Tokens.LPAREN){ + mediaList.push(this._media_query()); + } + + while(tokenStream.match(Tokens.COMMA)){ + this._readWhitespace(); + mediaList.push(this._media_query()); + } + + return mediaList; + }, + + /* + * Note: "expression" in the grammar maps to the _media_expression + * method. + + */ + _media_query: function(){ + /* + * media_query + * : [ONLY | NOT]? S* media_type S* [ AND S* expression ]* + * | expression [ AND S* expression ]* + * ; + */ + var tokenStream = this._tokenStream, + type = null, + ident = null, + token = null, + expressions = []; + + if (tokenStream.match(Tokens.IDENT)){ + ident = tokenStream.token().value.toLowerCase(); + + //since there's no custom tokens for these, need to manually check + if (ident != "only" && ident != "not"){ + tokenStream.unget(); + ident = null; + } else { + token = tokenStream.token(); + } + } + + this._readWhitespace(); + + if (tokenStream.peek() == Tokens.IDENT){ + type = this._media_type(); + if (token === null){ + token = tokenStream.token(); + } + } else if (tokenStream.peek() == Tokens.LPAREN){ + if (token === null){ + token = tokenStream.LT(1); + } + expressions.push(this._media_expression()); + } + + if (type === null && expressions.length === 0){ + return null; + } else { + this._readWhitespace(); + while (tokenStream.match(Tokens.IDENT)){ + if (tokenStream.token().value.toLowerCase() != "and"){ + this._unexpectedToken(tokenStream.token()); + } + + this._readWhitespace(); + expressions.push(this._media_expression()); + } + } + + return new MediaQuery(ident, type, expressions, token.startLine, token.startCol); + }, + + //CSS3 Media Queries + _media_type: function(){ + /* + * media_type + * : IDENT + * ; + */ + return this._media_feature(); + }, + + /** + * Note: in CSS3 Media Queries, this is called "expression". + * Renamed here to avoid conflict with CSS3 Selectors + * definition of "expression". Also note that "expr" in the + * grammar now maps to "expression" from CSS3 selectors. + * @method _media_expression + * @private + */ + _media_expression: function(){ + /* + * expression + * : '(' S* media_feature S* [ ':' S* expr ]? ')' S* + * ; + */ + var tokenStream = this._tokenStream, + feature = null, + token, + expression = null; + + tokenStream.mustMatch(Tokens.LPAREN); + + feature = this._media_feature(); + this._readWhitespace(); + + if (tokenStream.match(Tokens.COLON)){ + this._readWhitespace(); + token = tokenStream.LT(1); + expression = this._expression(); + } + + tokenStream.mustMatch(Tokens.RPAREN); + this._readWhitespace(); + + return new MediaFeature(feature, (expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null)); + }, + + //CSS3 Media Queries + _media_feature: function(){ + /* + * media_feature + * : IDENT + * ; + */ + var tokenStream = this._tokenStream; + + tokenStream.mustMatch(Tokens.IDENT); + + return SyntaxUnit.fromToken(tokenStream.token()); + }, + + //CSS3 Paged Media + _page: function(){ + /* + * page: + * PAGE_SYM S* IDENT? pseudo_page? S* + * '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col, + identifier = null, + pseudoPage = null; + + //look for @page + tokenStream.mustMatch(Tokens.PAGE_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this._readWhitespace(); + + if (tokenStream.match(Tokens.IDENT)){ + identifier = tokenStream.token().value; + + //The value 'auto' may not be used as a page name and MUST be treated as a syntax error. + if (identifier.toLowerCase() === "auto"){ + this._unexpectedToken(tokenStream.token()); + } + } + + //see if there's a colon upcoming + if (tokenStream.peek() == Tokens.COLON){ + pseudoPage = this._pseudo_page(); + } + + this._readWhitespace(); + + this.fire({ + type: "startpage", + id: identifier, + pseudo: pseudoPage, + line: line, + col: col + }); + + this._readDeclarations(true, true); + + this.fire({ + type: "endpage", + id: identifier, + pseudo: pseudoPage, + line: line, + col: col + }); + + }, + + //CSS3 Paged Media + _margin: function(){ + /* + * margin : + * margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col, + marginSym = this._margin_sym(); + + if (marginSym){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this.fire({ + type: "startpagemargin", + margin: marginSym, + line: line, + col: col + }); + + this._readDeclarations(true); + + this.fire({ + type: "endpagemargin", + margin: marginSym, + line: line, + col: col + }); + return true; + } else { + return false; + } + }, + + //CSS3 Paged Media + _margin_sym: function(){ + + /* + * margin_sym : + * TOPLEFTCORNER_SYM | + * TOPLEFT_SYM | + * TOPCENTER_SYM | + * TOPRIGHT_SYM | + * TOPRIGHTCORNER_SYM | + * BOTTOMLEFTCORNER_SYM | + * BOTTOMLEFT_SYM | + * BOTTOMCENTER_SYM | + * BOTTOMRIGHT_SYM | + * BOTTOMRIGHTCORNER_SYM | + * LEFTTOP_SYM | + * LEFTMIDDLE_SYM | + * LEFTBOTTOM_SYM | + * RIGHTTOP_SYM | + * RIGHTMIDDLE_SYM | + * RIGHTBOTTOM_SYM + * ; + */ + + var tokenStream = this._tokenStream; + + if(tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM, + Tokens.TOPCENTER_SYM, Tokens.TOPRIGHT_SYM, Tokens.TOPRIGHTCORNER_SYM, + Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM, + Tokens.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_SYM, + Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM, + Tokens.LEFTMIDDLE_SYM, Tokens.LEFTBOTTOM_SYM, Tokens.RIGHTTOP_SYM, + Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM])) + { + return SyntaxUnit.fromToken(tokenStream.token()); + } else { + return null; + } + + }, + + _pseudo_page: function(){ + /* + * pseudo_page + * : ':' IDENT + * ; + */ + + var tokenStream = this._tokenStream; + + tokenStream.mustMatch(Tokens.COLON); + tokenStream.mustMatch(Tokens.IDENT); + + //TODO: CSS3 Paged Media says only "left", "center", and "right" are allowed + + return tokenStream.token().value; + }, + + _font_face: function(){ + /* + * font_face + * : FONT_FACE_SYM S* + * '{' S* declaration [ ';' S* declaration ]* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col; + + //look for @page + tokenStream.mustMatch(Tokens.FONT_FACE_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this._readWhitespace(); + + this.fire({ + type: "startfontface", + line: line, + col: col + }); + + this._readDeclarations(true); + + this.fire({ + type: "endfontface", + line: line, + col: col + }); + }, + + _operator: function(){ + + /* + * operator + * : '/' S* | ',' S* | /( empty )/ + * ; + */ + + var tokenStream = this._tokenStream, + token = null; + + if (tokenStream.match([Tokens.SLASH, Tokens.COMMA])){ + token = tokenStream.token(); + this._readWhitespace(); + } + return token ? PropertyValuePart.fromToken(token) : null; + + }, + + _combinator: function(){ + + /* + * combinator + * : PLUS S* | GREATER S* | TILDE S* | S+ + * ; + */ + + var tokenStream = this._tokenStream, + value = null, + token; + + if(tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])){ + token = tokenStream.token(); + value = new Combinator(token.value, token.startLine, token.startCol); + this._readWhitespace(); + } + + return value; + }, + + _unary_operator: function(){ + + /* + * unary_operator + * : '-' | '+' + * ; + */ + + var tokenStream = this._tokenStream; + + if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])){ + return tokenStream.token().value; + } else { + return null; + } + }, + + _property: function(){ + + /* + * property + * : IDENT S* + * ; + */ + + var tokenStream = this._tokenStream, + value = null, + hack = null, + tokenValue, + token, + line, + col; + + //check for star hack - throws error if not allowed + if (tokenStream.peek() == Tokens.STAR && this.options.starHack){ + tokenStream.get(); + token = tokenStream.token(); + hack = token.value; + line = token.startLine; + col = token.startCol; + } + + if(tokenStream.match(Tokens.IDENT)){ + token = tokenStream.token(); + tokenValue = token.value; + + //check for underscore hack - no error if not allowed because it's valid CSS syntax + if (tokenValue.charAt(0) == "_" && this.options.underscoreHack){ + hack = "_"; + tokenValue = tokenValue.substring(1); + } + + value = new PropertyName(tokenValue, hack, (line||token.startLine), (col||token.startCol)); + this._readWhitespace(); + } + + return value; + }, + + //Augmented with CSS3 Selectors + _ruleset: function(){ + /* + * ruleset + * : selectors_group + * '{' S* declaration? [ ';' S* declaration? ]* '}' S* + * ; + */ + + var tokenStream = this._tokenStream, + tt, + selectors; + try { + selectors = this._selectors_group(); + } catch (ex){ + if (ex instanceof SyntaxError && !this.options.strict){ + + //fire error event + this.fire({ + type: "error", + error: ex, + message: ex.message, + line: ex.line, + col: ex.col + }); + + //skip over everything until closing brace + tt = tokenStream.advance([Tokens.RBRACE]); + if (tt == Tokens.RBRACE){ + //if there's a right brace, the rule is finished so don't do anything + } else { + //otherwise, rethrow the error because it wasn't handled properly + throw ex; + } + + } else { + //not a syntax error, rethrow it + throw ex; + } + + //trigger parser to continue + return true; + } + + //if it got here, all selectors parsed + if (selectors){ + + this.fire({ + type: "startrule", + selectors: selectors, + line: selectors[0].line, + col: selectors[0].col + }); + + this._readDeclarations(true); + + this.fire({ + type: "endrule", + selectors: selectors, + line: selectors[0].line, + col: selectors[0].col + }); + + } + + return selectors; + + }, + + //CSS3 Selectors + _selectors_group: function(){ + + /* + * selectors_group + * : selector [ COMMA S* selector ]* + * ; + */ + var tokenStream = this._tokenStream, + selectors = [], + selector; + + selector = this._selector(); + if (selector !== null){ + + selectors.push(selector); + while(tokenStream.match(Tokens.COMMA)){ + this._readWhitespace(); + selector = this._selector(); + if (selector !== null){ + selectors.push(selector); + } else { + this._unexpectedToken(tokenStream.LT(1)); + } + } + } + + return selectors.length ? selectors : null; + }, + + //CSS3 Selectors + _selector: function(){ + /* + * selector + * : simple_selector_sequence [ combinator simple_selector_sequence ]* + * ; + */ + + var tokenStream = this._tokenStream, + selector = [], + nextSelector = null, + combinator = null, + ws = null; + + //if there's no simple selector, then there's no selector + nextSelector = this._simple_selector_sequence(); + if (nextSelector === null){ + return null; + } + + selector.push(nextSelector); + + do { + + //look for a combinator + combinator = this._combinator(); + + if (combinator !== null){ + selector.push(combinator); + nextSelector = this._simple_selector_sequence(); + + //there must be a next selector + if (nextSelector === null){ + this._unexpectedToken(this.LT(1)); + } else { + + //nextSelector is an instance of SelectorPart + selector.push(nextSelector); + } + } else { + + //if there's not whitespace, we're done + if (this._readWhitespace()){ + + //add whitespace separator + ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol); + + //combinator is not required + combinator = this._combinator(); + + //selector is required if there's a combinator + nextSelector = this._simple_selector_sequence(); + if (nextSelector === null){ + if (combinator !== null){ + this._unexpectedToken(tokenStream.LT(1)); + } + } else { + + if (combinator !== null){ + selector.push(combinator); + } else { + selector.push(ws); + } + + selector.push(nextSelector); + } + } else { + break; + } + + } + } while(true); + + return new Selector(selector, selector[0].line, selector[0].col); + }, + + //CSS3 Selectors + _simple_selector_sequence: function(){ + /* + * simple_selector_sequence + * : [ type_selector | universal ] + * [ HASH | class | attrib | pseudo | negation ]* + * | [ HASH | class | attrib | pseudo | negation ]+ + * ; + */ + + var tokenStream = this._tokenStream, + + //parts of a simple selector + elementName = null, + modifiers = [], + + //complete selector text + selectorText= "", + + //the different parts after the element name to search for + components = [ + //HASH + function(){ + return tokenStream.match(Tokens.HASH) ? + new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : + null; + }, + this._class, + this._attrib, + this._pseudo, + this._negation + ], + i = 0, + len = components.length, + component = null, + found = false, + line, + col; + + + //get starting line and column for the selector + line = tokenStream.LT(1).startLine; + col = tokenStream.LT(1).startCol; + + elementName = this._type_selector(); + if (!elementName){ + elementName = this._universal(); + } + + if (elementName !== null){ + selectorText += elementName; + } + + while(true){ + + //whitespace means we're done + if (tokenStream.peek() === Tokens.S){ + break; + } + + //check for each component + while(i < len && component === null){ + component = components[i++].call(this); + } + + if (component === null){ + + //we don't have a selector + if (selectorText === ""){ + return null; + } else { + break; + } + } else { + i = 0; + modifiers.push(component); + selectorText += component.toString(); + component = null; + } + } + + + return selectorText !== "" ? + new SelectorPart(elementName, modifiers, selectorText, line, col) : + null; + }, + + //CSS3 Selectors + _type_selector: function(){ + /* + * type_selector + * : [ namespace_prefix ]? element_name + * ; + */ + + var tokenStream = this._tokenStream, + ns = this._namespace_prefix(), + elementName = this._element_name(); + + if (!elementName){ + /* + * Need to back out the namespace that was read due to both + * type_selector and universal reading namespace_prefix + * first. Kind of hacky, but only way I can figure out + * right now how to not change the grammar. + */ + if (ns){ + tokenStream.unget(); + if (ns.length > 1){ + tokenStream.unget(); + } + } + + return null; + } else { + if (ns){ + elementName.text = ns + elementName.text; + elementName.col -= ns.length; + } + return elementName; + } + }, + + //CSS3 Selectors + _class: function(){ + /* + * class + * : '.' IDENT + * ; + */ + + var tokenStream = this._tokenStream, + token; + + if (tokenStream.match(Tokens.DOT)){ + tokenStream.mustMatch(Tokens.IDENT); + token = tokenStream.token(); + return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1); + } else { + return null; + } + + }, + + //CSS3 Selectors + _element_name: function(){ + /* + * element_name + * : IDENT + * ; + */ + + var tokenStream = this._tokenStream, + token; + + if (tokenStream.match(Tokens.IDENT)){ + token = tokenStream.token(); + return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol); + + } else { + return null; + } + }, + + //CSS3 Selectors + _namespace_prefix: function(){ + /* + * namespace_prefix + * : [ IDENT | '*' ]? '|' + * ; + */ + var tokenStream = this._tokenStream, + value = ""; + + //verify that this is a namespace prefix + if (tokenStream.LA(1) === Tokens.PIPE || tokenStream.LA(2) === Tokens.PIPE){ + + if(tokenStream.match([Tokens.IDENT, Tokens.STAR])){ + value += tokenStream.token().value; + } + + tokenStream.mustMatch(Tokens.PIPE); + value += "|"; + + } + + return value.length ? value : null; + }, + + //CSS3 Selectors + _universal: function(){ + /* + * universal + * : [ namespace_prefix ]? '*' + * ; + */ + var tokenStream = this._tokenStream, + value = "", + ns; + + ns = this._namespace_prefix(); + if(ns){ + value += ns; + } + + if(tokenStream.match(Tokens.STAR)){ + value += "*"; + } + + return value.length ? value : null; + + }, + + //CSS3 Selectors + _attrib: function(){ + /* + * attrib + * : '[' S* [ namespace_prefix ]? IDENT S* + * [ [ PREFIXMATCH | + * SUFFIXMATCH | + * SUBSTRINGMATCH | + * '=' | + * INCLUDES | + * DASHMATCH ] S* [ IDENT | STRING ] S* + * ]? ']' + * ; + */ + + var tokenStream = this._tokenStream, + value = null, + ns, + token; + + if (tokenStream.match(Tokens.LBRACKET)){ + token = tokenStream.token(); + value = token.value; + value += this._readWhitespace(); + + ns = this._namespace_prefix(); + + if (ns){ + value += ns; + } + + tokenStream.mustMatch(Tokens.IDENT); + value += tokenStream.token().value; + value += this._readWhitespace(); + + if(tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH, + Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])){ + + value += tokenStream.token().value; + value += this._readWhitespace(); + + tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); + value += tokenStream.token().value; + value += this._readWhitespace(); + } + + tokenStream.mustMatch(Tokens.RBRACKET); + + return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol); + } else { + return null; + } + }, + + //CSS3 Selectors + _pseudo: function(){ + + /* + * pseudo + * : ':' ':'? [ IDENT | functional_pseudo ] + * ; + */ + + var tokenStream = this._tokenStream, + pseudo = null, + colons = ":", + line, + col; + + if (tokenStream.match(Tokens.COLON)){ + + if (tokenStream.match(Tokens.COLON)){ + colons += ":"; + } + + if (tokenStream.match(Tokens.IDENT)){ + pseudo = tokenStream.token().value; + line = tokenStream.token().startLine; + col = tokenStream.token().startCol - colons.length; + } else if (tokenStream.peek() == Tokens.FUNCTION){ + line = tokenStream.LT(1).startLine; + col = tokenStream.LT(1).startCol - colons.length; + pseudo = this._functional_pseudo(); + } + + if (pseudo){ + pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col); + } + } + + return pseudo; + }, + + //CSS3 Selectors + _functional_pseudo: function(){ + /* + * functional_pseudo + * : FUNCTION S* expression ')' + * ; + */ + + var tokenStream = this._tokenStream, + value = null; + + if(tokenStream.match(Tokens.FUNCTION)){ + value = tokenStream.token().value; + value += this._readWhitespace(); + value += this._expression(); + tokenStream.mustMatch(Tokens.RPAREN); + value += ")"; + } + + return value; + }, + + //CSS3 Selectors + _expression: function(){ + /* + * expression + * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ + * ; + */ + + var tokenStream = this._tokenStream, + value = ""; + + while(tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION, + Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH, + Tokens.FREQ, Tokens.ANGLE, Tokens.TIME, + Tokens.RESOLUTION])){ + + value += tokenStream.token().value; + value += this._readWhitespace(); + } + + return value.length ? value : null; + + }, + + //CSS3 Selectors + _negation: function(){ + /* + * negation + * : NOT S* negation_arg S* ')' + * ; + */ + + var tokenStream = this._tokenStream, + line, + col, + value = "", + arg, + subpart = null; + + if (tokenStream.match(Tokens.NOT)){ + value = tokenStream.token().value; + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + value += this._readWhitespace(); + arg = this._negation_arg(); + value += arg; + value += this._readWhitespace(); + tokenStream.match(Tokens.RPAREN); + value += tokenStream.token().value; + + subpart = new SelectorSubPart(value, "not", line, col); + subpart.args.push(arg); + } + + return subpart; + }, + + //CSS3 Selectors + _negation_arg: function(){ + /* + * negation_arg + * : type_selector | universal | HASH | class | attrib | pseudo + * ; + */ + + var tokenStream = this._tokenStream, + args = [ + this._type_selector, + this._universal, + function(){ + return tokenStream.match(Tokens.HASH) ? + new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : + null; + }, + this._class, + this._attrib, + this._pseudo + ], + arg = null, + i = 0, + len = args.length, + elementName, + line, + col, + part; + + line = tokenStream.LT(1).startLine; + col = tokenStream.LT(1).startCol; + + while(i < len && arg === null){ + + arg = args[i].call(this); + i++; + } + + //must be a negation arg + if (arg === null){ + this._unexpectedToken(tokenStream.LT(1)); + } + + //it's an element name + if (arg.type == "elementName"){ + part = new SelectorPart(arg, [], arg.toString(), line, col); + } else { + part = new SelectorPart(null, [arg], arg.toString(), line, col); + } + + return part; + }, + + _declaration: function(){ + + /* + * declaration + * : property ':' S* expr prio? + * | /( empty )/ + * ; + */ + + var tokenStream = this._tokenStream, + property = null, + expr = null, + prio = null, + error = null, + invalid = null; + + property = this._property(); + if (property !== null){ + + tokenStream.mustMatch(Tokens.COLON); + this._readWhitespace(); + + expr = this._expr(); + + //if there's no parts for the value, it's an error + if (!expr || expr.length === 0){ + this._unexpectedToken(tokenStream.LT(1)); + } + + prio = this._prio(); + + try { + this._validateProperty(property, expr); + } catch (ex) { + invalid = ex; + } + + this.fire({ + type: "property", + property: property, + value: expr, + important: prio, + line: property.line, + col: property.col, + invalid: invalid + }); + + return true; + } else { + return false; + } + }, + + _prio: function(){ + /* + * prio + * : IMPORTANT_SYM S* + * ; + */ + + var tokenStream = this._tokenStream, + result = tokenStream.match(Tokens.IMPORTANT_SYM); + + this._readWhitespace(); + return result; + }, + + _expr: function(){ + /* + * expr + * : term [ operator term ]* + * ; + */ + + var tokenStream = this._tokenStream, + values = [], + //valueParts = [], + value = null, + operator = null; + + value = this._term(); + if (value !== null){ + + values.push(value); + + do { + operator = this._operator(); + + //if there's an operator, keep building up the value parts + if (operator){ + values.push(operator); + } /*else { + //if there's not an operator, you have a full value + values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); + valueParts = []; + }*/ + + value = this._term(); + + if (value === null){ + break; + } else { + values.push(value); + } + } while(true); + } + + //cleanup + /*if (valueParts.length){ + values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); + }*/ + + return values.length > 0 ? new PropertyValue(values, values[0].line, values[0].col) : null; + }, + + _term: function(){ + + /* + * term + * : unary_operator? + * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* | + * TIME S* | FREQ S* | function | ie_function ] + * | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor + * ; + */ + + var tokenStream = this._tokenStream, + unary = null, + value = null, + token, + line, + col; + + //returns the operator or null + unary = this._unary_operator(); + if (unary !== null){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + } + + //exception for IE filters + if (tokenStream.peek() == Tokens.IE_FUNCTION && this.options.ieFilters){ + + value = this._ie_function(); + if (unary === null){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + } + + //see if there's a simple match + } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH, + Tokens.ANGLE, Tokens.TIME, + Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])){ + + value = tokenStream.token().value; + if (unary === null){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + } + this._readWhitespace(); + } else { + + //see if it's a color + token = this._hexcolor(); + if (token === null){ + + //if there's no unary, get the start of the next token for line/col info + if (unary === null){ + line = tokenStream.LT(1).startLine; + col = tokenStream.LT(1).startCol; + } + + //has to be a function + if (value === null){ + + /* + * This checks for alpha(opacity=0) style of IE + * functions. IE_FUNCTION only presents progid: style. + */ + if (tokenStream.LA(3) == Tokens.EQUALS && this.options.ieFilters){ + value = this._ie_function(); + } else { + value = this._function(); + } + } + + /*if (value === null){ + return null; + //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + "."); + }*/ + + } else { + value = token.value; + if (unary === null){ + line = token.startLine; + col = token.startCol; + } + } + + } + + return value !== null ? + new PropertyValuePart(unary !== null ? unary + value : value, line, col) : + null; + + }, + + _function: function(){ + + /* + * function + * : FUNCTION S* expr ')' S* + * ; + */ + + var tokenStream = this._tokenStream, + functionText = null, + expr = null, + lt; + + if (tokenStream.match(Tokens.FUNCTION)){ + functionText = tokenStream.token().value; + this._readWhitespace(); + expr = this._expr(); + functionText += expr; + + //START: Horrible hack in case it's an IE filter + if (this.options.ieFilters && tokenStream.peek() == Tokens.EQUALS){ + do { + + if (this._readWhitespace()){ + functionText += tokenStream.token().value; + } + + //might be second time in the loop + if (tokenStream.LA(0) == Tokens.COMMA){ + functionText += tokenStream.token().value; + } + + tokenStream.match(Tokens.IDENT); + functionText += tokenStream.token().value; + + tokenStream.match(Tokens.EQUALS); + functionText += tokenStream.token().value; + + //functionText += this._term(); + lt = tokenStream.peek(); + while(lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN){ + tokenStream.get(); + functionText += tokenStream.token().value; + lt = tokenStream.peek(); + } + } while(tokenStream.match([Tokens.COMMA, Tokens.S])); + } + + //END: Horrible Hack + + tokenStream.match(Tokens.RPAREN); + functionText += ")"; + this._readWhitespace(); + } + + return functionText; + }, + + _ie_function: function(){ + + /* (My own extension) + * ie_function + * : IE_FUNCTION S* IDENT '=' term [S* ','? IDENT '=' term]+ ')' S* + * ; + */ + + var tokenStream = this._tokenStream, + functionText = null, + expr = null, + lt; + + //IE function can begin like a regular function, too + if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])){ + functionText = tokenStream.token().value; + + do { + + if (this._readWhitespace()){ + functionText += tokenStream.token().value; + } + + //might be second time in the loop + if (tokenStream.LA(0) == Tokens.COMMA){ + functionText += tokenStream.token().value; + } + + tokenStream.match(Tokens.IDENT); + functionText += tokenStream.token().value; + + tokenStream.match(Tokens.EQUALS); + functionText += tokenStream.token().value; + + //functionText += this._term(); + lt = tokenStream.peek(); + while(lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN){ + tokenStream.get(); + functionText += tokenStream.token().value; + lt = tokenStream.peek(); + } + } while(tokenStream.match([Tokens.COMMA, Tokens.S])); + + tokenStream.match(Tokens.RPAREN); + functionText += ")"; + this._readWhitespace(); + } + + return functionText; + }, + + _hexcolor: function(){ + /* + * There is a constraint on the color that it must + * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F]) + * after the "#"; e.g., "#000" is OK, but "#abcd" is not. + * + * hexcolor + * : HASH S* + * ; + */ + + var tokenStream = this._tokenStream, + token = null, + color; + + if(tokenStream.match(Tokens.HASH)){ + + //need to do some validation here + + token = tokenStream.token(); + color = token.value; + if (!/#[a-f0-9]{3,6}/i.test(color)){ + throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); + } + this._readWhitespace(); + } + + return token; + }, + + //----------------------------------------------------------------- + // Animations methods + //----------------------------------------------------------------- + + _keyframes: function(){ + + /* + * keyframes: + * : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' { + * ; + */ + var tokenStream = this._tokenStream, + token, + tt, + name; + + tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); + this._readWhitespace(); + name = this._keyframe_name(); + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.LBRACE); + + this.fire({ + type: "startkeyframes", + name: name, + line: name.line, + col: name.col + }); + + this._readWhitespace(); + tt = tokenStream.peek(); + + //check for key + while(tt == Tokens.IDENT || tt == Tokens.PERCENTAGE) { + this._keyframe_rule(); + this._readWhitespace(); + tt = tokenStream.peek(); + } + + this.fire({ + type: "endkeyframes", + name: name, + line: name.line, + col: name.col + }); + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.RBRACE); + + }, + + _keyframe_name: function(){ + + /* + * keyframe_name: + * : IDENT + * | STRING + * ; + */ + var tokenStream = this._tokenStream, + token; + + tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); + return SyntaxUnit.fromToken(tokenStream.token()); + }, + + _keyframe_rule: function(){ + + /* + * keyframe_rule: + * : key_list S* + * '{' S* declaration [ ';' S* declaration ]* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + token, + keyList = this._key_list(); + + this.fire({ + type: "startkeyframerule", + keys: keyList, + line: keyList[0].line, + col: keyList[0].col + }); + + this._readDeclarations(true); + + this.fire({ + type: "endkeyframerule", + keys: keyList, + line: keyList[0].line, + col: keyList[0].col + }); + + }, + + _key_list: function(){ + + /* + * key_list: + * : key [ S* ',' S* key]* + * ; + */ + var tokenStream = this._tokenStream, + token, + key, + keyList = []; + + //must be least one key + keyList.push(this._key()); + + this._readWhitespace(); + + while(tokenStream.match(Tokens.COMMA)){ + this._readWhitespace(); + keyList.push(this._key()); + this._readWhitespace(); + } + + return keyList; + }, + + _key: function(){ + /* + * There is a restriction that IDENT can be only "from" or "to". + * + * key + * : PERCENTAGE + * | IDENT + * ; + */ + + var tokenStream = this._tokenStream, + token; + + if (tokenStream.match(Tokens.PERCENTAGE)){ + return SyntaxUnit.fromToken(tokenStream.token()); + } else if (tokenStream.match(Tokens.IDENT)){ + token = tokenStream.token(); + + if (/from|to/i.test(token.value)){ + return SyntaxUnit.fromToken(token); + } + + tokenStream.unget(); + } + + //if it gets here, there wasn't a valid token, so time to explode + this._unexpectedToken(tokenStream.LT(1)); + }, + + //----------------------------------------------------------------- + // Helper methods + //----------------------------------------------------------------- + + /** + * Not part of CSS grammar, but useful for skipping over + * combination of white space and HTML-style comments. + * @return {void} + * @method _skipCruft + * @private + */ + _skipCruft: function(){ + while(this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])){ + //noop + } + }, + + /** + * Not part of CSS grammar, but this pattern occurs frequently + * in the official CSS grammar. Split out here to eliminate + * duplicate code. + * @param {Boolean} checkStart Indicates if the rule should check + * for the left brace at the beginning. + * @param {Boolean} readMargins Indicates if the rule should check + * for margin patterns. + * @return {void} + * @method _readDeclarations + * @private + */ + _readDeclarations: function(checkStart, readMargins){ + /* + * Reads the pattern + * S* '{' S* declaration [ ';' S* declaration ]* '}' S* + * or + * S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* + * Note that this is how it is described in CSS3 Paged Media, but is actually incorrect. + * A semicolon is only necessary following a delcaration is there's another declaration + * or margin afterwards. + */ + var tokenStream = this._tokenStream, + tt; + + + this._readWhitespace(); + + if (checkStart){ + tokenStream.mustMatch(Tokens.LBRACE); + } + + this._readWhitespace(); + + try { + + while(true){ + + if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())){ + //noop + } else if (this._declaration()){ + if (!tokenStream.match(Tokens.SEMICOLON)){ + break; + } + } else { + break; + } + + //if ((!this._margin() && !this._declaration()) || !tokenStream.match(Tokens.SEMICOLON)){ + // break; + //} + this._readWhitespace(); + } + + tokenStream.mustMatch(Tokens.RBRACE); + this._readWhitespace(); + + } catch (ex) { + if (ex instanceof SyntaxError && !this.options.strict){ + + //fire error event + this.fire({ + type: "error", + error: ex, + message: ex.message, + line: ex.line, + col: ex.col + }); + + //see if there's another declaration + tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE]); + if (tt == Tokens.SEMICOLON){ + //if there's a semicolon, then there might be another declaration + this._readDeclarations(false, readMargins); + } else if (tt != Tokens.RBRACE){ + //if there's a right brace, the rule is finished so don't do anything + //otherwise, rethrow the error because it wasn't handled properly + throw ex; + } + + } else { + //not a syntax error, rethrow it + throw ex; + } + } + + }, + + /** + * In some cases, you can end up with two white space tokens in a + * row. Instead of making a change in every function that looks for + * white space, this function is used to match as much white space + * as necessary. + * @method _readWhitespace + * @return {String} The white space if found, empty string if not. + * @private + */ + _readWhitespace: function(){ + + var tokenStream = this._tokenStream, + ws = ""; + + while(tokenStream.match(Tokens.S)){ + ws += tokenStream.token().value; + } + + return ws; + }, + + + /** + * Throws an error when an unexpected token is found. + * @param {Object} token The token that was found. + * @method _unexpectedToken + * @return {void} + * @private + */ + _unexpectedToken: function(token){ + throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); + }, + + /** + * Helper method used for parsing subparts of a style sheet. + * @return {void} + * @method _verifyEnd + * @private + */ + _verifyEnd: function(){ + if (this._tokenStream.LA(1) != Tokens.EOF){ + this._unexpectedToken(this._tokenStream.LT(1)); + } + }, + + //----------------------------------------------------------------- + // Validation methods + //----------------------------------------------------------------- + _validateProperty: function(property, value){ + Validation.validate(property, value); + }, + + //----------------------------------------------------------------- + // Parsing methods + //----------------------------------------------------------------- + + parse: function(input){ + this._tokenStream = new TokenStream(input, Tokens); + this._stylesheet(); + }, + + parseStyleSheet: function(input){ + //just passthrough + return this.parse(input); + }, + + parseMediaQuery: function(input){ + this._tokenStream = new TokenStream(input, Tokens); + var result = this._media_query(); + + //if there's anything more, then it's an invalid selector + this._verifyEnd(); + + //otherwise return result + return result; + }, + + /** + * Parses a property value (everything after the semicolon). + * @return {parserlib.css.PropertyValue} The property value. + * @throws parserlib.util.SyntaxError If an unexpected token is found. + * @method parserPropertyValue + */ + parsePropertyValue: function(input){ + + this._tokenStream = new TokenStream(input, Tokens); + this._readWhitespace(); + + var result = this._expr(); + + //okay to have a trailing white space + this._readWhitespace(); + + //if there's anything more, then it's an invalid selector + this._verifyEnd(); + + //otherwise return result + return result; + }, + + /** + * Parses a complete CSS rule, including selectors and + * properties. + * @param {String} input The text to parser. + * @return {Boolean} True if the parse completed successfully, false if not. + * @method parseRule + */ + parseRule: function(input){ + this._tokenStream = new TokenStream(input, Tokens); + + //skip any leading white space + this._readWhitespace(); + + var result = this._ruleset(); + + //skip any trailing white space + this._readWhitespace(); + + //if there's anything more, then it's an invalid selector + this._verifyEnd(); + + //otherwise return result + return result; + }, + + /** + * Parses a single CSS selector (no comma) + * @param {String} input The text to parse as a CSS selector. + * @return {Selector} An object representing the selector. + * @throws parserlib.util.SyntaxError If an unexpected token is found. + * @method parseSelector + */ + parseSelector: function(input){ + + this._tokenStream = new TokenStream(input, Tokens); + + //skip any leading white space + this._readWhitespace(); + + var result = this._selector(); + + //skip any trailing white space + this._readWhitespace(); + + //if there's anything more, then it's an invalid selector + this._verifyEnd(); + + //otherwise return result + return result; + }, + + /** + * Parses an HTML style attribute: a set of CSS declarations + * separated by semicolons. + * @param {String} input The text to parse as a style attribute + * @return {void} + * @method parseStyleAttribute + */ + parseStyleAttribute: function(input){ + input += "}"; // for error recovery in _readDeclarations() + this._tokenStream = new TokenStream(input, Tokens); + this._readDeclarations(); + } + }; + + //copy over onto prototype + for (prop in additions){ + if (additions.hasOwnProperty(prop)){ + proto[prop] = additions[prop]; + } + } + + return proto; +}(); +/*global Validation, ValidationTypes, ValidationError*/ +var Properties = { + + //A + "alignment-adjust" : "auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | <percentage> | <length>", + "alignment-baseline" : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", + "animation" : 1, + "animation-delay" : { multi: "<time>", comma: true }, + "animation-direction" : { multi: "normal | alternate", comma: true }, + "animation-duration" : { multi: "<time>", comma: true }, + "animation-iteration-count" : { multi: "<number> | infinite", comma: true }, + "animation-name" : { multi: "none | <ident>", comma: true }, + "animation-play-state" : { multi: "running | paused", comma: true }, + "animation-timing-function" : 1, + + //vendor prefixed + "-moz-animation-delay" : { multi: "<time>", comma: true }, + "-moz-animation-direction" : { multi: "normal | alternate", comma: true }, + "-moz-animation-duration" : { multi: "<time>", comma: true }, + "-moz-animation-iteration-count" : { multi: "<number> | infinite", comma: true }, + "-moz-animation-name" : { multi: "none | <ident>", comma: true }, + "-moz-animation-play-state" : { multi: "running | paused", comma: true }, + + "-ms-animation-delay" : { multi: "<time>", comma: true }, + "-ms-animation-direction" : { multi: "normal | alternate", comma: true }, + "-ms-animation-duration" : { multi: "<time>", comma: true }, + "-ms-animation-iteration-count" : { multi: "<number> | infinite", comma: true }, + "-ms-animation-name" : { multi: "none | <ident>", comma: true }, + "-ms-animation-play-state" : { multi: "running | paused", comma: true }, + + "-webkit-animation-delay" : { multi: "<time>", comma: true }, + "-webkit-animation-direction" : { multi: "normal | alternate", comma: true }, + "-webkit-animation-duration" : { multi: "<time>", comma: true }, + "-webkit-animation-iteration-count" : { multi: "<number> | infinite", comma: true }, + "-webkit-animation-name" : { multi: "none | <ident>", comma: true }, + "-webkit-animation-play-state" : { multi: "running | paused", comma: true }, + + "-o-animation-delay" : { multi: "<time>", comma: true }, + "-o-animation-direction" : { multi: "normal | alternate", comma: true }, + "-o-animation-duration" : { multi: "<time>", comma: true }, + "-o-animation-iteration-count" : { multi: "<number> | infinite", comma: true }, + "-o-animation-name" : { multi: "none | <ident>", comma: true }, + "-o-animation-play-state" : { multi: "running | paused", comma: true }, + + "appearance" : "icon | window | desktop | workspace | document | tooltip | dialog | button | push-button | hyperlink | radio-button | checkbox | menu-item | tab | menu | menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | outline-tree | range | field | combo-box | signature | password | normal | inherit", + "azimuth" : function (expression) { + var simple = "<angle> | leftwards | rightwards | inherit", + direction = "left-side | far-left | left | center-left | center | center-right | right | far-right | right-side", + behind = false, + valid = false, + part; + + if (!ValidationTypes.isAny(expression, simple)) { + if (ValidationTypes.isAny(expression, "behind")) { + behind = true; + valid = true; + } + + if (ValidationTypes.isAny(expression, direction)) { + valid = true; + if (!behind) { + ValidationTypes.isAny(expression, "behind"); + } + } + } + + if (expression.hasNext()) { + part = expression.next(); + if (valid) { + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + throw new ValidationError("Expected (<'azimuth'>) but found '" + part + "'.", part.line, part.col); + } + } + }, + + //B + "backface-visibility" : "visible | hidden", + "background" : 1, + "background-attachment" : { multi: "<attachment>", comma: true }, + "background-clip" : { multi: "<box>", comma: true }, + "background-color" : "<color> | inherit", + "background-image" : { multi: "<bg-image>", comma: true }, + "background-origin" : { multi: "<box>", comma: true }, + "background-position" : { multi: "<bg-position>", comma: true }, + "background-repeat" : { multi: "<repeat-style>" }, + "background-size" : { multi: "<bg-size>", comma: true }, + "baseline-shift" : "baseline | sub | super | <percentage> | <length>", + "binding" : 1, + "bleed" : "<length>", + "bookmark-label" : "<content> | <attr> | <string>", + "bookmark-level" : "none | <integer>", + "bookmark-state" : "open | closed", + "bookmark-target" : "none | <uri> | <attr>", + "border" : "<border-width> || <border-style> || <color>", + "border-bottom" : "<border-width> || <border-style> || <color>", + "border-bottom-color" : "<color>", + "border-bottom-left-radius" : "<x-one-radius>", + "border-bottom-right-radius" : "<x-one-radius>", + "border-bottom-style" : "<border-style>", + "border-bottom-width" : "<border-width>", + "border-collapse" : "collapse | separate | inherit", + "border-color" : { multi: "<color> | inherit", max: 4 }, + "border-image" : 1, + "border-image-outset" : { multi: "<length> | <number>", max: 4 }, + "border-image-repeat" : { multi: "stretch | repeat | round", max: 2 }, + "border-image-slice" : function(expression) { + + var valid = false, + numeric = "<number> | <percentage>", + fill = false, + count = 0, + max = 4, + part; + + if (ValidationTypes.isAny(expression, "fill")) { + fill = true; + valid = true; + } + + while (expression.hasNext() && count < max) { + valid = ValidationTypes.isAny(expression, numeric); + if (!valid) { + break; + } + count++; + } + + + if (!fill) { + ValidationTypes.isAny(expression, "fill"); + } else { + valid = true; + } + + if (expression.hasNext()) { + part = expression.next(); + if (valid) { + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + throw new ValidationError("Expected ([<number> | <percentage>]{1,4} && fill?) but found '" + part + "'.", part.line, part.col); + } + } + }, + "border-image-source" : "<image> | none", + "border-image-width" : { multi: "<length> | <percentage> | <number> | auto", max: 4 }, + "border-left" : "<border-width> || <border-style> || <color>", + "border-left-color" : "<color> | inherit", + "border-left-style" : "<border-style>", + "border-left-width" : "<border-width>", + "border-radius" : function(expression) { + + var valid = false, + numeric = "<length> | <percentage>", + slash = false, + fill = false, + count = 0, + max = 8, + part; + + while (expression.hasNext() && count < max) { + valid = ValidationTypes.isAny(expression, numeric); + if (!valid) { + + if (expression.peek() == "/" && count > 1 && !slash) { + slash = true; + max = count + 5; + expression.next(); + } else { + break; + } + } + count++; + } + + if (expression.hasNext()) { + part = expression.next(); + if (valid) { + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + throw new ValidationError("Expected (<'border-radius'>) but found '" + part + "'.", part.line, part.col); + } + } + }, + "border-right" : "<border-width> || <border-style> || <color>", + "border-right-color" : "<color> | inherit", + "border-right-style" : "<border-style>", + "border-right-width" : "<border-width>", + "border-spacing" : { multi: "<length> | inherit", max: 2 }, + "border-style" : { multi: "<border-style>", max: 4 }, + "border-top" : "<border-width> || <border-style> || <color>", + "border-top-color" : "<color> | inherit", + "border-top-left-radius" : "<x-one-radius>", + "border-top-right-radius" : "<x-one-radius>", + "border-top-style" : "<border-style>", + "border-top-width" : "<border-width>", + "border-width" : { multi: "<border-width>", max: 4 }, + "bottom" : "<margin-width> | inherit", + "box-align" : "start | end | center | baseline | stretch", //http://www.w3.org/TR/2009/WD-css3-flexbox-20090723/ + "box-decoration-break" : "slice |clone", + "box-direction" : "normal | reverse | inherit", + "box-flex" : "<number>", + "box-flex-group" : "<integer>", + "box-lines" : "single | multiple", + "box-ordinal-group" : "<integer>", + "box-orient" : "horizontal | vertical | inline-axis | block-axis | inherit", + "box-pack" : "start | end | center | justify", + "box-shadow" : function (expression) { + var result = false, + part; + + if (!ValidationTypes.isAny(expression, "none")) { + Validation.multiProperty("<shadow>", expression, true, Infinity); + } else { + if (expression.hasNext()) { + part = expression.next(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } + } + }, + "box-sizing" : "content-box | border-box | inherit", + "break-after" : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column", + "break-before" : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column", + "break-inside" : "auto | avoid | avoid-page | avoid-column", + + //C + "caption-side" : "top | bottom | inherit", + "clear" : "none | right | left | both | inherit", + "clip" : 1, + "color" : "<color> | inherit", + "color-profile" : 1, + "column-count" : "<integer> | auto", //http://www.w3.org/TR/css3-multicol/ + "column-fill" : "auto | balance", + "column-gap" : "<length> | normal", + "column-rule" : "<border-width> || <border-style> || <color>", + "column-rule-color" : "<color>", + "column-rule-style" : "<border-style>", + "column-rule-width" : "<border-width>", + "column-span" : "none | all", + "column-width" : "<length> | auto", + "columns" : 1, + "content" : 1, + "counter-increment" : 1, + "counter-reset" : 1, + "crop" : "<shape> | auto", + "cue" : "cue-after | cue-before | inherit", + "cue-after" : 1, + "cue-before" : 1, + "cursor" : 1, + + //D + "direction" : "ltr | rtl | inherit", + "display" : "inline | block | list-item | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | box | inline-box | grid | inline-grid | none | inherit", + "dominant-baseline" : 1, + "drop-initial-after-adjust" : "central | middle | after-edge | text-after-edge | ideographic | alphabetic | mathematical | <percentage> | <length>", + "drop-initial-after-align" : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", + "drop-initial-before-adjust" : "before-edge | text-before-edge | central | middle | hanging | mathematical | <percentage> | <length>", + "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", + "drop-initial-size" : "auto | line | <length> | <percentage>", + "drop-initial-value" : "initial | <integer>", + + //E + "elevation" : "<angle> | below | level | above | higher | lower | inherit", + "empty-cells" : "show | hide | inherit", + + //F + "filter" : 1, + "fit" : "fill | hidden | meet | slice", + "fit-position" : 1, + "float" : "left | right | none | inherit", + "float-offset" : 1, + "font" : 1, + "font-family" : 1, + "font-size" : "<absolute-size> | <relative-size> | <length> | <percentage> | inherit", + "font-size-adjust" : "<number> | none | inherit", + "font-stretch" : "normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit", + "font-style" : "normal | italic | oblique | inherit", + "font-variant" : "normal | small-caps | inherit", + "font-weight" : "normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit", + + //G + "grid-cell-stacking" : "columns | rows | layer", + "grid-column" : 1, + "grid-columns" : 1, + "grid-column-align" : "start | end | center | stretch", + "grid-column-sizing" : 1, + "grid-column-span" : "<integer>", + "grid-flow" : "none | rows | columns", + "grid-layer" : "<integer>", + "grid-row" : 1, + "grid-rows" : 1, + "grid-row-align" : "start | end | center | stretch", + "grid-row-span" : "<integer>", + "grid-row-sizing" : 1, + + //H + "hanging-punctuation" : 1, + "height" : "<margin-width> | inherit", + "hyphenate-after" : "<integer> | auto", + "hyphenate-before" : "<integer> | auto", + "hyphenate-character" : "<string> | auto", + "hyphenate-lines" : "no-limit | <integer>", + "hyphenate-resource" : 1, + "hyphens" : "none | manual | auto", + + //I + "icon" : 1, + "image-orientation" : "angle | auto", + "image-rendering" : 1, + "image-resolution" : 1, + "inline-box-align" : "initial | last | <integer>", + + //L + "left" : "<margin-width> | inherit", + "letter-spacing" : "<length> | normal | inherit", + "line-height" : "<number> | <length> | <percentage> | normal | inherit", + "line-break" : "auto | loose | normal | strict", + "line-stacking" : 1, + "line-stacking-ruby" : "exclude-ruby | include-ruby", + "line-stacking-shift" : "consider-shifts | disregard-shifts", + "line-stacking-strategy" : "inline-line-height | block-line-height | max-height | grid-height", + "list-style" : 1, + "list-style-image" : "<uri> | none | inherit", + "list-style-position" : "inside | outside | inherit", + "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", + + //M + "margin" : { multi: "<margin-width> | inherit", max: 4 }, + "margin-bottom" : "<margin-width> | inherit", + "margin-left" : "<margin-width> | inherit", + "margin-right" : "<margin-width> | inherit", + "margin-top" : "<margin-width> | inherit", + "mark" : 1, + "mark-after" : 1, + "mark-before" : 1, + "marks" : 1, + "marquee-direction" : 1, + "marquee-play-count" : 1, + "marquee-speed" : 1, + "marquee-style" : 1, + "max-height" : "<length> | <percentage> | none | inherit", + "max-width" : "<length> | <percentage> | none | inherit", + "min-height" : "<length> | <percentage> | inherit", + "min-width" : "<length> | <percentage> | inherit", + "move-to" : 1, + + //N + "nav-down" : 1, + "nav-index" : 1, + "nav-left" : 1, + "nav-right" : 1, + "nav-up" : 1, + + //O + "opacity" : "<number> | inherit", + "orphans" : "<integer> | inherit", + "outline" : 1, + "outline-color" : "<color> | invert | inherit", + "outline-offset" : 1, + "outline-style" : "<border-style> | inherit", + "outline-width" : "<border-width> | inherit", + "overflow" : "visible | hidden | scroll | auto | inherit", + "overflow-style" : 1, + "overflow-x" : 1, + "overflow-y" : 1, + + //P + "padding" : { multi: "<padding-width> | inherit", max: 4 }, + "padding-bottom" : "<padding-width> | inherit", + "padding-left" : "<padding-width> | inherit", + "padding-right" : "<padding-width> | inherit", + "padding-top" : "<padding-width> | inherit", + "page" : 1, + "page-break-after" : "auto | always | avoid | left | right | inherit", + "page-break-before" : "auto | always | avoid | left | right | inherit", + "page-break-inside" : "auto | avoid | inherit", + "page-policy" : 1, + "pause" : 1, + "pause-after" : 1, + "pause-before" : 1, + "perspective" : 1, + "perspective-origin" : 1, + "phonemes" : 1, + "pitch" : 1, + "pitch-range" : 1, + "play-during" : 1, + "position" : "static | relative | absolute | fixed | inherit", + "presentation-level" : 1, + "punctuation-trim" : 1, + + //Q + "quotes" : 1, + + //R + "rendering-intent" : 1, + "resize" : 1, + "rest" : 1, + "rest-after" : 1, + "rest-before" : 1, + "richness" : 1, + "right" : "<margin-width> | inherit", + "rotation" : 1, + "rotation-point" : 1, + "ruby-align" : 1, + "ruby-overhang" : 1, + "ruby-position" : 1, + "ruby-span" : 1, + + //S + "size" : 1, + "speak" : "normal | none | spell-out | inherit", + "speak-header" : "once | always | inherit", + "speak-numeral" : "digits | continuous | inherit", + "speak-punctuation" : "code | none | inherit", + "speech-rate" : 1, + "src" : 1, + "stress" : 1, + "string-set" : 1, + + "table-layout" : "auto | fixed | inherit", + "tab-size" : "<integer> | <length>", + "target" : 1, + "target-name" : 1, + "target-new" : 1, + "target-position" : 1, + "text-align" : "left | right | center | justify | inherit" , + "text-align-last" : 1, + "text-decoration" : 1, + "text-emphasis" : 1, + "text-height" : 1, + "text-indent" : "<length> | <percentage> | inherit", + "text-justify" : "auto | none | inter-word | inter-ideograph | inter-cluster | distribute | kashida", + "text-outline" : 1, + "text-overflow" : 1, + "text-shadow" : 1, + "text-transform" : "capitalize | uppercase | lowercase | none | inherit", + "text-wrap" : "normal | none | avoid", + "top" : "<margin-width> | inherit", + "transform" : 1, + "transform-origin" : 1, + "transform-style" : 1, + "transition" : 1, + "transition-delay" : 1, + "transition-duration" : 1, + "transition-property" : 1, + "transition-timing-function" : 1, + + //U + "unicode-bidi" : "normal | embed | bidi-override | inherit", + "user-modify" : "read-only | read-write | write-only | inherit", + "user-select" : "none | text | toggle | element | elements | all | inherit", + + //V + "vertical-align" : "<percentage> | <length> | baseline | sub | super | top | text-top | middle | bottom | text-bottom | inherit", + "visibility" : "visible | hidden | collapse | inherit", + "voice-balance" : 1, + "voice-duration" : 1, + "voice-family" : 1, + "voice-pitch" : 1, + "voice-pitch-range" : 1, + "voice-rate" : 1, + "voice-stress" : 1, + "voice-volume" : 1, + "volume" : 1, + + //W + "white-space" : "normal | pre | nowrap | pre-wrap | pre-line | inherit", + "white-space-collapse" : 1, + "widows" : "<integer> | inherit", + "width" : "<length> | <percentage> | auto | inherit" , + "word-break" : "normal | keep-all | break-all", + "word-spacing" : "<length> | normal | inherit", + "word-wrap" : 1, + + //Z + "z-index" : "<integer> | auto | inherit", + "zoom" : "<number> | <percentage> | normal" +}; +/** + * Represents a selector combinator (whitespace, +, >). + * @namespace parserlib.css + * @class PropertyName + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} text The text representation of the unit. + * @param {String} hack The type of IE hack applied ("*", "_", or null). + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function PropertyName(text, hack, line, col){ + + SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_NAME_TYPE); + this.hack = hack; + +} + +PropertyName.prototype = new SyntaxUnit(); +PropertyName.prototype.constructor = PropertyName; +PropertyName.prototype.toString = function(){ + return (this.hack ? this.hack : "") + this.text; +}; +/** + * Represents a single part of a CSS property value, meaning that it represents + * just everything single part between ":" and ";". If there are multiple values + * separated by commas, this type represents just one of the values. + * @param {String[]} parts An array of value parts making up this value. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + * @namespace parserlib.css + * @class PropertyValue + * @extends parserlib.util.SyntaxUnit + * @constructor + */ +function PropertyValue(parts, line, col){ + + SyntaxUnit.call(this, parts.join(" "), line, col, Parser.PROPERTY_VALUE_TYPE); + this.parts = parts; + +} + +PropertyValue.prototype = new SyntaxUnit(); +PropertyValue.prototype.constructor = PropertyValue; +/** + * A utility class that allows for easy iteration over the various parts of a + * property value. + * @param {parserlib.css.PropertyValue} value The property value to iterate over. + * @namespace parserlib.css + * @class PropertyValueIterator + * @constructor + */ +function PropertyValueIterator(value){ + + /** + * Iterator value + * @type int + * @property _i + * @private + */ + this._i = 0; + this._parts = value.parts; + this._marks = []; + this.value = value; + +} + +/** + * Returns the total number of parts in the value. + * @return {int} The total number of parts in the value. + * @method count + */ +PropertyValueIterator.prototype.count = function(){ + return this._parts.length; +}; +PropertyValueIterator.prototype.isFirst = function(){ + return this._i === 0; +}; +PropertyValueIterator.prototype.hasNext = function(){ + return (this._i < this._parts.length); +}; +PropertyValueIterator.prototype.mark = function(){ + this._marks.push(this._i); +}; +PropertyValueIterator.prototype.peek = function(count){ + return this.hasNext() ? this._parts[this._i + (count || 0)] : null; +}; +PropertyValueIterator.prototype.next = function(){ + return this.hasNext() ? this._parts[this._i++] : null; +}; +PropertyValueIterator.prototype.previous = function(){ + return this._i > 0 ? this._parts[--this._i] : null; +}; +PropertyValueIterator.prototype.restore = function(){ + if (this._marks.length){ + this._i = this._marks.pop(); + } +}; +/** + * Represents a single part of a CSS property value, meaning that it represents + * just one part of the data between ":" and ";". + * @param {String} text The text representation of the unit. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + * @namespace parserlib.css + * @class PropertyValuePart + * @extends parserlib.util.SyntaxUnit + * @constructor + */ +function PropertyValuePart(text, line, col){ + + SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_VALUE_PART_TYPE); + this.type = "unknown"; + + //figure out what type of data it is + + var temp; + + //it is a measurement? + if (/^([+\-]?[\d\.]+)([a-z]+)$/i.test(text)){ //dimension + this.type = "dimension"; + this.value = +RegExp.$1; + this.units = RegExp.$2; + + //try to narrow down + switch(this.units.toLowerCase()){ + + case "em": + case "rem": + case "ex": + case "px": + case "cm": + case "mm": + case "in": + case "pt": + case "pc": + case "ch": + this.type = "length"; + break; + + case "deg": + case "rad": + case "grad": + this.type = "angle"; + break; + + case "ms": + case "s": + this.type = "time"; + break; + + case "hz": + case "khz": + this.type = "frequency"; + break; + + case "dpi": + case "dpcm": + this.type = "resolution"; + break; + + //default + + } + + } else if (/^([+\-]?[\d\.]+)%$/i.test(text)){ //percentage + this.type = "percentage"; + this.value = +RegExp.$1; + } else if (/^([+\-]?[\d\.]+)%$/i.test(text)){ //percentage + this.type = "percentage"; + this.value = +RegExp.$1; + } else if (/^([+\-]?\d+)$/i.test(text)){ //integer + this.type = "integer"; + this.value = +RegExp.$1; + } else if (/^([+\-]?[\d\.]+)$/i.test(text)){ //number + this.type = "number"; + this.value = +RegExp.$1; + + } else if (/^#([a-f0-9]{3,6})/i.test(text)){ //hexcolor + this.type = "color"; + temp = RegExp.$1; + if (temp.length == 3){ + this.red = parseInt(temp.charAt(0)+temp.charAt(0),16); + this.green = parseInt(temp.charAt(1)+temp.charAt(1),16); + this.blue = parseInt(temp.charAt(2)+temp.charAt(2),16); + } else { + this.red = parseInt(temp.substring(0,2),16); + this.green = parseInt(temp.substring(2,4),16); + this.blue = parseInt(temp.substring(4,6),16); + } + } else if (/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i.test(text)){ //rgb() color with absolute numbers + this.type = "color"; + this.red = +RegExp.$1; + this.green = +RegExp.$2; + this.blue = +RegExp.$3; + } else if (/^rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)){ //rgb() color with percentages + this.type = "color"; + this.red = +RegExp.$1 * 255 / 100; + this.green = +RegExp.$2 * 255 / 100; + this.blue = +RegExp.$3 * 255 / 100; + } else if (/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //rgba() color with absolute numbers + this.type = "color"; + this.red = +RegExp.$1; + this.green = +RegExp.$2; + this.blue = +RegExp.$3; + this.alpha = +RegExp.$4; + } else if (/^rgba\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //rgba() color with percentages + this.type = "color"; + this.red = +RegExp.$1 * 255 / 100; + this.green = +RegExp.$2 * 255 / 100; + this.blue = +RegExp.$3 * 255 / 100; + this.alpha = +RegExp.$4; + } else if (/^hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)){ //hsl() + this.type = "color"; + this.hue = +RegExp.$1; + this.saturation = +RegExp.$2 / 100; + this.lightness = +RegExp.$3 / 100; + } else if (/^hsla\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //hsla() color with percentages + this.type = "color"; + this.hue = +RegExp.$1; + this.saturation = +RegExp.$2 / 100; + this.lightness = +RegExp.$3 / 100; + this.alpha = +RegExp.$4; + } else if (/^url\(["']?([^\)"']+)["']?\)/i.test(text)){ //URI + this.type = "uri"; + this.uri = RegExp.$1; + } else if (/^([^\(]+)\(/i.test(text)){ + this.type = "function"; + this.name = RegExp.$1; + this.value = text; + } else if (/^["'][^"']*["']/.test(text)){ //string + this.type = "string"; + this.value = eval(text); + } else if (Colors[text.toLowerCase()]){ //named color + this.type = "color"; + temp = Colors[text.toLowerCase()].substring(1); + this.red = parseInt(temp.substring(0,2),16); + this.green = parseInt(temp.substring(2,4),16); + this.blue = parseInt(temp.substring(4,6),16); + } else if (/^[\,\/]$/.test(text)){ + this.type = "operator"; + this.value = text; + } else if (/^[a-z\-\u0080-\uFFFF][a-z0-9\-\u0080-\uFFFF]*$/i.test(text)){ + this.type = "identifier"; + this.value = text; + } + +} + +PropertyValuePart.prototype = new SyntaxUnit(); +PropertyValuePart.prototype.constructor = PropertyValuePart; +PropertyValuePart.fromToken = function(token){ + return new PropertyValuePart(token.value, token.startLine, token.startCol); +}; +var Pseudos = { + ":first-letter": 1, + ":first-line": 1, + ":before": 1, + ":after": 1 +}; + +Pseudos.ELEMENT = 1; +Pseudos.CLASS = 2; + +Pseudos.isElement = function(pseudo){ + return pseudo.indexOf("::") === 0 || Pseudos[pseudo.toLowerCase()] == Pseudos.ELEMENT; +}; +/** + * Represents an entire single selector, including all parts but not + * including multiple selectors (those separated by commas). + * @namespace parserlib.css + * @class Selector + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {Array} parts Array of selectors parts making up this selector. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function Selector(parts, line, col){ + + SyntaxUnit.call(this, parts.join(" "), line, col, Parser.SELECTOR_TYPE); + this.parts = parts; + this.specificity = Specificity.calculate(this); + +} + +Selector.prototype = new SyntaxUnit(); +Selector.prototype.constructor = Selector; +/** + * Represents a single part of a selector string, meaning a single set of + * element name and modifiers. This does not include combinators such as + * spaces, +, >, etc. + * @namespace parserlib.css + * @class SelectorPart + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} elementName The element name in the selector or null + * if there is no element name. + * @param {Array} modifiers Array of individual modifiers for the element. + * May be empty if there are none. + * @param {String} text The text representation of the unit. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function SelectorPart(elementName, modifiers, text, line, col){ + + SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_PART_TYPE); + this.elementName = elementName; + this.modifiers = modifiers; + +} + +SelectorPart.prototype = new SyntaxUnit(); +SelectorPart.prototype.constructor = SelectorPart; +/** + * Represents a selector modifier string, meaning a class name, element name, + * element ID, pseudo rule, etc. + * @namespace parserlib.css + * @class SelectorSubPart + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} text The text representation of the unit. + * @param {String} type The type of selector modifier. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function SelectorSubPart(text, type, line, col){ + + SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_SUB_PART_TYPE); + this.type = type; + this.args = []; + +} + +SelectorSubPart.prototype = new SyntaxUnit(); +SelectorSubPart.prototype.constructor = SelectorSubPart; +/** + * Represents a selector's specificity. + * @namespace parserlib.css + * @class Specificity + * @constructor + * @param {int} a Should be 1 for inline styles, zero for stylesheet styles + * @param {int} b Number of ID selectors + * @param {int} c Number of classes and pseudo classes + * @param {int} d Number of element names and pseudo elements + */ +function Specificity(a, b, c, d){ + this.a = a; + this.b = b; + this.c = c; + this.d = d; +} + +Specificity.prototype = { + constructor: Specificity, + + /** + * Compare this specificity to another. + * @param {Specificity} other The other specificity to compare to. + * @return {int} -1 if the other specificity is larger, 1 if smaller, 0 if equal. + * @method compare + */ + compare: function(other){ + var comps = ["a", "b", "c", "d"], + i, len; + + for (i=0, len=comps.length; i < len; i++){ + if (this[comps[i]] < other[comps[i]]){ + return -1; + } else if (this[comps[i]] > other[comps[i]]){ + return 1; + } + } + + return 0; + }, + + /** + * Creates a numeric value for the specificity. + * @return {int} The numeric value for the specificity. + * @method valueOf + */ + valueOf: function(){ + return (this.a * 1000) + (this.b * 100) + (this.c * 10) + this.d; + }, + + /** + * Returns a string representation for specificity. + * @return {String} The string representation of specificity. + * @method toString + */ + toString: function(){ + return this.a + "," + this.b + "," + this.c + "," + this.d; + } + +}; +Specificity.calculate = function(selector){ + + var i, len, + part, + b=0, c=0, d=0; + + function updateValues(part){ + + var i, j, len, num, + elementName = part.elementName ? part.elementName.text : "", + modifier; + + if (elementName && elementName.charAt(elementName.length-1) != "*") { + d++; + } + + for (i=0, len=part.modifiers.length; i < len; i++){ + modifier = part.modifiers[i]; + switch(modifier.type){ + case "class": + case "attribute": + c++; + break; + + case "id": + b++; + break; + + case "pseudo": + if (Pseudos.isElement(modifier.text)){ + d++; + } else { + c++; + } + break; + + case "not": + for (j=0, num=modifier.args.length; j < num; j++){ + updateValues(modifier.args[j]); + } + } + } + } + + for (i=0, len=selector.parts.length; i < len; i++){ + part = selector.parts[i]; + + if (part instanceof SelectorPart){ + updateValues(part); + } + } + + return new Specificity(0, b, c, d); +}; + +var h = /^[0-9a-fA-F]$/, + nonascii = /^[\u0080-\uFFFF]$/, + nl = /\n|\r\n|\r|\f/; + +//----------------------------------------------------------------------------- +// Helper functions +//----------------------------------------------------------------------------- + + +function isHexDigit(c){ + return c !== null && h.test(c); +} + +function isDigit(c){ + return c !== null && /\d/.test(c); +} + +function isWhitespace(c){ + return c !== null && /\s/.test(c); +} + +function isNewLine(c){ + return c !== null && nl.test(c); +} + +function isNameStart(c){ + return c !== null && (/[a-z_\u0080-\uFFFF\\]/i.test(c)); +} + +function isNameChar(c){ + return c !== null && (isNameStart(c) || /[0-9\-\\]/.test(c)); +} + +function isIdentStart(c){ + return c !== null && (isNameStart(c) || /\-\\/.test(c)); +} + +function mix(receiver, supplier){ + for (var prop in supplier){ + if (supplier.hasOwnProperty(prop)){ + receiver[prop] = supplier[prop]; + } + } + return receiver; +} + +//----------------------------------------------------------------------------- +// CSS Token Stream +//----------------------------------------------------------------------------- + + +/** + * A token stream that produces CSS tokens. + * @param {String|Reader} input The source of text to tokenize. + * @constructor + * @class TokenStream + * @namespace parserlib.css + */ +function TokenStream(input){ + TokenStreamBase.call(this, input, Tokens); +} + +TokenStream.prototype = mix(new TokenStreamBase(), { + + /** + * Overrides the TokenStreamBase method of the same name + * to produce CSS tokens. + * @param {variant} channel The name of the channel to use + * for the next token. + * @return {Object} A token object representing the next token. + * @method _getToken + * @private + */ + _getToken: function(channel){ + + var c, + reader = this._reader, + token = null, + startLine = reader.getLine(), + startCol = reader.getCol(); + + c = reader.read(); + + + while(c){ + switch(c){ + + /* + * Potential tokens: + * - COMMENT + * - SLASH + * - CHAR + */ + case "/": + + if(reader.peek() == "*"){ + token = this.commentToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + case "|": + case "~": + case "^": + case "$": + case "*": + if(reader.peek() == "="){ + token = this.comparisonToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + case "\"": + case "'": + token = this.stringToken(c, startLine, startCol); + break; + case "#": + if (isNameChar(reader.peek())){ + token = this.hashToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + case ".": + if (isDigit(reader.peek())){ + token = this.numberToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + case "-": + if (reader.peek() == "-"){ //could be closing HTML-style comment + token = this.htmlCommentEndToken(c, startLine, startCol); + } else if (isNameStart(reader.peek())){ + token = this.identOrFunctionToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + case "!": + token = this.importantToken(c, startLine, startCol); + break; + case "@": + token = this.atRuleToken(c, startLine, startCol); + break; + case ":": + token = this.notToken(c, startLine, startCol); + break; + case "<": + token = this.htmlCommentStartToken(c, startLine, startCol); + break; + case "U": + case "u": + if (reader.peek() == "+"){ + token = this.unicodeRangeToken(c, startLine, startCol); + break; + } + /* falls through */ + default: + + /* + * Potential tokens: + * - NUMBER + * - DIMENSION + * - LENGTH + * - FREQ + * - TIME + * - EMS + * - EXS + * - ANGLE + */ + if (isDigit(c)){ + token = this.numberToken(c, startLine, startCol); + } else + + /* + * Potential tokens: + * - S + */ + if (isWhitespace(c)){ + token = this.whitespaceToken(c, startLine, startCol); + } else + + /* + * Potential tokens: + * - IDENT + */ + if (isIdentStart(c)){ + token = this.identOrFunctionToken(c, startLine, startCol); + } else + + /* + * Potential tokens: + * - CHAR + * - PLUS + */ + { + token = this.charToken(c, startLine, startCol); + } + + + + + + + } + + //make sure this token is wanted + //TODO: check channel + break; + } + + if (!token && c === null){ + token = this.createToken(Tokens.EOF,null,startLine,startCol); + } + + return token; + }, + + //------------------------------------------------------------------------- + // Methods to create tokens + //------------------------------------------------------------------------- + + /** + * Produces a token based on available data and the current + * reader position information. This method is called by other + * private methods to create tokens and is never called directly. + * @param {int} tt The token type. + * @param {String} value The text value of the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @param {Object} options (Optional) Specifies a channel property + * to indicate that a different channel should be scanned + * and/or a hide property indicating that the token should + * be hidden. + * @return {Object} A token object. + * @method createToken + */ + createToken: function(tt, value, startLine, startCol, options){ + var reader = this._reader; + options = options || {}; + + return { + value: value, + type: tt, + channel: options.channel, + hide: options.hide || false, + startLine: startLine, + startCol: startCol, + endLine: reader.getLine(), + endCol: reader.getCol() + }; + }, + + //------------------------------------------------------------------------- + // Methods to create specific tokens + //------------------------------------------------------------------------- + + /** + * Produces a token for any at-rule. If the at-rule is unknown, then + * the token is for a single "@" character. + * @param {String} first The first character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method atRuleToken + */ + atRuleToken: function(first, startLine, startCol){ + var rule = first, + reader = this._reader, + tt = Tokens.CHAR, + valid = false, + ident, + c; + reader.mark(); + + //try to find the at-keyword + ident = this.readName(); + rule = first + ident; + tt = Tokens.type(rule.toLowerCase()); + + //if it's not valid, use the first character only and reset the reader + if (tt == Tokens.CHAR || tt == Tokens.UNKNOWN){ + if (rule.length > 1){ + tt = Tokens.UNKNOWN_SYM; + } else { + tt = Tokens.CHAR; + rule = first; + reader.reset(); + } + } + + return this.createToken(tt, rule, startLine, startCol); + }, + + /** + * Produces a character token based on the given character + * and location in the stream. If there's a special (non-standard) + * token name, this is used; otherwise CHAR is used. + * @param {String} c The character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method charToken + */ + charToken: function(c, startLine, startCol){ + var tt = Tokens.type(c); + + if (tt == -1){ + tt = Tokens.CHAR; + } + + return this.createToken(tt, c, startLine, startCol); + }, + + /** + * Produces a character token based on the given character + * and location in the stream. If there's a special (non-standard) + * token name, this is used; otherwise CHAR is used. + * @param {String} first The first character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method commentToken + */ + commentToken: function(first, startLine, startCol){ + var reader = this._reader, + comment = this.readComment(first); + + return this.createToken(Tokens.COMMENT, comment, startLine, startCol); + }, + + /** + * Produces a comparison token based on the given character + * and location in the stream. The next character must be + * read and is already known to be an equals sign. + * @param {String} c The character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method comparisonToken + */ + comparisonToken: function(c, startLine, startCol){ + var reader = this._reader, + comparison = c + reader.read(), + tt = Tokens.type(comparison) || Tokens.CHAR; + + return this.createToken(tt, comparison, startLine, startCol); + }, + + /** + * Produces a hash token based on the specified information. The + * first character provided is the pound sign (#) and then this + * method reads a name afterward. + * @param {String} first The first character (#) in the hash name. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method hashToken + */ + hashToken: function(first, startLine, startCol){ + var reader = this._reader, + name = this.readName(first); + + return this.createToken(Tokens.HASH, name, startLine, startCol); + }, + + /** + * Produces a CDO or CHAR token based on the specified information. The + * first character is provided and the rest is read by the function to determine + * the correct token to create. + * @param {String} first The first character in the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method htmlCommentStartToken + */ + htmlCommentStartToken: function(first, startLine, startCol){ + var reader = this._reader, + text = first; + + reader.mark(); + text += reader.readCount(3); + + if (text == "<!--"){ + return this.createToken(Tokens.CDO, text, startLine, startCol); + } else { + reader.reset(); + return this.charToken(first, startLine, startCol); + } + }, + + /** + * Produces a CDC or CHAR token based on the specified information. The + * first character is provided and the rest is read by the function to determine + * the correct token to create. + * @param {String} first The first character in the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method htmlCommentEndToken + */ + htmlCommentEndToken: function(first, startLine, startCol){ + var reader = this._reader, + text = first; + + reader.mark(); + text += reader.readCount(2); + + if (text == "-->"){ + return this.createToken(Tokens.CDC, text, startLine, startCol); + } else { + reader.reset(); + return this.charToken(first, startLine, startCol); + } + }, + + /** + * Produces an IDENT or FUNCTION token based on the specified information. The + * first character is provided and the rest is read by the function to determine + * the correct token to create. + * @param {String} first The first character in the identifier. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method identOrFunctionToken + */ + identOrFunctionToken: function(first, startLine, startCol){ + var reader = this._reader, + ident = this.readName(first), + tt = Tokens.IDENT; + + //if there's a left paren immediately after, it's a URI or function + if (reader.peek() == "("){ + ident += reader.read(); + if (ident.toLowerCase() == "url("){ + tt = Tokens.URI; + ident = this.readURI(ident); + + //didn't find a valid URL or there's no closing paren + if (ident.toLowerCase() == "url("){ + tt = Tokens.FUNCTION; + } + } else { + tt = Tokens.FUNCTION; + } + } else if (reader.peek() == ":"){ //might be an IE function + + //IE-specific functions always being with progid: + if (ident.toLowerCase() == "progid"){ + ident += reader.readTo("("); + tt = Tokens.IE_FUNCTION; + } + } + + return this.createToken(tt, ident, startLine, startCol); + }, + + /** + * Produces an IMPORTANT_SYM or CHAR token based on the specified information. The + * first character is provided and the rest is read by the function to determine + * the correct token to create. + * @param {String} first The first character in the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method importantToken + */ + importantToken: function(first, startLine, startCol){ + var reader = this._reader, + important = first, + tt = Tokens.CHAR, + temp, + c; + + reader.mark(); + c = reader.read(); + + while(c){ + + //there can be a comment in here + if (c == "/"){ + + //if the next character isn't a star, then this isn't a valid !important token + if (reader.peek() != "*"){ + break; + } else { + temp = this.readComment(c); + if (temp === ""){ //broken! + break; + } + } + } else if (isWhitespace(c)){ + important += c + this.readWhitespace(); + } else if (/i/i.test(c)){ + temp = reader.readCount(8); + if (/mportant/i.test(temp)){ + important += c + temp; + tt = Tokens.IMPORTANT_SYM; + + } + break; //we're done + } else { + break; + } + + c = reader.read(); + } + + if (tt == Tokens.CHAR){ + reader.reset(); + return this.charToken(first, startLine, startCol); + } else { + return this.createToken(tt, important, startLine, startCol); + } + + + }, + + /** + * Produces a NOT or CHAR token based on the specified information. The + * first character is provided and the rest is read by the function to determine + * the correct token to create. + * @param {String} first The first character in the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method notToken + */ + notToken: function(first, startLine, startCol){ + var reader = this._reader, + text = first; + + reader.mark(); + text += reader.readCount(4); + + if (text.toLowerCase() == ":not("){ + return this.createToken(Tokens.NOT, text, startLine, startCol); + } else { + reader.reset(); + return this.charToken(first, startLine, startCol); + } + }, + + /** + * Produces a number token based on the given character + * and location in the stream. This may return a token of + * NUMBER, EMS, EXS, LENGTH, ANGLE, TIME, FREQ, DIMENSION, + * or PERCENTAGE. + * @param {String} first The first character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method numberToken + */ + numberToken: function(first, startLine, startCol){ + var reader = this._reader, + value = this.readNumber(first), + ident, + tt = Tokens.NUMBER, + c = reader.peek(); + + if (isIdentStart(c)){ + ident = this.readName(reader.read()); + value += ident; + + if (/^em$|^ex$|^px$|^gd$|^rem$|^vw$|^vh$|^vm$|^ch$|^cm$|^mm$|^in$|^pt$|^pc$/i.test(ident)){ + tt = Tokens.LENGTH; + } else if (/^deg|^rad$|^grad$/i.test(ident)){ + tt = Tokens.ANGLE; + } else if (/^ms$|^s$/i.test(ident)){ + tt = Tokens.TIME; + } else if (/^hz$|^khz$/i.test(ident)){ + tt = Tokens.FREQ; + } else if (/^dpi$|^dpcm$/i.test(ident)){ + tt = Tokens.RESOLUTION; + } else { + tt = Tokens.DIMENSION; + } + + } else if (c == "%"){ + value += reader.read(); + tt = Tokens.PERCENTAGE; + } + + return this.createToken(tt, value, startLine, startCol); + }, + + /** + * Produces a string token based on the given character + * and location in the stream. Since strings may be indicated + * by single or double quotes, a failure to match starting + * and ending quotes results in an INVALID token being generated. + * The first character in the string is passed in and then + * the rest are read up to and including the final quotation mark. + * @param {String} first The first character in the string. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method stringToken + */ + stringToken: function(first, startLine, startCol){ + var delim = first, + string = first, + reader = this._reader, + prev = first, + tt = Tokens.STRING, + c = reader.read(); + + while(c){ + string += c; + + //if the delimiter is found with an escapement, we're done. + if (c == delim && prev != "\\"){ + break; + } + + //if there's a newline without an escapement, it's an invalid string + if (isNewLine(reader.peek()) && c != "\\"){ + tt = Tokens.INVALID; + break; + } + + //save previous and get next + prev = c; + c = reader.read(); + } + + //if c is null, that means we're out of input and the string was never closed + if (c === null){ + tt = Tokens.INVALID; + } + + return this.createToken(tt, string, startLine, startCol); + }, + + unicodeRangeToken: function(first, startLine, startCol){ + var reader = this._reader, + value = first, + temp, + tt = Tokens.CHAR; + + //then it should be a unicode range + if (reader.peek() == "+"){ + reader.mark(); + value += reader.read(); + value += this.readUnicodeRangePart(true); + + //ensure there's an actual unicode range here + if (value.length == 2){ + reader.reset(); + } else { + + tt = Tokens.UNICODE_RANGE; + + //if there's a ? in the first part, there can't be a second part + if (value.indexOf("?") == -1){ + + if (reader.peek() == "-"){ + reader.mark(); + temp = reader.read(); + temp += this.readUnicodeRangePart(false); + + //if there's not another value, back up and just take the first + if (temp.length == 1){ + reader.reset(); + } else { + value += temp; + } + } + + } + } + } + + return this.createToken(tt, value, startLine, startCol); + }, + + /** + * Produces a S token based on the specified information. Since whitespace + * may have multiple characters, this consumes all whitespace characters + * into a single token. + * @param {String} first The first character in the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method whitespaceToken + */ + whitespaceToken: function(first, startLine, startCol){ + var reader = this._reader, + value = first + this.readWhitespace(); + return this.createToken(Tokens.S, value, startLine, startCol); + }, + + + + + //------------------------------------------------------------------------- + // Methods to read values from the string stream + //------------------------------------------------------------------------- + + readUnicodeRangePart: function(allowQuestionMark){ + var reader = this._reader, + part = "", + c = reader.peek(); + + //first read hex digits + while(isHexDigit(c) && part.length < 6){ + reader.read(); + part += c; + c = reader.peek(); + } + + //then read question marks if allowed + if (allowQuestionMark){ + while(c == "?" && part.length < 6){ + reader.read(); + part += c; + c = reader.peek(); + } + } + + //there can't be any other characters after this point + + return part; + }, + + readWhitespace: function(){ + var reader = this._reader, + whitespace = "", + c = reader.peek(); + + while(isWhitespace(c)){ + reader.read(); + whitespace += c; + c = reader.peek(); + } + + return whitespace; + }, + readNumber: function(first){ + var reader = this._reader, + number = first, + hasDot = (first == "."), + c = reader.peek(); + + + while(c){ + if (isDigit(c)){ + number += reader.read(); + } else if (c == "."){ + if (hasDot){ + break; + } else { + hasDot = true; + number += reader.read(); + } + } else { + break; + } + + c = reader.peek(); + } + + return number; + }, + readString: function(){ + var reader = this._reader, + delim = reader.read(), + string = delim, + prev = delim, + c = reader.peek(); + + while(c){ + c = reader.read(); + string += c; + + //if the delimiter is found with an escapement, we're done. + if (c == delim && prev != "\\"){ + break; + } + + //if there's a newline without an escapement, it's an invalid string + if (isNewLine(reader.peek()) && c != "\\"){ + string = ""; + break; + } + + //save previous and get next + prev = c; + c = reader.peek(); + } + + //if c is null, that means we're out of input and the string was never closed + if (c === null){ + string = ""; + } + + return string; + }, + readURI: function(first){ + var reader = this._reader, + uri = first, + inner = "", + c = reader.peek(); + + reader.mark(); + + //skip whitespace before + while(c && isWhitespace(c)){ + reader.read(); + c = reader.peek(); + } + + //it's a string + if (c == "'" || c == "\""){ + inner = this.readString(); + } else { + inner = this.readURL(); + } + + c = reader.peek(); + + //skip whitespace after + while(c && isWhitespace(c)){ + reader.read(); + c = reader.peek(); + } + + //if there was no inner value or the next character isn't closing paren, it's not a URI + if (inner === "" || c != ")"){ + uri = first; + reader.reset(); + } else { + uri += inner + reader.read(); + } + + return uri; + }, + readURL: function(){ + var reader = this._reader, + url = "", + c = reader.peek(); + + //TODO: Check for escape and nonascii + while (/^[!#$%&\\*-~]$/.test(c)){ + url += reader.read(); + c = reader.peek(); + } + + return url; + + }, + readName: function(first){ + var reader = this._reader, + ident = first || "", + c = reader.peek(); + + while(true){ + if (c == "\\"){ + ident += this.readEscape(reader.read()); + c = reader.peek(); + } else if(c && isNameChar(c)){ + ident += reader.read(); + c = reader.peek(); + } else { + break; + } + } + + return ident; + }, + + readEscape: function(first){ + var reader = this._reader, + cssEscape = first || "", + i = 0, + c = reader.peek(); + + if (isHexDigit(c)){ + do { + cssEscape += reader.read(); + c = reader.peek(); + } while(c && isHexDigit(c) && ++i < 6); + } + + if (cssEscape.length == 3 && /\s/.test(c) || + cssEscape.length == 7 || cssEscape.length == 1){ + reader.read(); + } else { + c = ""; + } + + return cssEscape + c; + }, + + readComment: function(first){ + var reader = this._reader, + comment = first || "", + c = reader.read(); + + if (c == "*"){ + while(c){ + comment += c; + + //look for end of comment + if (comment.length > 2 && c == "*" && reader.peek() == "/"){ + comment += reader.read(); + break; + } + + c = reader.read(); + } + + return comment; + } else { + return ""; + } + + } +}); + + +var Tokens = [ + + /* + * The following token names are defined in CSS3 Grammar: http://www.w3.org/TR/css3-syntax/#lexical + */ + + //HTML-style comments + { name: "CDO"}, + { name: "CDC"}, + + //ignorables + { name: "S", whitespace: true/*, channel: "ws"*/}, + { name: "COMMENT", comment: true, hide: true, channel: "comment" }, + + //attribute equality + { name: "INCLUDES", text: "~="}, + { name: "DASHMATCH", text: "|="}, + { name: "PREFIXMATCH", text: "^="}, + { name: "SUFFIXMATCH", text: "$="}, + { name: "SUBSTRINGMATCH", text: "*="}, + + //identifier types + { name: "STRING"}, + { name: "IDENT"}, + { name: "HASH"}, + + //at-keywords + { name: "IMPORT_SYM", text: "@import"}, + { name: "PAGE_SYM", text: "@page"}, + { name: "MEDIA_SYM", text: "@media"}, + { name: "FONT_FACE_SYM", text: "@font-face"}, + { name: "CHARSET_SYM", text: "@charset"}, + { name: "NAMESPACE_SYM", text: "@namespace"}, + { name: "UNKNOWN_SYM" }, + //{ name: "ATKEYWORD"}, + + //CSS3 animations + { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-ms-keyframes" ] }, + + //important symbol + { name: "IMPORTANT_SYM"}, + + //measurements + { name: "LENGTH"}, + { name: "ANGLE"}, + { name: "TIME"}, + { name: "FREQ"}, + { name: "DIMENSION"}, + { name: "PERCENTAGE"}, + { name: "NUMBER"}, + + //functions + { name: "URI"}, + { name: "FUNCTION"}, + + //Unicode ranges + { name: "UNICODE_RANGE"}, + + /* + * The following token names are defined in CSS3 Selectors: http://www.w3.org/TR/css3-selectors/#selector-syntax + */ + + //invalid string + { name: "INVALID"}, + + //combinators + { name: "PLUS", text: "+" }, + { name: "GREATER", text: ">"}, + { name: "COMMA", text: ","}, + { name: "TILDE", text: "~"}, + + //modifier + { name: "NOT"}, + + /* + * Defined in CSS3 Paged Media + */ + { name: "TOPLEFTCORNER_SYM", text: "@top-left-corner"}, + { name: "TOPLEFT_SYM", text: "@top-left"}, + { name: "TOPCENTER_SYM", text: "@top-center"}, + { name: "TOPRIGHT_SYM", text: "@top-right"}, + { name: "TOPRIGHTCORNER_SYM", text: "@top-right-corner"}, + { name: "BOTTOMLEFTCORNER_SYM", text: "@bottom-left-corner"}, + { name: "BOTTOMLEFT_SYM", text: "@bottom-left"}, + { name: "BOTTOMCENTER_SYM", text: "@bottom-center"}, + { name: "BOTTOMRIGHT_SYM", text: "@bottom-right"}, + { name: "BOTTOMRIGHTCORNER_SYM", text: "@bottom-right-corner"}, + { name: "LEFTTOP_SYM", text: "@left-top"}, + { name: "LEFTMIDDLE_SYM", text: "@left-middle"}, + { name: "LEFTBOTTOM_SYM", text: "@left-bottom"}, + { name: "RIGHTTOP_SYM", text: "@right-top"}, + { name: "RIGHTMIDDLE_SYM", text: "@right-middle"}, + { name: "RIGHTBOTTOM_SYM", text: "@right-bottom"}, + + /* + * The following token names are defined in CSS3 Media Queries: http://www.w3.org/TR/css3-mediaqueries/#syntax + */ + /*{ name: "MEDIA_ONLY", state: "media"}, + { name: "MEDIA_NOT", state: "media"}, + { name: "MEDIA_AND", state: "media"},*/ + { name: "RESOLUTION", state: "media"}, + + /* + * The following token names are not defined in any CSS specification but are used by the lexer. + */ + + //not a real token, but useful for stupid IE filters + { name: "IE_FUNCTION" }, + + //part of CSS3 grammar but not the Flex code + { name: "CHAR" }, + + //TODO: Needed? + //Not defined as tokens, but might as well be + { + name: "PIPE", + text: "|" + }, + { + name: "SLASH", + text: "/" + }, + { + name: "MINUS", + text: "-" + }, + { + name: "STAR", + text: "*" + }, + + { + name: "LBRACE", + text: "{" + }, + { + name: "RBRACE", + text: "}" + }, + { + name: "LBRACKET", + text: "[" + }, + { + name: "RBRACKET", + text: "]" + }, + { + name: "EQUALS", + text: "=" + }, + { + name: "COLON", + text: ":" + }, + { + name: "SEMICOLON", + text: ";" + }, + + { + name: "LPAREN", + text: "(" + }, + { + name: "RPAREN", + text: ")" + }, + { + name: "DOT", + text: "." + } +]; + +(function(){ + + var nameMap = [], + typeMap = {}; + + Tokens.UNKNOWN = -1; + Tokens.unshift({name:"EOF"}); + for (var i=0, len = Tokens.length; i < len; i++){ + nameMap.push(Tokens[i].name); + Tokens[Tokens[i].name] = i; + if (Tokens[i].text){ + if (Tokens[i].text instanceof Array){ + for (var j=0; j < Tokens[i].text.length; j++){ + typeMap[Tokens[i].text[j]] = i; + } + } else { + typeMap[Tokens[i].text] = i; + } + } + } + + Tokens.name = function(tt){ + return nameMap[tt]; + }; + + Tokens.type = function(c){ + return typeMap[c] || -1; + }; + +})(); + + + + +//This file will likely change a lot! Very experimental! +/*global Properties, ValidationTypes, ValidationError, PropertyValueIterator */ +var Validation = { + + validate: function(property, value){ + + //normalize name + var name = property.toString().toLowerCase(), + parts = value.parts, + expression = new PropertyValueIterator(value), + spec = Properties[name], + part, + valid, + j, count, + msg, + types, + last, + literals, + max, multi, group; + + if (!spec) { + if (name.indexOf("-") !== 0){ //vendor prefixed are ok + throw new ValidationError("Unknown property '" + property + "'.", property.line, property.col); + } + } else if (typeof spec != "number"){ + + //initialization + if (typeof spec == "string"){ + if (spec.indexOf("||") > -1) { + this.groupProperty(spec, expression); + } else { + this.singleProperty(spec, expression, 1); + } + + } else if (spec.multi) { + this.multiProperty(spec.multi, expression, spec.comma, spec.max || Infinity); + } else if (typeof spec == "function") { + spec(expression); + } + + } + + }, + + singleProperty: function(types, expression, max, partial) { + + var result = false, + value = expression.value, + count = 0, + part; + + while (expression.hasNext() && count < max) { + result = ValidationTypes.isAny(expression, types); + if (!result) { + break; + } + count++; + } + + if (!result) { + if (expression.hasNext() && !expression.isFirst()) { + part = expression.peek(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col); + } + } else if (expression.hasNext()) { + part = expression.next(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } + + }, + + multiProperty: function (types, expression, comma, max) { + + var result = false, + value = expression.value, + count = 0, + sep = false, + part; + + while(expression.hasNext() && !result && count < max) { + if (ValidationTypes.isAny(expression, types)) { + count++; + if (!expression.hasNext()) { + result = true; + + } else if (comma) { + if (expression.peek() == ",") { + part = expression.next(); + } else { + break; + } + } + } else { + break; + + } + } + + if (!result) { + if (expression.hasNext() && !expression.isFirst()) { + part = expression.peek(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + part = expression.previous(); + if (comma && part == ",") { + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col); + } + } + + } else if (expression.hasNext()) { + part = expression.next(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } + + }, + + groupProperty: function (types, expression, comma) { + + var result = false, + value = expression.value, + typeCount = types.split("||").length, + groups = { count: 0 }, + partial = false, + name, + part; + + while(expression.hasNext() && !result) { + name = ValidationTypes.isAnyOfGroup(expression, types); + if (name) { + + //no dupes + if (groups[name]) { + break; + } else { + groups[name] = 1; + groups.count++; + partial = true; + + if (groups.count == typeCount || !expression.hasNext()) { + result = true; + } + } + } else { + break; + } + } + + if (!result) { + if (partial && expression.hasNext()) { + part = expression.peek(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col); + } + } else if (expression.hasNext()) { + part = expression.next(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } + } + + + +}; +function ValidationError(message, line, col){ + + /** + * The column at which the error occurred. + * @type int + * @property col + */ + this.col = col; + this.line = line; + this.message = message; + +} + +//inherit from Error +ValidationError.prototype = new Error(); +//This file will likely change a lot! Very experimental! +/*global Properties, Validation, ValidationError, PropertyValueIterator, console*/ +var ValidationTypes = { + + isLiteral: function (part, literals) { + var text = part.text.toString().toLowerCase(), + args = literals.split(" | "), + i, len, found = false; + + for (i=0,len=args.length; i < len && !found; i++){ + if (text == args[i]){ + found = true; + } + } + + return found; + }, + + isSimple: function(type) { + return !!this.simple[type]; + }, + + isComplex: function(type) { + return !!this.complex[type]; + }, + + /** + * Determines if the next part(s) of the given expression + * are any of the given types. + */ + isAny: function (expression, types) { + var args = types.split(" | "), + i, len, found = false; + + for (i=0,len=args.length; i < len && !found && expression.hasNext(); i++){ + found = this.isType(expression, args[i]); + } + + return found; + }, + + /** + * Determines if the next part(s) of the given expresion + * are one of a group. + */ + isAnyOfGroup: function(expression, types) { + var args = types.split(" || "), + i, len, found = false; + + for (i=0,len=args.length; i < len && !found; i++){ + found = this.isType(expression, args[i]); + } + + return found ? args[i-1] : false; + }, + + /** + * Determines if the next part(s) of the given expression + * are of a given type. + */ + isType: function (expression, type) { + var part = expression.peek(), + result = false; + + if (type.charAt(0) != "<") { + result = this.isLiteral(part, type); + if (result) { + expression.next(); + } + } else if (this.simple[type]) { + result = this.simple[type](part); + if (result) { + expression.next(); + } + } else { + result = this.complex[type](expression); + } + + return result; + }, + + + + simple: { + + "<absolute-size>": function(part){ + return ValidationTypes.isLiteral(part, "xx-small | x-small | small | medium | large | x-large | xx-large"); + }, + + "<attachment>": function(part){ + return ValidationTypes.isLiteral(part, "scroll | fixed | local"); + }, + + "<attr>": function(part){ + return part.type == "function" && part.name == "attr"; + }, + + "<bg-image>": function(part){ + return this["<image>"](part) || this["<gradient>"](part) || part == "none"; + }, + + "<gradient>": function(part) { + return part.type == "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?(?:repeating\-)?(?:radial|linear)\-gradient/i.test(part); + }, + + "<box>": function(part){ + return ValidationTypes.isLiteral(part, "padding-box | border-box | content-box"); + }, + + "<content>": function(part){ + return part.type == "function" && part.name == "content"; + }, + + "<relative-size>": function(part){ + return ValidationTypes.isLiteral(part, "smaller | larger"); + }, + + //any identifier + "<ident>": function(part){ + return part.type == "identifier"; + }, + + "<length>": function(part){ + return part.type == "length" || part.type == "number" || part.type == "integer" || part == "0"; + }, + + "<color>": function(part){ + return part.type == "color" || part == "transparent"; + }, + + "<number>": function(part){ + return part.type == "number" || this["<integer>"](part); + }, + + "<integer>": function(part){ + return part.type == "integer"; + }, + + "<line>": function(part){ + return part.type == "integer"; + }, + + "<angle>": function(part){ + return part.type == "angle"; + }, + + "<uri>": function(part){ + return part.type == "uri"; + }, + + "<image>": function(part){ + return this["<uri>"](part); + }, + + "<percentage>": function(part){ + return part.type == "percentage" || part == "0"; + }, + + "<border-width>": function(part){ + return this["<length>"](part) || ValidationTypes.isLiteral(part, "thin | medium | thick"); + }, + + "<border-style>": function(part){ + return ValidationTypes.isLiteral(part, "none | hidden | dotted | dashed | solid | double | groove | ridge | inset | outset"); + }, + + "<margin-width>": function(part){ + return this["<length>"](part) || this["<percentage>"](part) || ValidationTypes.isLiteral(part, "auto"); + }, + + "<padding-width>": function(part){ + return this["<length>"](part) || this["<percentage>"](part); + }, + + "<shape>": function(part){ + return part.type == "function" && (part.name == "rect" || part.name == "inset-rect"); + }, + + "<time>": function(part) { + return part.type == "time"; + } + }, + + complex: { + + "<bg-position>": function(expression){ + var types = this, + result = false, + numeric = "<percentage> | <length>", + xDir = "left | center | right", + yDir = "top | center | bottom", + part, + i, len; + + + if (ValidationTypes.isAny(expression, "top | bottom")) { + result = true; + } else { + + //must be two-part + if (ValidationTypes.isAny(expression, numeric)){ + if (expression.hasNext()){ + result = ValidationTypes.isAny(expression, numeric + " | " + yDir); + } + } else if (ValidationTypes.isAny(expression, xDir)){ + if (expression.hasNext()){ + + //two- or three-part + if (ValidationTypes.isAny(expression, yDir)){ + result = true; + + ValidationTypes.isAny(expression, numeric); + + } else if (ValidationTypes.isAny(expression, numeric)){ + + //could also be two-part, so check the next part + if (ValidationTypes.isAny(expression, yDir)){ + ValidationTypes.isAny(expression, numeric); + } + + result = true; + } + } + } + } + + + return result; + }, + + "<bg-size>": function(expression){ + //<bg-size> = [ <length> | <percentage> | auto ]{1,2} | cover | contain + var types = this, + result = false, + numeric = "<percentage> | <length> | auto", + part, + i, len; + + if (ValidationTypes.isAny(expression, "cover | contain")) { + result = true; + } else if (ValidationTypes.isAny(expression, numeric)) { + result = true; + ValidationTypes.isAny(expression, numeric); + } + + return result; + }, + + "<repeat-style>": function(expression){ + //repeat-x | repeat-y | [repeat | space | round | no-repeat]{1,2} + var result = false, + values = "repeat | space | round | no-repeat", + part; + + if (expression.hasNext()){ + part = expression.next(); + + if (ValidationTypes.isLiteral(part, "repeat-x | repeat-y")) { + result = true; + } else if (ValidationTypes.isLiteral(part, values)) { + result = true; + + if (expression.hasNext() && ValidationTypes.isLiteral(expression.peek(), values)) { + expression.next(); + } + } + } + + return result; + + }, + + "<shadow>": function(expression) { + //inset? && [ <length>{2,4} && <color>? ] + var result = false, + count = 0, + inset = false, + color = false, + part; + + if (expression.hasNext()) { + + if (ValidationTypes.isAny(expression, "inset")){ + inset = true; + } + + if (ValidationTypes.isAny(expression, "<color>")) { + color = true; + } + + while (ValidationTypes.isAny(expression, "<length>") && count < 4) { + count++; + } + + + if (expression.hasNext()) { + if (!color) { + ValidationTypes.isAny(expression, "<color>"); + } + + if (!inset) { + ValidationTypes.isAny(expression, "inset"); + } + + } + + result = (count >= 2 && count <= 4); + + } + + return result; + }, + + "<x-one-radius>": function(expression) { + //[ <length> | <percentage> ] [ <length> | <percentage> ]? + var result = false, + count = 0, + numeric = "<length> | <percentage>", + part; + + if (ValidationTypes.isAny(expression, numeric)){ + result = true; + + ValidationTypes.isAny(expression, numeric); + } + + return result; + } + } +}; + + +parserlib.css = { +Colors :Colors, +Combinator :Combinator, +Parser :Parser, +PropertyName :PropertyName, +PropertyValue :PropertyValue, +PropertyValuePart :PropertyValuePart, +MediaFeature :MediaFeature, +MediaQuery :MediaQuery, +Selector :Selector, +SelectorPart :SelectorPart, +SelectorSubPart :SelectorSubPart, +Specificity :Specificity, +TokenStream :TokenStream, +Tokens :Tokens, +ValidationError :ValidationError +}; +})(); +/*global parserlib, Reporter*/ +var CSSLint = (function(){ + + var rules = [], + formatters = [], + api = new parserlib.util.EventTarget(); + + api.version = "0.9.7"; + + //------------------------------------------------------------------------- + // Rule Management + //------------------------------------------------------------------------- + + /** + * Adds a new rule to the engine. + * @param {Object} rule The rule to add. + * @method addRule + */ + api.addRule = function(rule){ + rules.push(rule); + rules[rule.id] = rule; + }; + api.clearRules = function(){ + rules = []; + }; + api.getRules = function(){ + return [].concat(rules).sort(function(a,b){ + return a.id > b.id ? 1 : 0; + }); + }; + + //------------------------------------------------------------------------- + // Formatters + //------------------------------------------------------------------------- + + /** + * Adds a new formatter to the engine. + * @param {Object} formatter The formatter to add. + * @method addFormatter + */ + api.addFormatter = function(formatter) { + // formatters.push(formatter); + formatters[formatter.id] = formatter; + }; + api.getFormatter = function(formatId){ + return formatters[formatId]; + }; + api.format = function(results, filename, formatId, options) { + var formatter = this.getFormatter(formatId), + result = null; + + if (formatter){ + result = formatter.startFormat(); + result += formatter.formatResults(results, filename, options || {}); + result += formatter.endFormat(); + } + + return result; + }; + api.hasFormat = function(formatId){ + return formatters.hasOwnProperty(formatId); + }; + + //------------------------------------------------------------------------- + // Verification + //------------------------------------------------------------------------- + + /** + * Starts the verification process for the given CSS text. + * @param {String} text The CSS text to verify. + * @param {Object} ruleset (Optional) List of rules to apply. If null, then + * all rules are used. If a rule has a value of 1 then it's a warning, + * a value of 2 means it's an error. + * @return {Object} Results of the verification. + * @method verify + */ + api.verify = function(text, ruleset){ + + var i = 0, + len = rules.length, + reporter, + lines, + report, + parser = new parserlib.css.Parser({ starHack: true, ieFilters: true, + underscoreHack: true, strict: false }); + + lines = text.replace(/\n\r?/g, "$split$").split('$split$'); + + if (!ruleset){ + ruleset = {}; + while (i < len){ + ruleset[rules[i++].id] = 1; //by default, everything is a warning + } + } + + reporter = new Reporter(lines, ruleset); + + ruleset.errors = 2; //always report parsing errors as errors + for (i in ruleset){ + if(ruleset.hasOwnProperty(i)){ + if (rules[i]){ + rules[i].init(parser, reporter); + } + } + } + + + //capture most horrible error type + try { + parser.parse(text); + } catch (ex) { + reporter.error("Fatal error, cannot continue: " + ex.message, ex.line, ex.col, {}); + } + + report = { + messages : reporter.messages, + stats : reporter.stats + }; + + //sort by line numbers, rollups at the bottom + report.messages.sort(function (a, b){ + if (a.rollup && !b.rollup){ + return 1; + } else if (!a.rollup && b.rollup){ + return -1; + } else { + return a.line - b.line; + } + }); + + return report; + }; + + //------------------------------------------------------------------------- + // Publish the API + //------------------------------------------------------------------------- + + return api; + +})(); +/** + * An instance of Report is used to report results of the + * verification back to the main API. + * @class Reporter + * @constructor + * @param {String[]} lines The text lines of the source. + * @param {Object} ruleset The set of rules to work with, including if + * they are errors or warnings. + */ +function Reporter(lines, ruleset){ + + /** + * List of messages being reported. + * @property messages + * @type String[] + */ + this.messages = []; + this.stats = []; + this.lines = lines; + this.ruleset = ruleset; +} + +Reporter.prototype = { + + //restore constructor + constructor: Reporter, + + /** + * Report an error. + * @param {String} message The message to store. + * @param {int} line The line number. + * @param {int} col The column number. + * @param {Object} rule The rule this message relates to. + * @method error + */ + error: function(message, line, col, rule){ + this.messages.push({ + type : "error", + line : line, + col : col, + message : message, + evidence: this.lines[line-1], + rule : rule || {} + }); + }, + + /** + * Report an warning. + * @param {String} message The message to store. + * @param {int} line The line number. + * @param {int} col The column number. + * @param {Object} rule The rule this message relates to. + * @method warn + * @deprecated Use report instead. + */ + warn: function(message, line, col, rule){ + this.report(message, line, col, rule); + }, + + /** + * Report an issue. + * @param {String} message The message to store. + * @param {int} line The line number. + * @param {int} col The column number. + * @param {Object} rule The rule this message relates to. + * @method report + */ + report: function(message, line, col, rule){ + this.messages.push({ + type : this.ruleset[rule.id] == 2 ? "error" : "warning", + line : line, + col : col, + message : message, + evidence: this.lines[line-1], + rule : rule + }); + }, + + /** + * Report some informational text. + * @param {String} message The message to store. + * @param {int} line The line number. + * @param {int} col The column number. + * @param {Object} rule The rule this message relates to. + * @method info + */ + info: function(message, line, col, rule){ + this.messages.push({ + type : "info", + line : line, + col : col, + message : message, + evidence: this.lines[line-1], + rule : rule + }); + }, + + /** + * Report some rollup error information. + * @param {String} message The message to store. + * @param {Object} rule The rule this message relates to. + * @method rollupError + */ + rollupError: function(message, rule){ + this.messages.push({ + type : "error", + rollup : true, + message : message, + rule : rule + }); + }, + + /** + * Report some rollup warning information. + * @param {String} message The message to store. + * @param {Object} rule The rule this message relates to. + * @method rollupWarn + */ + rollupWarn: function(message, rule){ + this.messages.push({ + type : "warning", + rollup : true, + message : message, + rule : rule + }); + }, + + /** + * Report a statistic. + * @param {String} name The name of the stat to store. + * @param {Variant} value The value of the stat. + * @method stat + */ + stat: function(name, value){ + this.stats[name] = value; + } +}; + +//expose for testing purposes +CSSLint._Reporter = Reporter; + +/* + * Utility functions that make life easier. + */ +CSSLint.Util = { + /* + * Adds all properties from supplier onto receiver, + * overwriting if the same name already exists on + * reciever. + * @param {Object} The object to receive the properties. + * @param {Object} The object to provide the properties. + * @return {Object} The receiver + */ + mix: function(receiver, supplier){ + var prop; + + for (prop in supplier){ + if (supplier.hasOwnProperty(prop)){ + receiver[prop] = supplier[prop]; + } + } + + return prop; + }, + + /* + * Polyfill for array indexOf() method. + * @param {Array} values The array to search. + * @param {Variant} value The value to search for. + * @return {int} The index of the value if found, -1 if not. + */ + indexOf: function(values, value){ + if (values.indexOf){ + return values.indexOf(value); + } else { + for (var i=0, len=values.length; i < len; i++){ + if (values[i] === value){ + return i; + } + } + return -1; + } + }, + + /* + * Polyfill for array forEach() method. + * @param {Array} values The array to operate on. + * @param {Function} func The function to call on each item. + * @return {void} + */ + forEach: function(values, func) { + if (values.forEach){ + return values.forEach(func); + } else { + for (var i=0, len=values.length; i < len; i++){ + func(values[i], i, values); + } + } + } +}; +/* + * Rule: Don't use adjoining classes (.foo.bar). + */ +CSSLint.addRule({ + + //rule information + id: "adjoining-classes", + name: "Disallow adjoining classes", + desc: "Don't use adjoining classes.", + browsers: "IE6", + + //initialization + init: function(parser, reporter){ + var rule = this; + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + classCount, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part.type == parser.SELECTOR_PART_TYPE){ + classCount = 0; + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (modifier.type == "class"){ + classCount++; + } + if (classCount > 1){ + reporter.report("Don't use adjoining classes.", part.line, part.col, rule); + } + } + } + } + } + }); + } + +}); + +/* + * Rule: Don't use width or height when using padding or border. + */ +CSSLint.addRule({ + + //rule information + id: "box-model", + name: "Beware of broken box size", + desc: "Don't use width or height when using padding or border.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + widthProperties = { + border: 1, + "border-left": 1, + "border-right": 1, + padding: 1, + "padding-left": 1, + "padding-right": 1 + }, + heightProperties = { + border: 1, + "border-bottom": 1, + "border-top": 1, + padding: 1, + "padding-bottom": 1, + "padding-top": 1 + }, + properties; + + function startRule(){ + properties = {}; + } + + function endRule(){ + var prop; + if (properties.height){ + for (prop in heightProperties){ + if (heightProperties.hasOwnProperty(prop) && properties[prop]){ + + //special case for padding + if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[0].value === 0)){ + reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } + } + } + } + + if (properties.width){ + for (prop in widthProperties){ + if (widthProperties.hasOwnProperty(prop) && properties[prop]){ + + if (!(prop == "padding" && properties[prop].value.parts.length === 2 && properties[prop].value.parts[1].value === 0)){ + reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } + } + } + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + parser.addListener("startpage", startRule); + parser.addListener("startpagemargin", startRule); + parser.addListener("startkeyframerule", startRule); + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (heightProperties[name] || widthProperties[name]){ + if (!/^0\S*$/.test(event.value) && !(name == "border" && event.value == "none")){ + properties[name] = { line: event.property.line, col: event.property.col, value: event.value }; + } + } else { + if (name == "width" || name == "height"){ + properties[name] = 1; + } + } + + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + parser.addListener("endpage", endRule); + parser.addListener("endpagemargin", endRule); + parser.addListener("endkeyframerule", endRule); + } + +}); + +/* + * Rule: box-sizing doesn't work in IE6 and IE7. + */ +CSSLint.addRule({ + + //rule information + id: "box-sizing", + name: "Disallow use of box-sizing", + desc: "The box-sizing properties isn't supported in IE6 and IE7.", + browsers: "IE6, IE7", + tags: ["Compatibility"], + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (name == "box-sizing"){ + reporter.report("The box-sizing property isn't supported in IE6 and IE7.", event.line, event.col, rule); + } + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "compatible-vendor-prefixes", + name: "Require compatible vendor prefixes", + desc: "Include all compatible vendor prefixes to reach a wider range of users.", + browsers: "All", + + //initialization + init: function (parser, reporter) { + var rule = this, + compatiblePrefixes, + properties, + prop, + variations, + prefixed, + i, + len, + arrayPush = Array.prototype.push, + applyTo = []; + + // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details + compatiblePrefixes = { + "animation" : "webkit moz ms", + "animation-delay" : "webkit moz ms", + "animation-direction" : "webkit moz ms", + "animation-duration" : "webkit moz ms", + "animation-fill-mode" : "webkit moz ms", + "animation-iteration-count" : "webkit moz ms", + "animation-name" : "webkit moz ms", + "animation-play-state" : "webkit moz ms", + "animation-timing-function" : "webkit moz ms", + "appearance" : "webkit moz", + "border-end" : "webkit moz", + "border-end-color" : "webkit moz", + "border-end-style" : "webkit moz", + "border-end-width" : "webkit moz", + "border-image" : "webkit moz o", + "border-radius" : "webkit moz", + "border-start" : "webkit moz", + "border-start-color" : "webkit moz", + "border-start-style" : "webkit moz", + "border-start-width" : "webkit moz", + "box-align" : "webkit moz ms", + "box-direction" : "webkit moz ms", + "box-flex" : "webkit moz ms", + "box-lines" : "webkit ms", + "box-ordinal-group" : "webkit moz ms", + "box-orient" : "webkit moz ms", + "box-pack" : "webkit moz ms", + "box-sizing" : "webkit moz", + "box-shadow" : "webkit moz", + "column-count" : "webkit moz ms", + "column-gap" : "webkit moz ms", + "column-rule" : "webkit moz ms", + "column-rule-color" : "webkit moz ms", + "column-rule-style" : "webkit moz ms", + "column-rule-width" : "webkit moz ms", + "column-width" : "webkit moz ms", + "hyphens" : "epub moz", + "line-break" : "webkit ms", + "margin-end" : "webkit moz", + "margin-start" : "webkit moz", + "marquee-speed" : "webkit wap", + "marquee-style" : "webkit wap", + "padding-end" : "webkit moz", + "padding-start" : "webkit moz", + "tab-size" : "moz o", + "text-size-adjust" : "webkit ms", + "transform" : "webkit moz ms o", + "transform-origin" : "webkit moz ms o", + "transition" : "webkit moz o ms", + "transition-delay" : "webkit moz o ms", + "transition-duration" : "webkit moz o ms", + "transition-property" : "webkit moz o ms", + "transition-timing-function" : "webkit moz o ms", + "user-modify" : "webkit moz", + "user-select" : "webkit moz ms", + "word-break" : "epub ms", + "writing-mode" : "epub ms" + }; + + + for (prop in compatiblePrefixes) { + if (compatiblePrefixes.hasOwnProperty(prop)) { + variations = []; + prefixed = compatiblePrefixes[prop].split(' '); + for (i = 0, len = prefixed.length; i < len; i++) { + variations.push('-' + prefixed[i] + '-' + prop); + } + compatiblePrefixes[prop] = variations; + arrayPush.apply(applyTo, variations); + } + } + parser.addListener("startrule", function () { + properties = []; + }); + + parser.addListener("property", function (event) { + var name = event.property; + if (CSSLint.Util.indexOf(applyTo, name.text) > -1) { + properties.push(name); + } + }); + + parser.addListener("endrule", function (event) { + if (!properties.length) { + return; + } + + var propertyGroups = {}, + i, + len, + name, + prop, + variations, + value, + full, + actual, + item, + propertiesSpecified; + + for (i = 0, len = properties.length; i < len; i++) { + name = properties[i]; + + for (prop in compatiblePrefixes) { + if (compatiblePrefixes.hasOwnProperty(prop)) { + variations = compatiblePrefixes[prop]; + if (CSSLint.Util.indexOf(variations, name.text) > -1) { + if (!propertyGroups[prop]) { + propertyGroups[prop] = { + full : variations.slice(0), + actual : [], + actualNodes: [] + }; + } + if (CSSLint.Util.indexOf(propertyGroups[prop].actual, name.text) === -1) { + propertyGroups[prop].actual.push(name.text); + propertyGroups[prop].actualNodes.push(name); + } + } + } + } + } + + for (prop in propertyGroups) { + if (propertyGroups.hasOwnProperty(prop)) { + value = propertyGroups[prop]; + full = value.full; + actual = value.actual; + + if (full.length > actual.length) { + for (i = 0, len = full.length; i < len; i++) { + item = full[i]; + if (CSSLint.Util.indexOf(actual, item) === -1) { + propertiesSpecified = (actual.length === 1) ? actual[0] : (actual.length == 2) ? actual.join(" and ") : actual.join(", "); + reporter.report("The property " + item + " is compatible with " + propertiesSpecified + " and should be included as well.", value.actualNodes[0].line, value.actualNodes[0].col, rule); + } + } + + } + } + } + }); + } +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "display-property-grouping", + name: "Require properties appropriate for display", + desc: "Certain properties shouldn't be used with certain display property values.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + var propertiesToCheck = { + display: 1, + "float": "none", + height: 1, + width: 1, + margin: 1, + "margin-left": 1, + "margin-right": 1, + "margin-bottom": 1, + "margin-top": 1, + padding: 1, + "padding-left": 1, + "padding-right": 1, + "padding-bottom": 1, + "padding-top": 1, + "vertical-align": 1 + }, + properties; + + function reportProperty(name, display, msg){ + if (properties[name]){ + if (typeof propertiesToCheck[name] != "string" || properties[name].value.toLowerCase() != propertiesToCheck[name]){ + reporter.report(msg || name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule); + } + } + } + + function startRule(){ + properties = {}; + } + + function endRule(){ + + var display = properties.display ? properties.display.value : null; + if (display){ + switch(display){ + + case "inline": + //height, width, margin-top, margin-bottom, float should not be used with inline + reportProperty("height", display); + reportProperty("width", display); + reportProperty("margin", display); + reportProperty("margin-top", display); + reportProperty("margin-bottom", display); + reportProperty("float", display, "display:inline has no effect on floated elements (but may be used to fix the IE6 double-margin bug)."); + break; + + case "block": + //vertical-align should not be used with block + reportProperty("vertical-align", display); + break; + + case "inline-block": + //float should not be used with inline-block + reportProperty("float", display); + break; + + default: + //margin, float should not be used with table + if (display.indexOf("table-") === 0){ + reportProperty("margin", display); + reportProperty("margin-left", display); + reportProperty("margin-right", display); + reportProperty("margin-top", display); + reportProperty("margin-bottom", display); + reportProperty("float", display); + } + + //otherwise do nothing + } + } + + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + parser.addListener("startkeyframerule", startRule); + parser.addListener("startpagemargin", startRule); + parser.addListener("startpage", startRule); + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (propertiesToCheck[name]){ + properties[name] = { value: event.value.text, line: event.property.line, col: event.property.col }; + } + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + parser.addListener("endkeyframerule", endRule); + parser.addListener("endpagemargin", endRule); + parser.addListener("endpage", endRule); + + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "duplicate-background-images", + name: "Disallow duplicate background images", + desc: "Every background-image should be unique. Use a common class for e.g. sprites.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + stack = {}; + + parser.addListener("property", function(event){ + var name = event.property.text, + value = event.value, + i, len; + + if (name.match(/background/i)) { + for (i=0, len=value.parts.length; i < len; i++) { + if (value.parts[i].type == 'uri') { + if (typeof stack[value.parts[i].uri] === 'undefined') { + stack[value.parts[i].uri] = event; + } + else { + 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); + } + } + } + } + }); + } +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "duplicate-properties", + name: "Disallow duplicate properties", + desc: "Duplicate properties must appear one after the other.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + properties, + lastProperty; + + function startRule(event){ + properties = {}; + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + parser.addListener("startpage", startRule); + parser.addListener("startpagemargin", startRule); + parser.addListener("startkeyframerule", startRule); + + parser.addListener("property", function(event){ + var property = event.property, + name = property.text.toLowerCase(); + + if (properties[name] && (lastProperty != name || properties[name] == event.value.text)){ + reporter.report("Duplicate property '" + event.property + "' found.", event.line, event.col, rule); + } + + properties[name] = event.value.text; + lastProperty = name; + + }); + + + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "empty-rules", + name: "Disallow empty rules", + desc: "Rules without any properties specified should be removed.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + parser.addListener("startrule", function(){ + count=0; + }); + + parser.addListener("property", function(){ + count++; + }); + + parser.addListener("endrule", function(event){ + var selectors = event.selectors; + if (count === 0){ + reporter.report("Rule is empty.", selectors[0].line, selectors[0].col, rule); + } + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "errors", + name: "Parsing Errors", + desc: "This rule looks for recoverable syntax errors.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("error", function(event){ + reporter.error(event.message, event.line, event.col, rule); + }); + + } + +}); +CSSLint.addRule({ + + //rule information + id: "fallback-colors", + name: "Require fallback colors", + desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.", + browsers: "IE6,IE7,IE8", + + //initialization + init: function(parser, reporter){ + var rule = this, + lastProperty, + propertiesToCheck = { + color: 1, + background: 1, + "background-color": 1 + }, + properties; + + function startRule(event){ + properties = {}; + lastProperty = null; + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + parser.addListener("startpage", startRule); + parser.addListener("startpagemargin", startRule); + parser.addListener("startkeyframerule", startRule); + + parser.addListener("property", function(event){ + var property = event.property, + name = property.text.toLowerCase(), + parts = event.value.parts, + i = 0, + colorType = "", + len = parts.length; + + if(propertiesToCheck[name]){ + while(i < len){ + if (parts[i].type == "color"){ + if ("alpha" in parts[i] || "hue" in parts[i]){ + + if (/([^\)]+)\(/.test(parts[i])){ + colorType = RegExp.$1.toUpperCase(); + } + + if (!lastProperty || (lastProperty.property.text.toLowerCase() != name || lastProperty.colorType != "compat")){ + reporter.report("Fallback " + name + " (hex or RGB) should precede " + colorType + " " + name + ".", event.line, event.col, rule); + } + } else { + event.colorType = "compat"; + } + } + + i++; + } + } + + lastProperty = event; + }); + + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "floats", + name: "Disallow too many floats", + desc: "This rule tests if the float property is used too many times", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + var count = 0; + + //count how many times "float" is used + parser.addListener("property", function(event){ + if (event.property.text.toLowerCase() == "float" && + event.value.text.toLowerCase() != "none"){ + count++; + } + }); + + //report the results + parser.addListener("endstylesheet", function(){ + reporter.stat("floats", count); + if (count >= 10){ + reporter.rollupWarn("Too many floats (" + count + "), you're probably using them for layout. Consider using a grid system instead.", rule); + } + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "font-faces", + name: "Don't use too many web fonts", + desc: "Too many different web fonts in the same stylesheet.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + + parser.addListener("startfontface", function(){ + count++; + }); + + parser.addListener("endstylesheet", function(){ + if (count > 5){ + reporter.rollupWarn("Too many @font-face declarations (" + count + ").", rule); + } + }); + } + +}); + +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "font-sizes", + name: "Disallow too many font sizes", + desc: "Checks the number of font-size declarations.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + //check for use of "font-size" + parser.addListener("property", function(event){ + if (event.property == "font-size"){ + count++; + } + }); + + //report the results + parser.addListener("endstylesheet", function(){ + reporter.stat("font-sizes", count); + if (count >= 10){ + reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule); + } + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "gradients", + name: "Require all gradient definitions", + desc: "When using a vendor-prefixed gradient, make sure to use them all.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + gradients; + + parser.addListener("startrule", function(){ + gradients = { + moz: 0, + webkit: 0, + oldWebkit: 0, + ms: 0, + o: 0 + }; + }); + + parser.addListener("property", function(event){ + + if (/\-(moz|ms|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)){ + gradients[RegExp.$1] = 1; + } else if (/\-webkit\-gradient/i.test(event.value)){ + gradients.oldWebkit = 1; + } + + }); + + parser.addListener("endrule", function(event){ + var missing = []; + + if (!gradients.moz){ + missing.push("Firefox 3.6+"); + } + + if (!gradients.webkit){ + missing.push("Webkit (Safari 5+, Chrome)"); + } + + if (!gradients.oldWebkit){ + missing.push("Old Webkit (Safari 4+, Chrome)"); + } + + if (!gradients.ms){ + missing.push("Internet Explorer 10+"); + } + + if (!gradients.o){ + missing.push("Opera 11.1+"); + } + + if (missing.length && missing.length < 5){ + reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule); + } + + }); + + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "ids", + name: "Disallow IDs in selectors", + desc: "Selectors should not contain IDs.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + idCount, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + idCount = 0; + + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part.type == parser.SELECTOR_PART_TYPE){ + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (modifier.type == "id"){ + idCount++; + } + } + } + } + + if (idCount == 1){ + reporter.report("Don't use IDs in selectors.", selector.line, selector.col, rule); + } else if (idCount > 1){ + reporter.report(idCount + " IDs in the selector, really?", selector.line, selector.col, rule); + } + } + + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "import", + name: "Disallow @import", + desc: "Don't use @import, use <link> instead.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("import", function(event){ + reporter.report("@import prevents parallel downloads, use <link> instead.", event.line, event.col, rule); + }); + + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "important", + name: "Disallow !important", + desc: "Be careful when using !important declaration", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + //warn that important is used and increment the declaration counter + parser.addListener("property", function(event){ + if (event.important === true){ + count++; + reporter.report("Use of !important", event.line, event.col, rule); + } + }); + + //if there are more than 10, show an error + parser.addListener("endstylesheet", function(){ + reporter.stat("important", count); + if (count >= 10){ + reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specifity issues.", rule); + } + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "known-properties", + name: "Require use of known properties", + desc: "Properties should be known (listed in CSS specification) or be a vendor-prefixed property.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + properties = { + + "alignment-adjust": 1, + "alignment-baseline": 1, + "animation": 1, + "animation-delay": 1, + "animation-direction": 1, + "animation-duration": 1, + "animation-fill-mode": 1, + "animation-iteration-count": 1, + "animation-name": 1, + "animation-play-state": 1, + "animation-timing-function": 1, + "appearance": 1, + "azimuth": 1, + "backface-visibility": 1, + "background": 1, + "background-attachment": 1, + "background-break": 1, + "background-clip": 1, + "background-color": 1, + "background-image": 1, + "background-origin": 1, + "background-position": 1, + "background-repeat": 1, + "background-size": 1, + "baseline-shift": 1, + "binding": 1, + "bleed": 1, + "bookmark-label": 1, + "bookmark-level": 1, + "bookmark-state": 1, + "bookmark-target": 1, + "border": 1, + "border-bottom": 1, + "border-bottom-color": 1, + "border-bottom-left-radius": 1, + "border-bottom-right-radius": 1, + "border-bottom-style": 1, + "border-bottom-width": 1, + "border-collapse": 1, + "border-color": 1, + "border-image": 1, + "border-image-outset": 1, + "border-image-repeat": 1, + "border-image-slice": 1, + "border-image-source": 1, + "border-image-width": 1, + "border-left": 1, + "border-left-color": 1, + "border-left-style": 1, + "border-left-width": 1, + "border-radius": 1, + "border-right": 1, + "border-right-color": 1, + "border-right-style": 1, + "border-right-width": 1, + "border-spacing": 1, + "border-style": 1, + "border-top": 1, + "border-top-color": 1, + "border-top-left-radius": 1, + "border-top-right-radius": 1, + "border-top-style": 1, + "border-top-width": 1, + "border-width": 1, + "bottom": 1, + "box-align": 1, + "box-decoration-break": 1, + "box-direction": 1, + "box-flex": 1, + "box-flex-group": 1, + "box-lines": 1, + "box-ordinal-group": 1, + "box-orient": 1, + "box-pack": 1, + "box-shadow": 1, + "box-sizing": 1, + "break-after": 1, + "break-before": 1, + "break-inside": 1, + "caption-side": 1, + "clear": 1, + "clip": 1, + "color": 1, + "color-profile": 1, + "column-count": 1, + "column-fill": 1, + "column-gap": 1, + "column-rule": 1, + "column-rule-color": 1, + "column-rule-style": 1, + "column-rule-width": 1, + "column-span": 1, + "column-width": 1, + "columns": 1, + "content": 1, + "counter-increment": 1, + "counter-reset": 1, + "crop": 1, + "cue": 1, + "cue-after": 1, + "cue-before": 1, + "cursor": 1, + "direction": 1, + "display": 1, + "dominant-baseline": 1, + "drop-initial-after-adjust": 1, + "drop-initial-after-align": 1, + "drop-initial-before-adjust": 1, + "drop-initial-before-align": 1, + "drop-initial-size": 1, + "drop-initial-value": 1, + "elevation": 1, + "empty-cells": 1, + "fit": 1, + "fit-position": 1, + "float": 1, + "float-offset": 1, + "font": 1, + "font-family": 1, + "font-size": 1, + "font-size-adjust": 1, + "font-stretch": 1, + "font-style": 1, + "font-variant": 1, + "font-weight": 1, + "grid-columns": 1, + "grid-rows": 1, + "hanging-punctuation": 1, + "height": 1, + "hyphenate-after": 1, + "hyphenate-before": 1, + "hyphenate-character": 1, + "hyphenate-lines": 1, + "hyphenate-resource": 1, + "hyphens": 1, + "icon": 1, + "image-orientation": 1, + "image-rendering": 1, + "image-resolution": 1, + "inline-box-align": 1, + "left": 1, + "letter-spacing": 1, + "line-height": 1, + "line-stacking": 1, + "line-stacking-ruby": 1, + "line-stacking-shift": 1, + "line-stacking-strategy": 1, + "list-style": 1, + "list-style-image": 1, + "list-style-position": 1, + "list-style-type": 1, + "margin": 1, + "margin-bottom": 1, + "margin-left": 1, + "margin-right": 1, + "margin-top": 1, + "mark": 1, + "mark-after": 1, + "mark-before": 1, + "marks": 1, + "marquee-direction": 1, + "marquee-play-count": 1, + "marquee-speed": 1, + "marquee-style": 1, + "max-height": 1, + "max-width": 1, + "min-height": 1, + "min-width": 1, + "move-to": 1, + "nav-down": 1, + "nav-index": 1, + "nav-left": 1, + "nav-right": 1, + "nav-up": 1, + "opacity": 1, + "orphans": 1, + "outline": 1, + "outline-color": 1, + "outline-offset": 1, + "outline-style": 1, + "outline-width": 1, + "overflow": 1, + "overflow-style": 1, + "overflow-x": 1, + "overflow-y": 1, + "padding": 1, + "padding-bottom": 1, + "padding-left": 1, + "padding-right": 1, + "padding-top": 1, + "page": 1, + "page-break-after": 1, + "page-break-before": 1, + "page-break-inside": 1, + "page-policy": 1, + "pause": 1, + "pause-after": 1, + "pause-before": 1, + "perspective": 1, + "perspective-origin": 1, + "phonemes": 1, + "pitch": 1, + "pitch-range": 1, + "play-during": 1, + "position": 1, + "presentation-level": 1, + "punctuation-trim": 1, + "quotes": 1, + "rendering-intent": 1, + "resize": 1, + "rest": 1, + "rest-after": 1, + "rest-before": 1, + "richness": 1, + "right": 1, + "rotation": 1, + "rotation-point": 1, + "ruby-align": 1, + "ruby-overhang": 1, + "ruby-position": 1, + "ruby-span": 1, + "size": 1, + "speak": 1, + "speak-header": 1, + "speak-numeral": 1, + "speak-punctuation": 1, + "speech-rate": 1, + "stress": 1, + "string-set": 1, + "table-layout": 1, + "target": 1, + "target-name": 1, + "target-new": 1, + "target-position": 1, + "text-align": 1, + "text-align-last": 1, + "text-decoration": 1, + "text-emphasis": 1, + "text-height": 1, + "text-indent": 1, + "text-justify": 1, + "text-outline": 1, + "text-shadow": 1, + "text-transform": 1, + "text-wrap": 1, + "top": 1, + "transform": 1, + "transform-origin": 1, + "transform-style": 1, + "transition": 1, + "transition-delay": 1, + "transition-duration": 1, + "transition-property": 1, + "transition-timing-function": 1, + "unicode-bidi": 1, + "user-modify": 1, + "user-select": 1, + "vertical-align": 1, + "visibility": 1, + "voice-balance": 1, + "voice-duration": 1, + "voice-family": 1, + "voice-pitch": 1, + "voice-pitch-range": 1, + "voice-rate": 1, + "voice-stress": 1, + "voice-volume": 1, + "volume": 1, + "white-space": 1, + "white-space-collapse": 1, + "widows": 1, + "width": 1, + "word-break": 1, + "word-spacing": 1, + "word-wrap": 1, + "z-index": 1, + + //IE + "filter": 1, + "zoom": 1, + + //@font-face + "src": 1 + }; + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (event.invalid) { + reporter.report(event.invalid.message, event.line, event.col, rule); + } + //if (!properties[name] && name.charAt(0) != "-"){ + // reporter.error("Unknown property '" + event.property + "'.", event.line, event.col, rule); + //} + + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "outline-none", + name: "Disallow outline: none", + desc: "Use of outline: none or outline: 0 should be limited to :focus rules.", + browsers: "All", + tags: ["Accessibility"], + + //initialization + init: function(parser, reporter){ + var rule = this, + lastRule; + + function startRule(event){ + if (event.selectors){ + lastRule = { + line: event.line, + col: event.col, + selectors: event.selectors, + propCount: 0, + outline: false + }; + } else { + lastRule = null; + } + } + + function endRule(event){ + if (lastRule){ + if (lastRule.outline){ + if (lastRule.selectors.toString().toLowerCase().indexOf(":focus") == -1){ + reporter.report("Outlines should only be modified using :focus.", lastRule.line, lastRule.col, rule); + } else if (lastRule.propCount == 1) { + reporter.report("Outlines shouldn't be hidden unless other visual changes are made.", lastRule.line, lastRule.col, rule); + } + } + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + parser.addListener("startpage", startRule); + parser.addListener("startpagemargin", startRule); + parser.addListener("startkeyframerule", startRule); + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(), + value = event.value; + + if (lastRule){ + lastRule.propCount++; + if (name == "outline" && (value == "none" || value == "0")){ + lastRule.outline = true; + } + } + + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + parser.addListener("endpage", endRule); + parser.addListener("endpagemargin", endRule); + parser.addListener("endkeyframerule", endRule); + + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "overqualified-elements", + name: "Disallow overqualified elements", + desc: "Don't use classes or IDs with elements (a.foo or a#foo).", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + classes = {}; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part.type == parser.SELECTOR_PART_TYPE){ + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (part.elementName && modifier.type == "id"){ + reporter.report("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule); + } else if (modifier.type == "class"){ + + if (!classes[modifier]){ + classes[modifier] = []; + } + classes[modifier].push({ modifier: modifier, part: part }); + } + } + } + } + } + }); + + parser.addListener("endstylesheet", function(){ + + var prop; + for (prop in classes){ + if (classes.hasOwnProperty(prop)){ + + //one use means that this is overqualified + if (classes[prop].length == 1 && classes[prop][0].part.elementName){ + 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); + } + } + } + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "qualified-headings", + name: "Disallow qualified headings", + desc: "Headings should not be qualified (namespaced).", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + i, j; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part.type == parser.SELECTOR_PART_TYPE){ + if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0){ + reporter.report("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule); + } + } + } + } + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "regex-selectors", + name: "Disallow selectors that look like regexs", + desc: "Selectors that look like regular expressions are slow and should be avoided.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part.type == parser.SELECTOR_PART_TYPE){ + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (modifier.type == "attribute"){ + if (/([\~\|\^\$\*]=)/.test(modifier)){ + reporter.report("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule); + } + } + + } + } + } + } + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "rules-count", + name: "Rules Count", + desc: "Track how many rules there are.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + //count each rule + parser.addListener("startrule", function(){ + count++; + }); + + parser.addListener("endstylesheet", function(){ + reporter.stat("rule-count", count); + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "shorthand", + name: "Require shorthand properties", + desc: "Use shorthand properties where possible.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + prop, i, len, + propertiesToCheck = {}, + properties, + mapping = { + "margin": [ + "margin-top", + "margin-bottom", + "margin-left", + "margin-right" + ], + "padding": [ + "padding-top", + "padding-bottom", + "padding-left", + "padding-right" + ] + }; + + //initialize propertiesToCheck + for (prop in mapping){ + if (mapping.hasOwnProperty(prop)){ + for (i=0, len=mapping[prop].length; i < len; i++){ + propertiesToCheck[mapping[prop][i]] = prop; + } + } + } + + function startRule(event){ + properties = {}; + } + + //event handler for end of rules + function endRule(event){ + + var prop, i, len, total; + + //check which properties this rule has + for (prop in mapping){ + if (mapping.hasOwnProperty(prop)){ + total=0; + + for (i=0, len=mapping[prop].length; i < len; i++){ + total += properties[mapping[prop][i]] ? 1 : 0; + } + + if (total == mapping[prop].length){ + reporter.report("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule); + } + } + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + + //check for use of "font-size" + parser.addListener("property", function(event){ + var name = event.property.toString().toLowerCase(), + value = event.value.parts[0].value; + + if (propertiesToCheck[name]){ + properties[name] = 1; + } + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "text-indent", + name: "Disallow negative text-indent", + desc: "Checks for text indent less than -99px", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + textIndent = false; + + + function startRule(event){ + textIndent = false; + } + + //event handler for end of rules + function endRule(event){ + if (textIndent){ + 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); + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + + //check for use of "font-size" + parser.addListener("property", function(event){ + var name = event.property.toString().toLowerCase(), + value = event.value; + + if (name == "text-indent" && value.parts[0].value < -99){ + textIndent = event.property; + } else if (name == "direction" && value == "ltr"){ + textIndent = false; + } + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "unique-headings", + name: "Headings should only be defined once", + desc: "Headings should be defined only once.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + var headings = { + h1: 0, + h2: 0, + h3: 0, + h4: 0, + h5: 0, + h6: 0 + }; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + pseudo, + i, j; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + part = selector.parts[selector.parts.length-1]; + + if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())){ + + for (j=0; j < part.modifiers.length; j++){ + if (part.modifiers[j].type == "pseudo"){ + pseudo = true; + break; + } + } + + if (!pseudo){ + headings[RegExp.$1]++; + if (headings[RegExp.$1] > 1) { + reporter.report("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule); + } + } + } + } + }); + + parser.addListener("endstylesheet", function(event){ + var prop, + messages = []; + + for (prop in headings){ + if (headings.hasOwnProperty(prop)){ + if (headings[prop] > 1){ + messages.push(headings[prop] + " " + prop + "s"); + } + } + } + + if (messages.length){ + reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule); + } + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "universal-selector", + name: "Disallow universal selector", + desc: "The universal selector (*) is known to be slow.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + + part = selector.parts[selector.parts.length-1]; + if (part.elementName == "*"){ + reporter.report(rule.desc, part.line, part.col, rule); + } + } + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "unqualified-attributes", + name: "Disallow unqualified attribute selectors", + desc: "Unqualified attribute selectors are known to be slow.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("startrule", function(event){ + + var selectors = event.selectors, + selector, + part, + modifier, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + + part = selector.parts[selector.parts.length-1]; + if (part.type == parser.SELECTOR_PART_TYPE){ + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (modifier.type == "attribute" && (!part.elementName || part.elementName == "*")){ + reporter.report(rule.desc, part.line, part.col, rule); + } + } + } + + } + }); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "vendor-prefix", + name: "Require standard property with vendor prefix", + desc: "When using a vendor-prefixed property, make sure to include the standard one.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + properties, + num, + propertiesToCheck = { + "-webkit-border-radius": "border-radius", + "-webkit-border-top-left-radius": "border-top-left-radius", + "-webkit-border-top-right-radius": "border-top-right-radius", + "-webkit-border-bottom-left-radius": "border-bottom-left-radius", + "-webkit-border-bottom-right-radius": "border-bottom-right-radius", + + "-o-border-radius": "border-radius", + "-o-border-top-left-radius": "border-top-left-radius", + "-o-border-top-right-radius": "border-top-right-radius", + "-o-border-bottom-left-radius": "border-bottom-left-radius", + "-o-border-bottom-right-radius": "border-bottom-right-radius", + + "-moz-border-radius": "border-radius", + "-moz-border-radius-topleft": "border-top-left-radius", + "-moz-border-radius-topright": "border-top-right-radius", + "-moz-border-radius-bottomleft": "border-bottom-left-radius", + "-moz-border-radius-bottomright": "border-bottom-right-radius", + + "-moz-column-count": "column-count", + "-webkit-column-count": "column-count", + + "-moz-column-gap": "column-gap", + "-webkit-column-gap": "column-gap", + + "-moz-column-rule": "column-rule", + "-webkit-column-rule": "column-rule", + + "-moz-column-rule-style": "column-rule-style", + "-webkit-column-rule-style": "column-rule-style", + + "-moz-column-rule-color": "column-rule-color", + "-webkit-column-rule-color": "column-rule-color", + + "-moz-column-rule-width": "column-rule-width", + "-webkit-column-rule-width": "column-rule-width", + + "-moz-column-width": "column-width", + "-webkit-column-width": "column-width", + + "-webkit-column-span": "column-span", + "-webkit-columns": "columns", + + "-moz-box-shadow": "box-shadow", + "-webkit-box-shadow": "box-shadow", + + "-moz-transform" : "transform", + "-webkit-transform" : "transform", + "-o-transform" : "transform", + "-ms-transform" : "transform", + + "-moz-transform-origin" : "transform-origin", + "-webkit-transform-origin" : "transform-origin", + "-o-transform-origin" : "transform-origin", + "-ms-transform-origin" : "transform-origin", + + "-moz-box-sizing" : "box-sizing", + "-webkit-box-sizing" : "box-sizing", + + "-moz-user-select" : "user-select", + "-khtml-user-select" : "user-select", + "-webkit-user-select" : "user-select" + }; + + //event handler for beginning of rules + function startRule(){ + properties = {}; + num=1; + } + + //event handler for end of rules + function endRule(event){ + var prop, + i, len, + standard, + needed, + actual, + needsStandard = []; + + for (prop in properties){ + if (propertiesToCheck[prop]){ + needsStandard.push({ actual: prop, needed: propertiesToCheck[prop]}); + } + } + + for (i=0, len=needsStandard.length; i < len; i++){ + needed = needsStandard[i].needed; + actual = needsStandard[i].actual; + + if (!properties[needed]){ + reporter.report("Missing standard property '" + needed + "' to go along with '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule); + } else { + //make sure standard property is last + if (properties[needed][0].pos < properties[actual][0].pos){ + reporter.report("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule); + } + } + } + + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + parser.addListener("startpage", startRule); + parser.addListener("startpagemargin", startRule); + parser.addListener("startkeyframerule", startRule); + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (!properties[name]){ + properties[name] = []; + } + + properties[name].push({ name: event.property, value : event.value, pos:num++ }); + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + parser.addListener("endpage", endRule); + parser.addListener("endpagemargin", endRule); + parser.addListener("endkeyframerule", endRule); + } + +}); +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "zero-units", + name: "Disallow units for 0 values", + desc: "You don't need to specify units when a value is 0.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + //count how many times "float" is used + parser.addListener("property", function(event){ + var parts = event.value.parts, + i = 0, + len = parts.length; + + while(i < len){ + if ((parts[i].units || parts[i].type == "percentage") && parts[i].value === 0 && parts[i].type != "time"){ + reporter.report("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule); + } + i++; + } + + }); + + } + +}); +CSSLint.addFormatter({ + //format information + id: "checkstyle-xml", + name: "Checkstyle XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle>"; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function(){ + return "</checkstyle>"; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (UNUSED for now) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + var messages = results.messages, + output = []; + var generateSource = function(rule) { + if (!rule || !('name' in rule)) { + return ""; + } + return 'net.csslint.' + rule.name.replace(/\s/g,''); + }; + var escapeSpecialCharacters = function(str) { + if (!str || str.constructor !== String) { + return ""; + } + return str.replace(/\"/g, "'").replace(/</g, "<").replace(/>/g, ">"); + }; + + if (messages.length > 0) { + output.push("<file name=\""+filename+"\">"); + CSSLint.Util.forEach(messages, function (message, i) { + //ignore rollups for now + if (!message.rollup) { + output.push("<error line=\"" + message.line + "\" column=\"" + message.col + "\" severity=\"" + message.type + "\"" + + " message=\"" + escapeSpecialCharacters(message.message) + "\" source=\"" + generateSource(message.rule) +"\"/>"); + } + }); + output.push("</file>"); + } + + return output.join(""); + } +}); +CSSLint.addFormatter({ + //format information + id: "compact", + name: "Compact, 'porcelain' format", + + /** + * Return content to be printed before all file results. + * @return {String} to prepend before all results + */ + startFormat: function() { + return ""; + }, + + /** + * Return content to be printed after all file results. + * @return {String} to append after all results + */ + endFormat: function() { + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (Optional) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + var messages = results.messages, + output = ""; + options = options || {}; + var capitalize = function(str) { + return str.charAt(0).toUpperCase() + str.slice(1); + }; + + if (messages.length === 0) { + return options.quiet ? "" : filename + ": Lint Free!"; + } + + CSSLint.Util.forEach(messages, function(message, i) { + if (message.rollup) { + output += filename + ": " + capitalize(message.type) + " - " + message.message + "\n"; + } else { + output += filename + ": " + "line " + message.line + + ", col " + message.col + ", " + capitalize(message.type) + " - " + message.message + "\n"; + } + }); + + return output; + } +}); +CSSLint.addFormatter({ + //format information + id: "csslint-xml", + name: "CSSLint XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return "<?xml version=\"1.0\" encoding=\"utf-8\"?><csslint>"; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function(){ + return "</csslint>"; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (UNUSED for now) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + var messages = results.messages, + output = []; + var escapeSpecialCharacters = function(str) { + if (!str || str.constructor !== String) { + return ""; + } + return str.replace(/\"/g, "'").replace(/</g, "<").replace(/>/g, ">"); + }; + + if (messages.length > 0) { + output.push("<file name=\""+filename+"\">"); + CSSLint.Util.forEach(messages, function (message, i) { + if (message.rollup) { + output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>"); + } else { + output.push("<issue line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" + + " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>"); + } + }); + output.push("</file>"); + } + + return output.join(""); + } +}); +CSSLint.addFormatter({ + //format information + id: "lint-xml", + name: "Lint XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return "<?xml version=\"1.0\" encoding=\"utf-8\"?><lint>"; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function(){ + return "</lint>"; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (UNUSED for now) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + var messages = results.messages, + output = []; + var escapeSpecialCharacters = function(str) { + if (!str || str.constructor !== String) { + return ""; + } + return str.replace(/\"/g, "'").replace(/</g, "<").replace(/>/g, ">"); + }; + + if (messages.length > 0) { + + output.push("<file name=\""+filename+"\">"); + CSSLint.Util.forEach(messages, function (message, i) { + if (message.rollup) { + output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>"); + } else { + output.push("<issue line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" + + " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>"); + } + }); + output.push("</file>"); + } + + return output.join(""); + } +}); +CSSLint.addFormatter({ + //format information + id: "text", + name: "Plain Text", + + /** + * Return content to be printed before all file results. + * @return {String} to prepend before all results + */ + startFormat: function() { + return ""; + }, + + /** + * Return content to be printed after all file results. + * @return {String} to append after all results + */ + endFormat: function() { + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (Optional) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + var messages = results.messages, + output = ""; + options = options || {}; + + if (messages.length === 0) { + return options.quiet ? "" : "\n\ncsslint: No errors in " + filename + "."; + } + + output = "\n\ncsslint: There are " + messages.length + " problems in " + filename + "."; + var pos = filename.lastIndexOf("/"), + shortFilename = filename; + + if (pos === -1){ + pos = filename.lastIndexOf("\\"); + } + if (pos > -1){ + shortFilename = filename.substring(pos+1); + } + + CSSLint.Util.forEach(messages, function (message, i) { + output = output + "\n\n" + shortFilename; + if (message.rollup) { + output += "\n" + (i+1) + ": " + message.type; + output += "\n" + message.message; + } else { + output += "\n" + (i+1) + ": " + message.type + " at line " + message.line + ", col " + message.col; + output += "\n" + message.message; + output += "\n" + message.evidence; + } + }); + + return output; + } +}); + + +exports.CSSLint = CSSLint; + + +});
\ No newline at end of file |
