"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) { "use strict"; require("./regexp"); require("./es5-shim"); });/** * Based on code from: * * XRegExp 1.5.0 * (c) 2007-2010 Steven Levithan * MIT License * * Provides an augmented, extensible, cross-browser implementation of regular expressions, * including support for additional syntax, flags, and methods */ define('ace/lib/regexp', ['require', 'exports', 'module' ], function(require, exports, module) { "use strict"; //--------------------------------- // 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; }(); //--------------------------------- // 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 (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 // 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); }; });/* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Irakli Gozalishvili (http://jeditoolkit.com) * Mike de Boer * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/lib/event_emitter', ['require', 'exports', 'module' ], function(require, exports, module) { "use strict"; 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 * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/lib/oop', ['require', 'exports', 'module' ], function(require, exports, module) { "use strict"; 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); }; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/mode/css_worker', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/worker/mirror', 'ace/mode/css/csslint'], function(require, exports, module) { "use strict"; 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) { "use strict"; 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); });/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/document', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/event_emitter', 'ace/range', 'ace/anchor'], function(require, exports, module) { "use strict"; var oop = require("./lib/oop"); var EventEmitter = require("./lib/event_emitter").EventEmitter; var Range = require("./range").Range; var Anchor = require("./anchor").Anchor; var Document = function(text) { this.$lines = []; if (Array.isArray(text)) { this.insertLines(0, text); } // 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. else if (text.length == 0) { this.$lines = [""]; } 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"); }; /** * Get a verbatim copy of the given line as it is in the document */ this.getLine = function(row) { return this.$lines[row] || ""; }; this.getLines = function(firstRow, lastRow) { return this.$lines.slice(firstRow, lastRow + 1); }; /** * Returns all lines in the document as string array. Warning: The caller * should not modify this array! */ 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 = []; lines.push(this.$lines[range.start.row].substring(range.start.column)); lines.push.apply(lines, this.getLines(range.start.row+1, range.end.row-1)); 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.length == 0) return position; position = this.$clipPosition(position); 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}; 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 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; }; /** * Removes a range of full lines * * @param firstRow {Integer} The first row to be removed * @param lastRow {Integer} The last row to be removed * @return {String[]} The removed lines */ 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=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; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/range', ['require', 'exports', 'module' ], function(require, exports, module) { "use strict"; var Range = function(startRow, startColumn, endRow, endColumn) { this.start = { row: startRow, column: startColumn }; this.end = { row: endRow, column: endColumn }; }; (function() { this.isEequal = 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; }; /** * Compares this range (A) with another range (B), where B is the passed in * range. * * Return values: * -2: (B) is infront of (A) and doesn't intersect with (A) * -1: (B) begins before (A) but ends inside of (A) * 0: (B) is completly inside of (A) OR (A) is complety inside of (B) * +1: (B) begins inside of (A) but ends outside of (A) * +2: (B) is after (A) and doesn't intersect with (A) * * 42: FTW state: (B) ends in (A) but starts outside of (A) */ 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; } } } this.comparePoint = function(p) { return this.compare(p.row, p.column); } this.containsRange = function(range) { return this.comparePoint(range.start) == 0 && this.comparePoint(range.end) == 0; } this.isEnd = function(row, column) { return this.end.row == row && this.end.column == column; } this.isStart = function(row, column) { return this.start.row == row && this.start.column == column; } 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; } } 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; } } 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; } this.insideStart = function(row, column) { if (this.compare(row, column) == 0) { if (this.isEnd(row, column)) { return false; } else { return true; } } return false; } this.insideEnd = function(row, column) { if (this.compare(row, column) == 0) { if (this.isStart(row, column)) { return false; } else { return true; } } return false; } 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; }; /** * Like .compare(), but if isStart is true, return -1; */ this.compareStart = function(row, column) { if (this.start.row == row && this.start.column == column) { return -1; } else { return this.compare(row, column); } } /** * Like .compare(), but if isEnd is true, return 1; */ this.compareEnd = function(row, column) { if (this.end.row == row && this.end.column == column) { return 1; } else { return this.compare(row, column); } } 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); } } 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; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/anchor', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/event_emitter'], function(require, exports, module) { "use strict"; var oop = require("./lib/oop"); var EventEmitter = require("./lib/event_emitter").EventEmitter; /** * An Anchor is a floating pointer in the document. Whenever text is inserted or * deleted before the cursor, the position of the cursor is updated */ 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); }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/lib/lang', ['require', 'exports', 'module' ], function(require, exports, module) { "use strict"; 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= 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 }; })(); /* 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. */ /* Build time: 13-July-2011 04:35:28 */ (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", lightgrey :"#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); /** * The type of modifier. * @type String * @property 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; var Level1Properties = { "background": 1, "background-attachment": 1, "background-color": 1, "background-image": 1, "background-position": 1, "background-repeat": 1, "border": 1, "border-bottom": 1, "border-bottom-width": 1, "border-color": 1, "border-left": 1, "border-left-width": 1, "border-right": 1, "border-right-width": 1, "border-style": 1, "border-top": 1, "border-top-width": 1, "border-width": 1, "clear": 1, "color": 1, "display": 1, "float": 1, "font": 1, "font-family": 1, "font-size": 1, "font-style": 1, "font-variant": 1, "font-weight": 1, "height": 1, "letter-spacing": 1, "line-height": 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, "padding": 1, "padding-bottom": 1, "padding-left": 1, "padding-right": 1, "padding-top": 1, "text-align": 1, "text-decoration": 1, "text-indent": 1, "text-transform": 1, "vertical-align": 1, "white-space": 1, "width": 1, "word-spacing": 1 }; var Level2Properties = { //Aural "azimuth": 1, "cue-after": 1, "cue-before": 1, "cue": 1, "elevation": 1, "pause-after": 1, "pause-before": 1, "pause": 1, "pitch-range": 1, "pitch": 1, "play-during": 1, "richness": 1, "speak-header": 1, "speak-numeral": 1, "speak-punctuation": 1, "speak": 1, "speech-rate": 1, "stress": 1, "voice-family": 1, "volume": 1, //Paged "orphans": 1, "page-break-after": 1, "page-break-before": 1, "page-break-inside": 1, "widows": 1, //Interactive "cursor": 1, "outline-color": 1, "outline-style": 1, "outline-width": 1, "outline": 1, //Visual "background-attachment": 1, "background-color": 1, "background-image": 1, "background-position": 1, "background-repeat": 1, "background": 1, "border-collapse": 1, "border-color": 1, "border-spacing": 1, "border-style": 1, "border-top": 1, "border-top-color": 1, "border-top-style": 1, "border-top-width": 1, "border-width": 1, "border": 1, "bottom": 1, "caption-side": 1, "clear": 1, "clip": 1, "color": 1, "content": 1, "counter-increment": 1, "counter-reset": 1, "direction": 1, "display": 1, "empty-cells": 1, "float": 1, "font-family": 1, "font-size": 1, "font-style": 1, "font-variant": 1, "font-weight": 1, "font": 1, "height": 1, "left": 1, "letter-spacing": 1, "line-height": 1, "list-style-image": 1, "list-style-position": 1, "list-style-type": 1, "list-style": 1, "margin-right": 1, "margin-top": 1, "margin": 1, "max-height": 1, "max-width": 1, "min-height": 1, "min-width": 1, "overflow": 1, "padding-top": 1, "padding": 1, "position": 1, "quotes": 1, "right": 1, "table-layout": 1, "text-align": 1, "text-decoration": 1, "text-indent": 1, "text-transform": 1, "top": 1, "unicode-bidi": 1, "vertical-align": 1, "visibility": 1, "white-space": 1, "width": 1, "word-spacing": 1, "z-index": 1 }; /** * 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); /** * The name of the media feature * @type String * @property name */ this.name = name; /** * The value for the feature or null if there is none. * @type SyntaxUnit * @property value */ 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); /** * The media modifier ("not" or "only") * @type String * @property modifier */ this.modifier = modifier; /** * The mediaType (i.e., "print") * @type String * @property mediaType */ this.mediaType = mediaType; /** * The parts that make up the selector. * @type Array * @property features */ 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; } Parser.prototype = function(){ var proto = new EventTarget(), //new prototype prop, additions = { //restore constructor constructor: Parser, //----------------------------------------------------------------- // 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, 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.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]); /*if (!tokenStream.match(Tokens.STRING)){ tokenStream.mustMatch(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; /* * Error Recovery: If even a single selector fails to parse, * then the entire ruleset should be thrown away. */ 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; 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(); this.fire({ type: "property", property: property, value: expr, important: prio, line: property.line, col: property.col }); 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].startLine, values[0].startCol) : 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, 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 value = this._hexcolor(); if (value === 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 { if (unary === null){ line = tokenStream.token().startLine; col = tokenStream.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; if (tokenStream.match(Tokens.FUNCTION)){ functionText = tokenStream.token().value; this._readWhitespace(); expr = this._expr(); tokenStream.match(Tokens.RPAREN); functionText += expr + ")"; 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, color = null; 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 color; }, //----------------------------------------------------------------- // 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 (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 } else { //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)); } }, //----------------------------------------------------------------- // 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; } }; //copy over onto prototype for (prop in additions){ proto[prop] = additions[prop]; } return proto; }(); /* nth : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? | ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S* ; */ /** * 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, (hack||"") + text, line, col); /** * The type of IE hack applied ("*", "_", or null). * @type String * @property hack */ this.hack = hack; } PropertyName.prototype = new SyntaxUnit(); PropertyName.prototype.constructor = PropertyName; /** * 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); /** * The parts that make up the selector. * @type Array * @property parts */ this.parts = parts; } PropertyValue.prototype = new SyntaxUnit(); PropertyValue.prototype.constructor = PropertyValue; /** * 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.apply(this,arguments); /** * Indicates the type of value unit. * @type String * @property 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": 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 (/^url\(["']?([^\)"']+)["']?\)/i.test(text)){ //URI this.type = "uri"; this.uri = RegExp.$1; } 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 = PropertyValue; /** * 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.css.PropertyValuePart} The object representing the token. * @static * @method fromToken */ PropertyValuePart.fromToken = function(token){ return new PropertyValuePart(token.value, token.startLine, token.startCol); }; /** * 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); /** * The parts that make up the selector. * @type Array * @property parts */ this.parts = parts; } 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); /** * The tag name of the element to which this part * of the selector affects. * @type String * @property elementName */ this.elementName = elementName; /** * The parts that come after the element name, such as class names, IDs, * pseudo classes/elements, etc. * @type Array * @property modifiers */ 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); /** * The type of modifier. * @type String * @property type */ this.type = type; /** * Some subparts have arguments, this represents them. * @type Array * @property args */ this.args = []; } SelectorSubPart.prototype = new SyntaxUnit(); SelectorSubPart.prototype.constructor = SelectorSubPart; 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; /* * Potential tokens: * - DASHMATCH * - INCLUDES * - PREFIXMATCH * - SUFFIXMATCH * - SUBSTRINGMATCH * - CHAR */ case "|": case "~": case "^": case "$": case "*": if(reader.peek() == "="){ token = this.comparisonToken(c, startLine, startCol); } else { token = this.charToken(c, startLine, startCol); } break; /* * Potential tokens: * - STRING * - INVALID */ case "\"": case "'": token = this.stringToken(c, startLine, startCol); break; /* * Potential tokens: * - HASH * - CHAR */ case "#": if (isNameChar(reader.peek())){ token = this.hashToken(c, startLine, startCol); } else { token = this.charToken(c, startLine, startCol); } break; /* * Potential tokens: * - DOT * - NUMBER * - DIMENSION * - PERCENTAGE */ case ".": if (isDigit(reader.peek())){ token = this.numberToken(c, startLine, startCol); } else { token = this.charToken(c, startLine, startCol); } break; /* * Potential tokens: * - CDC * - MINUS * - NUMBER * - DIMENSION * - PERCENTAGE */ 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; /* * Potential tokens: * - IMPORTANT_SYM * - CHAR */ case "!": token = this.importantToken(c, startLine, startCol); break; /* * Any at-keyword or CHAR */ case "@": token = this.atRuleToken(c, startLine, startCol); break; /* * Potential tokens: * - NOT * - CHAR */ case ":": token = this.notToken(c, startLine, startCol); break; /* * Potential tokens: * - CDO * - CHAR */ case "<": token = this.htmlCommentStartToken(c, startLine, startCol); break; /* * Potential tokens: * - UNICODE_RANGE * - URL * - CHAR */ 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; c = reader.read(); } 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; /* * First, mark where we are. There are only four @ rules, * so anything else is really just an invalid token. * Basically, if this doesn't match one of the known @ * rules, just return '@' as an unknown token and allow * parsing to continue after that point. */ 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){ 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.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 (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: "ATKEYWORD"}, //CSS3 animations { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-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; }; })(); parserlib.css = { Colors :Colors, Combinator :Combinator, Parser :Parser, PropertyName :PropertyName, PropertyValue :PropertyValue, PropertyValuePart :PropertyValuePart, MediaFeature :MediaFeature, MediaQuery :MediaQuery, Selector :Selector, SelectorPart :SelectorPart, SelectorSubPart :SelectorSubPart, TokenStream :TokenStream, Tokens :Tokens }; })(); /** * Main CSSLint object. * @class CSSLint * @static * @extends parserlib.util.EventTarget */ var CSSLint = (function(){ var rules = [], formatters = [], api = new parserlib.util.EventTarget(); api.version = "@VERSION@"; //------------------------------------------------------------------------- // 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; }; /** * Clears all rule from the engine. * @method clearRules */ api.clearRules = function(){ rules = []; }; //------------------------------------------------------------------------- // 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; }; /** * Retrieves a formatter for use. * @param {String} formatId The name of the format to retrieve. * @return {Object} The formatter or undefined. * @method getFormatter */ api.getFormatter = function(formatId){ return formatters[formatId]; }; /** * Formats the results in a particular format for a single file. * @param {Object} result The results returned from CSSLint.verify(). * @param {String} filename The filename for which the results apply. * @param {String} formatId The name of the formatter to use. * @return {String} A formatted string for the results. * @method format */ api.format = function(results, filename, formatId) { var formatter = this.getFormatter(formatId), result = null; if (formatter){ result = formatter.startFormat(); result += formatter.formatResults(results, filename); result += formatter.endFormat(); } return result; } /** * Indicates if the given format is supported. * @param {String} formatId The ID of the format to check. * @return {Boolean} True if the format exists, false if not. * @method hasFormat */ 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. * @return {Object} Results of the verification. * @method verify */ api.verify = function(text, ruleset){ var i = 0, len = rules.length, reporter, lines, parser = new parserlib.css.Parser({ starHack: true, ieFilters: true, underscoreHack: true, strict: false }); lines = text.split(/\n\r?/g); reporter = new Reporter(lines); if (!ruleset){ while (i < len){ rules[i++].init(parser, reporter); } } else { ruleset.errors = 1; //always report parsing 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); } return { messages : reporter.messages, stats : reporter.stats }; }; //------------------------------------------------------------------------- // 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. */ function Reporter(lines){ /** * List of messages being reported. * @property messages * @type String[] */ this.messages = []; /** * List of statistics being reported. * @property stats * @type String[] */ this.stats = []; /** * Lines of code being reported on. Used to provide contextual information * for messages. * @property lines * @type String[] */ this.lines = lines; } 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 */ warn: function(message, line, col, rule){ this.messages.push({ type : "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; } }; /* * Utility functions that make life easier. */ /* * 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 */ function mix(reciever, 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. */ function indexOf(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; } } /* * Rule: Don't use adjoining classes (.foo.bar). */ CSSLint.addRule({ //rule information id: "adjoining-classes", name: "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 instanceof parserlib.css.SelectorPart){ classCount = 0; for (k=0; k < part.modifiers.length; k++){ modifier = part.modifiers[k]; if (modifier.type == "class"){ classCount++; } if (classCount > 1){ reporter.warn("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: "Box Model", 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; parser.addListener("startrule", function(){ properties = { }; }); 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", function(){ 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){ //noop } else { reporter.warn("Broken box model: using height with " + prop + ".", 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){ //noop } else { reporter.warn("Broken box model: using width with " + prop + ".", properties[prop].line, properties[prop].col, rule); } } } } }); } }); /* * Rule: Include all compatible vendor prefixes to reach a wider * range of users. */ /*global CSSLint*/ CSSLint.addRule({ //rule information id: "compatible-vendor-prefixes", name: "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", "animation-delay" : "webkit moz", "animation-direction" : "webkit moz", "animation-duration" : "webkit moz", "animation-fill-mode" : "webkit moz", "animation-iteration-count" : "webkit moz", "animation-name" : "webkit moz", "animation-play-state" : "webkit moz", "animation-timing-function" : "webkit moz", "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", "column-gap" : "webkit moz", "column-rule" : "webkit moz", "column-rule-color" : "webkit moz", "column-rule-style" : "webkit moz", "column-rule-width" : "webkit moz", "column-width" : "webkit moz", "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", "transition-delay" : "webkit moz o", "transition-duration" : "webkit moz o", "transition-property" : "webkit moz o", "transition-timing-function" : "webkit moz o", "user-modify" : "webkit moz", "user-select" : "webkit moz", "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.text; if (applyTo.indexOf(name) > -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 (variations.indexOf(name) > -1) { if (propertyGroups[prop] === undefined) { propertyGroups[prop] = { full : variations.slice(0), actual : [] }; } if (propertyGroups[prop].actual.indexOf(name) === -1) { propertyGroups[prop].actual.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 (actual.indexOf(item) === -1) { propertiesSpecified = (actual.length === 1) ? actual[0] : (actual.length == 2) ? actual.join(" and ") : actual.join(", "); reporter.warn("The property " + item + " is compatible with " + propertiesSpecified + " and should be included as well.", event.selectors[0].line, event.selectors[0].col, rule); } } } } } }); } }); /* * Rule: Certain properties don't play well with certain display values. * - float should not be used with inline-block * - height, width, margin-top, margin-bottom, float should not be used with inline * - vertical-align should not be used with block * - margin, float should not be used with table-* */ CSSLint.addRule({ //rule information id: "display-property-grouping", name: "Display Property Grouping", 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; parser.addListener("startrule", function(){ properties = {}; }); 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", function(){ 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 } } }); function reportProperty(name, display, msg){ if (properties[name]){ if (!(typeof propertiesToCheck[name] == "string") || properties[name].value.toLowerCase() != propertiesToCheck[name]){ reporter.warn(msg || name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule); } } } } }); /* * Rule: Duplicate properties must appear one after the other. If an already-defined * property appears somewhere else in the rule, then it's likely an error. */ CSSLint.addRule({ //rule information id: "duplicate-properties", name: "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("property", function(event){ var property = event.property, name = property.text.toLowerCase(); if (properties[name] && (lastProperty != name || properties[name] == event.value.text)){ reporter.warn("Duplicate property '" + event.property + "' found.", event.line, event.col, rule); } properties[name] = event.value.text; lastProperty = name; }); } }); /* * Rule: Style rules without any properties defined should be removed. */ CSSLint.addRule({ //rule information id: "empty-rules", name: "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.warn("Rule is empty.", selectors[0].line, selectors[0].col, rule); } }); } }); /* * Rule: There should be no syntax errors. (Duh.) */ 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); }); } }); /* * Rule: You shouldn't use more than 10 floats. If you do, there's probably * room for some abstraction. */ CSSLint.addRule({ //rule information id: "floats", name: "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); } }); } }); /* * Rule: Avoid too many @font-face declarations in the same stylesheet. */ CSSLint.addRule({ //rule information id: "font-faces", name: "Font Faces", 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); } }); } }); /* * Rule: You shouldn't need more than 9 font-size declarations. */ CSSLint.addRule({ //rule information id: "font-sizes", name: "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); } }); } }); /* * Rule: When using a vendor-prefixed gradient, make sure to use them all. */ CSSLint.addRule({ //rule information id: "gradients", name: "Gradients", 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, ms: 0, o: 0 }; }); parser.addListener("property", function(event){ if (/\-(moz|ms|o|webkit)(?:\-(?:linear|radial))\-gradient/.test(event.value)){ gradients[RegExp.$1] = 1; } }); parser.addListener("endrule", function(event){ var missing = []; if (!gradients.moz){ missing.push("Firefox 3.6+"); } if (!gradients.webkit){ missing.push("Webkit (Safari, Chrome)"); } if (!gradients.ms){ missing.push("Internet Explorer 10+"); } if (!gradients.o){ missing.push("Opera 11.1+"); } if (missing.length && missing.length < 4){ reporter.warn("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule); } }); } }); /* * Rule: Don't use IDs for selectors. */ CSSLint.addRule({ //rule information id: "ids", name: "IDs", 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 instanceof parserlib.css.SelectorPart){ for (k=0; k < part.modifiers.length; k++){ modifier = part.modifiers[k]; if (modifier.type == "id"){ idCount++; } } } } if (idCount == 1){ reporter.warn("Don't use IDs in selectors.", selector.line, selector.col, rule); } else if (idCount > 1){ reporter.warn(idCount + " IDs in the selector, really?", selector.line, selector.col, rule); } } }); } }); /* * Rule: Don't use @import, use instead. */ CSSLint.addRule({ //rule information id: "import", name: "@import", desc: "Don't use @import, use instead.", browsers: "All", //initialization init: function(parser, reporter){ var rule = this; parser.addListener("import", function(event){ reporter.warn("@import prevents parallel downloads, use instead.", event.line, event.col, rule); }); } }); /* * Rule: Make sure !important is not overused, this could lead to specificity * war. Display a warning on !important declarations, an error if it's * used more at least 10 times. */ CSSLint.addRule({ //rule information id: "important", name: "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.warn("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); } }); } }); /* * Rule: Properties should be known (listed in CSS3 specification) or * be a vendor-prefixed property. */ CSSLint.addRule({ //rule information id: "known-properties", name: "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-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, "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 }; parser.addListener("property", function(event){ var name = event.property.text.toLowerCase(); if (!properties[name] && name.charAt(0) != "-"){ reporter.error("Unknown property '" + event.property + "'.", event.line, event.col, rule); } }); } }); /* * Rule: Don't use classes or IDs with elements (a.foo or a#foo). */ CSSLint.addRule({ //rule information id: "overqualified-elements", name: "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 instanceof parserlib.css.SelectorPart){ for (k=0; k < part.modifiers.length; k++){ modifier = part.modifiers[k]; if (part.elementName && modifier.type == "id"){ reporter.warn("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.warn("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); } } } }); } }); /* * Rule: Headings (h1-h6) should not be qualified (namespaced). */ CSSLint.addRule({ //rule information id: "qualified-headings", name: "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 instanceof parserlib.css.SelectorPart){ if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0){ reporter.warn("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule); } } } } }); } }); /* * Rule: Selectors that look like regular expressions are slow and should be avoided. */ CSSLint.addRule({ //rule information id: "regex-selectors", name: "Regex Selectors", 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 instanceof parserlib.css.SelectorPart){ for (k=0; k < part.modifiers.length; k++){ modifier = part.modifiers[k]; if (modifier.type == "attribute"){ if (/([\~\|\^\$\*]=)/.test(modifier)){ reporter.warn("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule); } } } } } } }); } }); /* * Rule: Total number of rules should not exceed x. */ 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); }); } }); /* * Rule: Don't use text-indent for image replacement if you need to support rtl. * */ /* * Should we be checking for rtl/ltr? */ //Commented out due to lack of tests CSSLint.addRule({ //rule information id: "text-indent", name: "Text Indent", desc: "Checks for text indent less than -99px", browsers: "All", //initialization init: function(parser, reporter){ var rule = this; //check for use of "font-size" parser.addListener("property", function(event){ var name = event.property, value = event.value.parts[0].value; if (name == "text-indent" && value < -99){ reporter.warn("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set text-direction for that item to ltr.", name.line, name.col, rule); } }); } }); /* * Rule: Headings (h1-h6) should be defined only once. */ CSSLint.addRule({ //rule information id: "unique-headings", name: "Unique Headings", 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, i; for (i=0; i < selectors.length; i++){ selector = selectors[i]; part = selector.parts[selector.parts.length-1]; if (part.elementName && /(h[1-6])/.test(part.elementName.toString())){ headings[RegExp.$1]++; if (headings[RegExp.$1] > 1) { reporter.warn("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule); } } } }); } }); /* * Rule: Don't use universal selector because it's slow. */ CSSLint.addRule({ //rule information id: "universal-selector", name: "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.warn(rule.desc, part.line, part.col, rule); } } }); } }); /* * Rule: When using a vendor-prefixed property, make sure to * include the standard one. */ CSSLint.addRule({ //rule information id: "vendor-prefix", name: "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 = { "-moz-border-radius": "border-radius", "-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", "-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-box-shadow": "box-shadow", "-webkit-box-shadow": "box-shadow", "-moz-transform" : "transform", "-webkit-transform" : "transform", "-o-transform" : "transform", "-ms-transform" : "transform", "-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.warn("Missing standard property '" + needed + "' to go along with '" + actual + "'.", event.line, event.col, rule); } else { //make sure standard property is last if (properties[needed][0].pos < properties[actual][0].pos){ reporter.warn("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", event.line, event.col, rule); } } } } parser.addListener("startrule", startRule); parser.addListener("startfontface", 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); } }); /* * Rule: If an element has a width of 100%, be careful when placing within * an element that has padding. It may look strange. */ //Commented out pending further review. /*CSSLint.addRule({ //rule information id: "width-100", name: "Width 100%", desc: "Be careful when using width: 100% on elements.", browsers: "All", //initialization init: function(parser, reporter){ var rule = this, width100, boxsizing; parser.addListener("startrule", function(){ width100 = null; boxsizing = false; }); parser.addListener("property", function(event){ var name = event.property.text.toLowerCase(), value = event.value; if (name == "width" && value == "100%"){ width100 = event.property; } else if (name == "box-sizing" || /\-(?:webkit|ms|moz)\-box-sizing/.test(name)){ //means you know what you're doing boxsizing = true; } }); parser.addListener("endrule", function(){ if (width100 && !boxsizing){ reporter.warn("Elements with a width of 100% may not appear as you expect inside of other elements.", width100.line, width100.col, rule); } }); } });*/ /* * Rule: You don't need to specify units when a value is 0. */ CSSLint.addRule({ //rule information id: "zero-units", name: "Zero Units", 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){ reporter.warn("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule); } i++; } }); } }); exports.CSSLint = CSSLint; });