diff options
Diffstat (limited to 'contexts/data/lib/ace/worker-css.js')
-rw-r--r-- | contexts/data/lib/ace/worker-css.js | 6361 |
1 files changed, 2292 insertions, 4069 deletions
diff --git a/contexts/data/lib/ace/worker-css.js b/contexts/data/lib/ace/worker-css.js index 7b17906..f4cc2cb 100644 --- a/contexts/data/lib/ace/worker-css.js +++ b/contexts/data/lib/ace/worker-css.js @@ -1,8 +1,16 @@ "no use strict"; +if (typeof window != "undefined" && window.document) + throw "atempt to load ace worker into main window instead of webWorker"; + var console = { - log: function(msg) { - postMessage({type: "log", data: msg}); + log: function() { + var msgs = Array.prototype.slice.call(arguments, 0); + postMessage({type: "log", data: msgs}); + }, + error: function() { + var msgs = Array.prototype.slice.call(arguments, 0); + postMessage({type: "log", data: msgs}); } }; var window = { @@ -18,11 +26,11 @@ var normalizeModule = function(parentId, moduleName) { // normalize relative requires if (moduleName.charAt(0) == ".") { var base = parentId.split("/").slice(0, -1).join("/"); - var moduleName = base + "/" + moduleName; + moduleName = base + "/" + moduleName; while(moduleName.indexOf(".") !== -1 && previous != moduleName) { var previous = moduleName; - var moduleName = moduleName.replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, ""); + moduleName = moduleName.replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, ""); } } @@ -30,13 +38,16 @@ var normalizeModule = function(parentId, moduleName) { }; var require = function(parentId, id) { - var id = normalizeModule(parentId, id); - + if (!id.charAt) + throw new Error("worker.js require() accepts only (parentId, id) as arguments"); + + id = normalizeModule(parentId, id); + var module = require.modules[id]; if (module) { if (!module.initialized) { - module.exports = module.factory().exports; module.initialized = true; + module.exports = module.factory().exports; } return module.exports; } @@ -56,6 +67,10 @@ require.tlns = {}; var define = function(id, deps, factory) { if (arguments.length == 2) { factory = deps; + if (typeof id != "string") { + deps = id; + id = require.id; + } } else if (arguments.length == 1) { factory = id; id = require.id; @@ -123,7 +138,10 @@ var sender; onmessage = function(e) { var msg = e.data; if (msg.command) { - main[msg.command].apply(main, msg.args); + if (main[msg.command]) + main[msg.command].apply(main, msg.args); + else + throw new Error("Unknown command:" + msg.command); } else if (msg.init) { initBaseUrls(msg.tlns); @@ -137,40 +155,16 @@ onmessage = function(e) { } }; // 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 - * <http://xregexp.com> - * 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, @@ -186,25 +180,14 @@ define('ace/lib/regexp', ['require', 'exports', 'module' ], function(require, ex 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. + if (compliantLastIndexIncrement && compliantExecNpcg) + return; 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 ( typeof(str) == 'string' && match) { 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) @@ -212,7 +195,6 @@ define('ace/lib/regexp', ['require', 'exports', 'module' ], function(require, ex } }); } - // Attach named capture properties if (this._xregexp && this._xregexp.captureNames) { for (var i = 1; i < match.length; i++) { name = this._xregexp.captureNames[i - 1]; @@ -220,38 +202,27 @@ define('ace/lib/regexp', ['require', 'exports', 'module' ], function(require, ex 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 @@ -261,94 +232,21 @@ define('ace/lib/regexp', ['require', 'exports', 'module' ], function(require, ex 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; @@ -363,25 +261,6 @@ if (!Function.prototype.bind) { 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)) @@ -390,53 +269,15 @@ if (!Function.prototype.bind) { } }; - // 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; @@ -448,70 +289,35 @@ if ((supportsAccessors = owns(prototypeOfObject, "__defineGetter__"))) { 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 } @@ -523,18 +329,12 @@ if (!Array.prototype.map) { 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 } @@ -546,17 +346,11 @@ if (!Array.prototype.filter) { 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 } @@ -568,17 +362,11 @@ if (!Array.prototype.every) { 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 } @@ -590,21 +378,13 @@ if (!Array.prototype.some) { 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 @@ -618,8 +398,6 @@ if (!Array.prototype.reduce) { result = self[i++]; break; } - - // if array contains no values, no initial value to return if (++i >= length) throw new TypeError(); // TODO message } while (true); @@ -633,21 +411,13 @@ if (!Array.prototype.reduce) { 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 @@ -660,8 +430,6 @@ if (!Array.prototype.reduceRight) { result = self[i--]; break; } - - // if array contains no values, no initial value to return if (--i < 0) throw new TypeError(); // TODO message } while (true); @@ -675,10 +443,6 @@ if (!Array.prototype.reduceRight) { 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), @@ -690,8 +454,6 @@ if (!Array.prototype.indexOf) { 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) { @@ -701,10 +463,6 @@ if (!Array.prototype.indexOf) { 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), @@ -715,7 +473,6 @@ if (!Array.prototype.lastIndexOf) { 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]) @@ -724,18 +481,7 @@ if (!Array.prototype.lastIndexOf) { 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 ? @@ -744,84 +490,73 @@ if (!Object.getPrototypeOf) { ); }; } - -// 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) { + var createEmpty; + if (Object.prototype.__proto__ === null) { + createEmpty = function () { + return { "__proto__": null }; + }; + } else { + createEmpty = function () { + var empty = {}; + for (var i in empty) + empty[i] = null; + empty.constructor = + empty.hasOwnProperty = + empty.propertyIsEnumerable = + empty.isPrototypeOf = + empty.toLocaleString = + empty.toString = + empty.valueOf = + empty.__proto__ = null; + return empty; + } + } + Object.create = function create(prototype, properties) { var object; if (prototype === null) { - object = { "__proto__": null }; + object = createEmpty(); } 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) @@ -830,29 +565,13 @@ if (!Object.create) { }; } -// ES5 15.2.3.6 -// http://es5.github.com/#x15.2.3.6 - -// Patch for WebKit and IE8 standard mode -// Designed by hax <hax.github.com> -// related issue: https://github.com/kriskowal/es5-shim/issues#issue/5 -// IE8 Reference: -// http://msdn.microsoft.com/en-us/library/dd282900.aspx -// http://msdn.microsoft.com/en-us/library/dd229916.aspx -// WebKit Bugs: -// https://bugs.webkit.org/show_bug.cgi?id=36423 - function doesDefinePropertyWork(object) { try { Object.defineProperty(object, "sentinel", {}); return "sentinel" in object; } catch (exception) { - // returns falsy } } - -// check whether defineProperty works if it's given. Otherwise, -// shim partially. if (Object.defineProperty) { var definePropertyWorksOnObject = doesDefinePropertyWork({}); var definePropertyWorksOnDom = typeof document == "undefined" || @@ -873,48 +592,21 @@ if (!Object.defineProperty || definePropertyFallback) { 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; @@ -922,7 +614,6 @@ if (!Object.defineProperty || definePropertyFallback) { } 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")) @@ -932,9 +623,6 @@ if (!Object.defineProperty || definePropertyFallback) { 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) { @@ -944,30 +632,16 @@ if (!Object.defineProperties) { 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) { @@ -981,43 +655,26 @@ try { }; })(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 += '?'; @@ -1028,11 +685,7 @@ if (!Object.isExtensible) { 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", @@ -1073,26 +726,11 @@ if (!Object.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(); @@ -1101,76 +739,32 @@ if (!Date.prototype.toISOString || (new Date(-62198755200000).toISOString().inde 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) : @@ -1179,14 +773,11 @@ if (Date.parse("+275760-09-13T00:00:00.000Z") !== 8.64e15) { 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 @@ -1207,59 +798,33 @@ if (Date.parse("+275760-09-13T00:00:00.000Z") !== 8.64e15) { ")" + ")?)?)?)?" + "$"); - - // 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); @@ -1268,20 +833,10 @@ if (Date.parse("+275760-09-13T00:00:00.000Z") !== 8.64e15) { 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 + "*$"); @@ -1289,15 +844,6 @@ if (!String.prototype.trim || ws.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 @@ -1308,61 +854,19 @@ var toInteger = function (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 <fabian AT ajax DOT org> - * Irakli Gozalishvili <rfobic@gmail.com> (http://jeditoolkit.com) - * Mike de Boer <mike AT ajax DOT org> - * - * 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 = {}; @@ -1376,8 +880,11 @@ EventEmitter._dispatchEvent = function(eventName, e) { if (!listeners.length && !defaultHandler) return; - e = e || {}; - e.type = eventName; + if (typeof e != "object" || !e) + e = {}; + + if (!e.type) + e.type = eventName; if (!e.stopPropagation) { e.stopPropagation = function() { @@ -1398,7 +905,7 @@ EventEmitter._dispatchEvent = function(eventName, e) { } if (defaultHandler && !e.defaultPrevented) - defaultHandler(e); + return defaultHandler(e); }; EventEmitter.setDefaultHandler = function(eventName, callback) { @@ -1416,7 +923,7 @@ EventEmitter.addEventListener = function(eventName, callback) { var listeners = this._eventRegistry[eventName]; if (!listeners) - var listeners = this._eventRegistry[eventName] = []; + listeners = this._eventRegistry[eventName] = []; if (listeners.indexOf(callback) == -1) listeners.push(callback); @@ -1441,45 +948,10 @@ EventEmitter.removeAllListeners = function(eventName) { exports.EventEmitter = EventEmitter; -});/* ***** 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 <fabian AT ajax DOT org> - * - * 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() {}; @@ -1502,73 +974,242 @@ exports.implement = function(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 <fabian AT ajax DOT org> - * - * 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"; + +define('ace/mode/css_worker', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/lang', 'ace/worker/mirror', 'ace/mode/css/csslint'], function(require, exports, module) { + var oop = require("../lib/oop"); +var lang = require("../lib/lang"); 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); + this.setTimeout(400); + this.ruleset = null; + this.setDisabledRules("ids"); + this.setInfoRules("adjoining-classes|qualified-headings|zero-units|gradients|import|outline-none"); }; oop.inherits(Worker, Mirror); (function() { - + this.setInfoRules = function(ruleNames) { + if (typeof ruleNames == "string") + ruleNames = ruleNames.split("|"); + this.infoRules = lang.arrayToMap(ruleNames); + this.doc.getValue() && this.deferredUpdate.schedule(100); + }; + + this.setDisabledRules = function(ruleNames) { + if (!ruleNames) { + this.ruleset = null; + } else { + if (typeof ruleNames == "string") + ruleNames = ruleNames.split("|"); + var all = {}; + + CSSLint.getRules().forEach(function(x){ + all[x.id] = true; + }); + ruleNames.forEach(function(x) { + delete all[x]; + }); + + this.ruleset = all; + } + this.doc.getValue() && this.deferredUpdate.schedule(100); + }; + this.onUpdate = function() { var value = this.doc.getValue(); - - var result = CSSLint.verify(value); + var infoRules = this.infoRules; + + var result = CSSLint.verify(value, this.ruleset); this.sender.emit("csslint", result.messages.map(function(msg) { - delete msg.rule; - return msg; + return { + row: msg.line - 1, + column: msg.col - 1, + text: msg.message, + type: infoRules[msg.rule.id] ? "info" : msg.type + } })); }; - + }).call(Worker.prototype); -});define('ace/worker/mirror', ['require', 'exports', 'module' , 'ace/document', 'ace/lib/lang'], function(require, exports, module) { -"use strict"; +}); + +define('ace/lib/lang', ['require', 'exports', 'module' ], function(require, exports, module) { + + +exports.stringReverse = function(string) { + return string.split("").reverse().join(""); +}; + +exports.stringRepeat = function (string, count) { + return new Array(count + 1).join(string); +}; + +var trimBeginRegexp = /^\s\s*/; +var trimEndRegexp = /\s\s*$/; + +exports.stringTrimLeft = function (string) { + return string.replace(trimBeginRegexp, ''); +}; + +exports.stringTrimRight = function (string) { + return string.replace(trimEndRegexp, ''); +}; + +exports.copyObject = function(obj) { + var copy = {}; + for (var key in obj) { + copy[key] = obj[key]; + } + return copy; +}; + +exports.copyArray = function(array){ + var copy = []; + for (var i=0, l=array.length; i<l; i++) { + if (array[i] && typeof array[i] == "object") + copy[i] = this.copyObject( array[i] ); + else + copy[i] = array[i]; + } + return copy; +}; + +exports.deepCopy = function (obj) { + if (typeof obj != "object") { + return obj; + } + + var copy = obj.constructor(); + for (var key in obj) { + if (typeof obj[key] == "object") { + copy[key] = this.deepCopy(obj[key]); + } else { + copy[key] = obj[key]; + } + } + return copy; +}; + +exports.arrayToMap = function(arr) { + var map = {}; + for (var i=0; i<arr.length; i++) { + map[arr[i]] = 1; + } + return map; + +}; + +exports.createMap = function(props) { + var map = Object.create(null); + for (var i in props) { + map[i] = props[i]; + } + return map; +}; +exports.arrayRemove = function(array, value) { + for (var i = 0; i <= array.length; i++) { + if (value === array[i]) { + array.splice(i, 1); + } + } +}; + +exports.escapeRegExp = function(str) { + return str.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1'); +}; + +exports.escapeHTML = function(str) { + return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<"); +}; + +exports.getMatchOffsets = function(string, regExp) { + var matches = []; + + string.replace(regExp, function(str) { + matches.push({ + offset: arguments[arguments.length-2], + length: str.length + }); + }); + + return matches; +}; +exports.deferredCall = function(fcn) { + + var timer = null; + var callback = function() { + timer = null; + fcn(); + }; + + var deferred = function(timeout) { + deferred.cancel(); + timer = setTimeout(callback, timeout || 0); + return deferred; + }; + + deferred.schedule = deferred; + + deferred.call = function() { + this.cancel(); + fcn(); + return deferred; + }; + + deferred.cancel = function() { + clearTimeout(timer); + timer = null; + return deferred; + }; + + return deferred; +}; + + +exports.delayedCall = function(fcn, defaultTimeout) { + var timer = null; + var callback = function() { + timer = null; + fcn(); + }; + + var _self = function(timeout) { + timer && clearTimeout(timer); + timer = setTimeout(callback, timeout || defaultTimeout); + }; + + _self.delay = _self; + _self.schedule = function(timeout) { + if (timer == null) + timer = setTimeout(callback, timeout || 0); + }; + + _self.call = function() { + this.cancel(); + fcn(); + }; + + _self.cancel = function() { + timer && clearTimeout(timer); + timer = null; + }; + + _self.isPending = function() { + return timer; + }; + + return _self; +}; +}); +define('ace/worker/mirror', ['require', 'exports', 'module' , 'ace/document', 'ace/lib/lang'], function(require, exports, module) { + var Document = require("../document").Document; var lang = require("../lib/lang"); @@ -1604,50 +1245,14 @@ var Mirror = exports.Mirror = function(sender) { }; 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 <fabian AT ajax DOT org> - * - * 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; @@ -1656,14 +1261,10 @@ 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) { + if (text.length == 0) { this.$lines = [""]; + } else if (Array.isArray(text)) { + this.insertLines(0, text); } else { this.insert({row: 0, column:0}, text); } @@ -1672,22 +1273,17 @@ var Document = function(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"); @@ -1698,6 +1294,7 @@ var Document = function(text) { }; + this.$detectNewLine = function(text) { var match = text.match(/^.*?(\r\n|\r|\n)/m); if (match) { @@ -1706,18 +1303,17 @@ var Document = function(text) { this.$autoNewLine = "\n"; } }; - this.getNewLineCharacter = function() { - switch (this.$newLineMode) { + switch (this.$newLineMode) { case "windows": - return "\r\n"; + return "\r\n"; case "unix": - return "\n"; + return "\n"; - case "auto": - return this.$autoNewLine; - } + default: + return this.$autoNewLine; + } }; this.$autoNewLine = "\n"; @@ -1728,48 +1324,33 @@ var Document = function(text) { 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)); + var lines = this.getLines(range.start.row+1, range.end.row-1); + lines.unshift((this.$lines[range.start.row] || "").substring(range.start.column)); + lines.push((this.$lines[range.end.row] || "").substring(0, range.end.column)); return lines.join(this.getNewLineCharacter()); } }; @@ -1782,13 +1363,11 @@ var Document = function(text) { } return position; }; - this.insert = function(position, text) { - if (text.length == 0) + if (!text || text.length === 0) return position; position = this.$clipPosition(position); - if (this.getLength() <= 1) this.$detectNewLine(text); @@ -1804,10 +1383,13 @@ var Document = function(text) { } return position; }; - this.insertLines = function(row, lines) { if (lines.length == 0) return {row: row, column: 0}; + if (lines.length > 0xFFFF) { + var end = this.insertLines(row, lines.slice(0xFFFF)); + lines = lines.slice(0, 0xFFFF); + } var args = [row, 0]; args.push.apply(args, lines); @@ -1820,9 +1402,8 @@ var Document = function(text) { lines: lines }; this._emit("change", { data: delta }); - return range.end; + return end || range.end; }; - this.insertNewLine = function(position) { position = this.$clipPosition(position); var line = this.$lines[position.row] || ""; @@ -1844,7 +1425,6 @@ var Document = function(text) { return end; }; - this.insertInLine = function(position, text) { if (text.length == 0) return position; @@ -1868,9 +1448,7 @@ var Document = function(text) { return end; }; - this.remove = function(range) { - // clip to document range.start = this.$clipPosition(range.start); range.end = this.$clipPosition(range.end); @@ -1900,7 +1478,6 @@ var Document = function(text) { } return range.start; }; - this.removeInLine = function(row, startColumn, endColumn) { if (startColumn == endColumn) return; @@ -1919,14 +1496,6 @@ var Document = function(text) { 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); @@ -1940,7 +1509,6 @@ var Document = function(text) { this._emit("change", { data: delta }); return removed; }; - this.removeNewLine = function(row) { var firstLine = this.getLine(row); var secondLine = this.getLine(row+1); @@ -1957,13 +1525,9 @@ var Document = function(text) { }; 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; @@ -1977,7 +1541,6 @@ var Document = function(text) { return end; }; - this.applyDeltas = function(deltas) { for (var i=0; i<deltas.length; i++) { var delta = deltas[i]; @@ -1993,7 +1556,6 @@ var Document = function(text) { this.remove(range); } }; - this.revertDeltas = function(deltas) { for (var i=deltas.length-1; i>=0; i--) { var delta = deltas[i]; @@ -2010,51 +1572,33 @@ var Document = function(text) { this.insert(range.start, delta.text); } }; + this.indexToPosition = function(index, startRow) { + var lines = this.$lines || this.getAllLines(); + var newlineLength = this.getNewLineCharacter().length; + for (var i = startRow || 0, l = lines.length; i < l; i++) { + index -= lines[i].length + newlineLength; + if (index < 0) + return {row: i, column: index + lines[i].length + newlineLength}; + } + return {row: l-1, column: lines[l-1].length}; + }; + this.positionToIndex = function(pos, startRow) { + var lines = this.$lines || this.getAllLines(); + var newlineLength = this.getNewLineCharacter().length; + var index = 0; + var row = Math.min(pos.row, lines.length); + for (var i = startRow || 0; i < row; ++i) + index += lines[i].length; + + return index + newlineLength * i + pos.column; + }; }).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 <fabian AT ajax DOT org> - * - * 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, @@ -2067,36 +1611,21 @@ var Range = function(startRow, startColumn, endRow, endColumn) { }; }; -(function() { - this.isEequal = function(range) { +(function() { + this.isEqual = function(range) { return this.start.row == range.start.row && this.end.row == range.end.row && this.start.column == range.start.column && this.end.column == range.end.column - }; - + }; this.toString = function() { return ("Range: [" + this.start.row + "/" + this.start.column + "] -> [" + this.end.row + "/" + this.end.column + "]"); - }; + }; this.contains = function(row, column) { return this.compare(row, column) == 0; - }; - - /** - * 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, @@ -2124,24 +1653,23 @@ var Range = function(startRow, startColumn, endRow, endColumn) { 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.intersects = function(range) { + var cmp = this.compareRange(range); + return (cmp == -1 || cmp == 0 || cmp == 1); + }; 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; @@ -2150,8 +1678,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) { this.start.row = row; this.start.column = column; } - } - + }; this.setEnd = function(row, column) { if (typeof row == "object") { this.end.column = row.column; @@ -2160,8 +1687,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) { 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)) { @@ -2171,8 +1697,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) { } } return false; - } - + }; this.insideStart = function(row, column) { if (this.compare(row, column) == 0) { if (this.isEnd(row, column)) { @@ -2182,8 +1707,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) { } } return false; - } - + }; this.insideEnd = function(row, column) { if (this.compare(row, column) == 0) { if (this.isStart(row, column)) { @@ -2193,8 +1717,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) { } } return false; - } - + }; this.compare = function(row, column) { if (!this.isMultiLine()) { if (row === this.start.row) { @@ -2216,29 +1739,20 @@ var Range = function(startRow, startColumn, endRow, endColumn) { 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; @@ -2247,8 +1761,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) { } else { return this.compare(row, column); } - } - + }; this.clipRows = function(firstRow, lastRow) { if (this.end.row > lastRow) { var end = { @@ -2279,7 +1792,6 @@ var Range = function(startRow, startColumn, endRow, endColumn) { } return Range.fromPoints(start || this.start, end || this.end); }; - this.extend = function(row, column) { var cmp = this.compare(row, column); @@ -2296,22 +1808,18 @@ var Range = function(startRow, startColumn, endRow, endColumn) { 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); @@ -2325,61 +1833,19 @@ var Range = function(startRow, startColumn, endRow, endColumn) { }; }).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 <fabian AT ajax DOT org> - * - * 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; @@ -2395,15 +1861,15 @@ var Anchor = exports.Anchor = function(doc, row, column) { (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; @@ -2496,11 +1962,10 @@ var Anchor = exports.Anchor = function(doc, row, column) { value: pos }); }; - + this.detach = function() { this.document.removeEventListener("change", this.$onChange); }; - this.$clipPositionToDocument = function(row, column) { var pos = {}; @@ -2526,280 +1991,41 @@ var Anchor = exports.Anchor = function(doc, row, column) { }).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 <fabian AT ajax DOT org> - * - * 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<l; i++) { - if (array[i] && typeof array[i] == "object") - copy[i] = this.copyObject( array[i] ); - else - copy[i] = array[i]; - } - return copy; -}; - -exports.deepCopy = function (obj) { - if (typeof obj != "object") { - return obj; - } - - var copy = obj.constructor(); - for (var key in obj) { - if (typeof obj[key] == "object") { - copy[key] = this.deepCopy(obj[key]); - } else { - copy[key] = obj[key]; - } - } - return copy; -}; - -exports.arrayToMap = function(arr) { - var map = {}; - for (var i=0; i<arr.length; i++) { - map[arr[i]] = 1; - } - return map; - -}; - -/** - * splice out of 'array' anything that === 'value' - */ -exports.arrayRemove = function(array, value) { - for (var i = 0; i <= array.length; i++) { - if (value === array[i]) { - array.splice(i, 1); - } - } -}; - -exports.escapeRegExp = function(str) { - return str.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1'); -}; - -exports.deferredCall = function(fcn) { - - var timer = null; - var callback = function() { - timer = null; - fcn(); - }; - - var deferred = function(timeout) { - deferred.cancel(); - timer = setTimeout(callback, timeout || 0); - return deferred; - }; - - deferred.schedule = deferred; - - deferred.call = function() { - this.cancel(); - fcn(); - return deferred; - }; - - deferred.cancel = function() { - clearTimeout(timer); - timer = null; - return deferred; - }; - - return deferred; -}; - -}); -/* -CSSLint -Copyright (c) 2011 Nicole Sullivan and Nicholas C. Zakas. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ define('ace/mode/css/csslint', ['require', 'exports', 'module' ], function(require, exports, module) { -/*! -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 */ var parserlib = {}; (function(){ - -/** - * A generic base to inherit from for any object - * that needs event handling. - * @class EventTarget - * @constructor - */ function EventTarget(){ - - /** - * The array of listeners for various events. - * @type Object - * @property _listeners - * @private - */ this._listeners = {}; } EventTarget.prototype = { - - //restore constructor constructor: EventTarget, - - /** - * Adds a listener for a given event type. - * @param {String} type The type of event to add a listener for. - * @param {Function} listener The function to call when the event occurs. - * @return {void} - * @method addListener - */ addListener: function(type, listener){ if (!this._listeners[type]){ this._listeners[type] = []; } this._listeners[type].push(listener); - }, - - /** - * Fires an event based on the passed-in object. - * @param {Object|String} event An object with at least a 'type' attribute - * or a string indicating the event name. - * @return {void} - * @method fire - */ + }, fire: function(event){ if (typeof event == "string"){ event = { type: event }; } - if (!event.target){ + if (typeof event.target != "undefined"){ event.target = this; } - if (!event.type){ + if (typeof event.type == "undefined"){ throw new Error("Event object missing 'type' property."); } if (this._listeners[event.type]){ - - //create a copy of the array and use that so listeners can't chane var listeners = this._listeners[event.type].concat(); for (var i=0, len=listeners.length; i < len; i++){ listeners[i].call(this, event); } } }, - - /** - * Removes a listener for a given event type. - * @param {String} type The type of event to remove a listener from. - * @param {Function} listener The function to remove from the event. - * @return {void} - * @method removeListener - */ removeListener: function(type, listener){ if (this._listeners[type]){ var listeners = this._listeners[type]; @@ -2814,147 +2040,47 @@ EventTarget.prototype = { } } }; -/** - * Convenient way to read through strings. - * @namespace parserlib.util - * @class StringReader - * @constructor - * @param {String} text The text to read. - */ function StringReader(text){ - - /** - * The input text with line endings normalized. - * @property _input - * @type String - * @private - */ this._input = text.replace(/\n\r?/g, "\n"); - - - /** - * The row for the character to be read next. - * @property _line - * @type int - * @private - */ this._line = 1; - - - /** - * The column for the character to be read next. - * @property _col - * @type int - * @private - */ this._col = 1; - - /** - * The index of the character in the input to be read next. - * @property _cursor - * @type int - * @private - */ this._cursor = 0; } StringReader.prototype = { - - //restore constructor constructor: StringReader, - - //------------------------------------------------------------------------- - // Position info - //------------------------------------------------------------------------- - - /** - * Returns the column of the character to be read next. - * @return {int} The column of the character to be read next. - * @method getCol - */ getCol: function(){ return this._col; }, - - /** - * Returns the row of the character to be read next. - * @return {int} The row of the character to be read next. - * @method getLine - */ getLine: function(){ return this._line ; }, - - /** - * Determines if you're at the end of the input. - * @return {Boolean} True if there's no more input, false otherwise. - * @method eof - */ eof: function(){ return (this._cursor == this._input.length); }, - - //------------------------------------------------------------------------- - // Basic reading - //------------------------------------------------------------------------- - - /** - * Reads the next character without advancing the cursor. - * @param {int} count How many characters to look ahead (default is 1). - * @return {String} The next character or null if there is no next character. - * @method peek - */ peek: function(count){ var c = null; count = (typeof count == "undefined" ? 1 : count); - - //if we're not at the end of the input... if (this._cursor < this._input.length){ - - //get character and increment cursor and column c = this._input.charAt(this._cursor + count - 1); } return c; }, - - /** - * Reads the next character from the input and adjusts the row and column - * accordingly. - * @return {String} The next character or null if there is no next character. - * @method read - */ read: function(){ var c = null; - - //if we're not at the end of the input... if (this._cursor < this._input.length){ - - //if the last character was a newline, increment row count - //and reset column count if (this._input.charAt(this._cursor) == "\n"){ this._line++; this._col=1; } else { this._col++; } - - //get character and increment cursor and column c = this._input.charAt(this._cursor++); } return c; }, - - //------------------------------------------------------------------------- - // Misc - //------------------------------------------------------------------------- - - /** - * Saves the current location so it can be returned to later. - * @method mark - * @return {void} - */ mark: function(){ this._bookmark = { cursor: this._cursor, @@ -2971,29 +2097,10 @@ StringReader.prototype = { delete this._bookmark; } }, - - //------------------------------------------------------------------------- - // Advanced reading - //------------------------------------------------------------------------- - - /** - * Reads up to and including the given string. Throws an error if that - * string is not found. - * @param {String} pattern The string to read. - * @return {String} The string when it is found. - * @throws Error when the string pattern is not found. - * @method readTo - */ readTo: function(pattern){ var buffer = "", c; - - /* - * First, buffer must be the same length as the pattern. - * Then, buffer must end with the pattern or else reach the - * end of the input. - */ while (buffer.length < pattern.length || buffer.lastIndexOf(pattern) != buffer.length - pattern.length){ c = this.read(); if (c){ @@ -3006,17 +2113,6 @@ StringReader.prototype = { return buffer; }, - - /** - * Reads characters while each character causes the given - * filter function to return true. The function is passed - * in each character and either returns true to continue - * reading or false to stop. - * @param {Function} filter The function to read on each character. - * @return {String} The string made up of all characters that passed the - * filter check. - * @method readWhile - */ readWhile: function(filter){ var buffer = "", @@ -3030,25 +2126,10 @@ StringReader.prototype = { return buffer; }, - - /** - * Reads characters that match either text or a regular expression and - * returns those characters. If a match is found, the row and column - * are adjusted; if no match is found, the reader's state is unchanged. - * reading or false to stop. - * @param {String|RegExp} matchter If a string, then the literal string - * value is searched for. If a regular expression, then any string - * matching the pattern is search for. - * @return {String} The string made up of all characters that matched or - * null if there was no match. - * @method readMatch - */ readMatch: function(matcher){ var source = this._input.substring(this._cursor), value = null; - - //if it's a string, just do a straight match if (typeof matcher == "string"){ if (source.indexOf(matcher) === 0){ value = this.readCount(matcher.length); @@ -3061,15 +2142,6 @@ StringReader.prototype = { return value; }, - - - /** - * Reads a given number of characters. If the end of the input is reached, - * it reads only the remaining characters and does not throw an error. - * @param {int} count The number of characters to read. - * @return {String} The string made up the read characters. - * @method readCount - */ readCount: function(count){ var buffer = ""; @@ -3081,187 +2153,52 @@ StringReader.prototype = { } }; -/** - * Type to use when a syntax error occurs. - * @class SyntaxError - * @namespace parserlib.util - * @constructor - * @param {String} message The error message. - * @param {int} line The line at which the error occurred. - * @param {int} col The column at which the error occurred. - */ function SyntaxError(message, line, col){ - - /** - * The column at which the error occurred. - * @type int - * @property col - */ this.col = col; - - /** - * The line at which the error occurred. - * @type int - * @property line - */ this.line = line; - - /** - * The text representation of the unit. - * @type String - * @property text - */ this.message = message; } - -//inherit from Error SyntaxError.prototype = new Error(); -/** - * Base type to represent a single syntactic unit. - * @class SyntaxUnit - * @namespace parserlib.util - * @constructor - * @param {String} text The text 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 SyntaxUnit(text, line, col){ - - - /** - * The column of text on which the unit resides. - * @type int - * @property col - */ +function SyntaxUnit(text, line, col, type){ this.col = col; - - /** - * The line of text on which the unit resides. - * @type int - * @property line - */ this.line = line; - - /** - * The text representation of the unit. - * @type String - * @property text - */ this.text = text; - + this.type = type; } - -/** - * Create a new syntax unit based solely on the given token. - * Convenience method for creating a new syntax unit when - * it represents a single token instead of multiple. - * @param {Object} token The token object to represent. - * @return {parserlib.util.SyntaxUnit} The object representing the token. - * @static - * @method fromToken - */ SyntaxUnit.fromToken = function(token){ return new SyntaxUnit(token.value, token.startLine, token.startCol); }; SyntaxUnit.prototype = { - - //restore constructor constructor: SyntaxUnit, - - /** - * Returns the text representation of the unit. - * @return {String} The text representation of the unit. - * @method valueOf - */ valueOf: function(){ return this.toString(); }, - - /** - * Returns the text representation of the unit. - * @return {String} The text representation of the unit. - * @method toString - */ toString: function(){ return this.text; } }; -/** - * Generic TokenStream providing base functionality. - * @class TokenStreamBase - * @namespace parserlib.util - * @constructor - * @param {String|StringReader} input The text to tokenize or a reader from - * which to read the input. - */ function TokenStreamBase(input, tokenData){ - - /** - * The string reader for easy access to the text. - * @type StringReader - * @property _reader - * @private - */ - //this._reader = (typeof input == "string") ? new StringReader(input) : input; this._reader = input ? new StringReader(input.toString()) : null; - - /** - * Token object for the last consumed token. - * @type Token - * @property _token - * @private - */ - this._token = null; - - /** - * The array of token information. - * @type Array - * @property _tokenData - * @private - */ + this._token = null; this._tokenData = tokenData; - - /** - * Lookahead token buffer. - * @type Array - * @property _lt - * @private - */ this._lt = []; - - /** - * Lookahead token buffer index. - * @type int - * @property _ltIndex - * @private - */ this._ltIndex = 0; this._ltIndexCache = []; } - -/** - * Accepts an array of token information and outputs - * an array of token data containing key-value mappings - * and matching functions that the TokenStream needs. - * @param {Array} tokens An array of token descriptors. - * @return {Array} An array of processed token data. - * @method createTokenData - * @static - */ TokenStreamBase.createTokenData = function(tokens){ - var nameMap = [], - typeMap = {}, - tokenData = tokens.concat([]), - i = 0, - len = tokenData.length+1; + var nameMap = [], + typeMap = {}, + tokenData = tokens.concat([]), + i = 0, + len = tokenData.length+1; tokenData.UNKNOWN = -1; - tokenData.unshift({name:"EOF"}); + tokenData.unshift({name:"EOF"}); for (; i < len; i++){ nameMap.push(tokenData[i].name); @@ -3278,36 +2215,13 @@ TokenStreamBase.createTokenData = function(tokens){ tokenData.type = function(c){ return typeMap[c]; }; - - return tokenData; + + return tokenData; }; TokenStreamBase.prototype = { - - //restore constructor constructor: TokenStreamBase, - - //------------------------------------------------------------------------- - // Matching methods - //------------------------------------------------------------------------- - - /** - * Determines if the next token matches the given token type. - * If so, that token is consumed; if not, the token is placed - * back onto the token stream. You can pass in any number of - * token types and this will return true if any of the token - * types is found. - * @param {int|int[]} tokenTypes Either a single token type or an array of - * token types that the next token might be. If an array is passed, - * it's assumed that the token can be any of these. - * @param {variant} channel (Optional) The channel to read from. If not - * provided, reads from the default (unnamed) channel. - * @return {Boolean} True if the token type matches, false if not. - * @method match - */ match: function(tokenTypes, channel){ - - //always convert to an array, makes things easier if (!(tokenTypes instanceof Array)){ tokenTypes = [tokenTypes]; } @@ -3321,26 +2235,12 @@ TokenStreamBase.prototype = { return true; } } - - //no match found, put the token back this.unget(); return false; - }, - - /** - * Determines if the next token matches the given token type. - * If so, that token is consumed; if not, an error is thrown. - * @param {int|int[]} tokenTypes Either a single token type or an array of - * token types that the next token should be. If an array is passed, - * it's assumed that the token must be one of these. - * @param {variant} channel (Optional) The channel to read from. If not - * provided, reads from the default (unnamed) channel. - * @return {void} - * @method mustMatch - */ + }, mustMatch: function(tokenTypes, channel){ - //always convert to an array, makes things easier + var token; if (!(tokenTypes instanceof Array)){ tokenTypes = [tokenTypes]; } @@ -3351,36 +2251,14 @@ TokenStreamBase.prototype = { " at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); } }, - - //------------------------------------------------------------------------- - // Consuming methods - //------------------------------------------------------------------------- - - /** - * Keeps reading from the token stream until either one of the specified - * token types is found or until the end of the input is reached. - * @param {int|int[]} tokenTypes Either a single token type or an array of - * token types that the next token should be. If an array is passed, - * it's assumed that the token must be one of these. - * @param {variant} channel (Optional) The channel to read from. If not - * provided, reads from the default (unnamed) channel. - * @return {void} - * @method advance - */ advance: function(tokenTypes, channel){ - while(this.LA(0) != 0 && !this.match(tokenTypes, channel)){ + while(this.LA(0) !== 0 && !this.match(tokenTypes, channel)){ this.get(); } return this.LA(0); - }, - - /** - * Consumes the next token from the token stream. - * @return {int} The token type of the token that was just consumed. - * @method get - */ + }, get: function(channel){ var tokenInfo = this._tokenData, @@ -3391,102 +2269,57 @@ TokenStreamBase.prototype = { found = false, token, info; - - //check the lookahead buffer first if (this._lt.length && this._ltIndex >= 0 && this._ltIndex < this._lt.length){ i++; this._token = this._lt[this._ltIndex++]; info = tokenInfo[this._token.type]; - - //obey channels logic while((info.channel !== undefined && channel !== info.channel) && this._ltIndex < this._lt.length){ this._token = this._lt[this._ltIndex++]; info = tokenInfo[this._token.type]; i++; } - - //here be dragons if ((info.channel === undefined || channel === info.channel) && this._ltIndex <= this._lt.length){ this._ltIndexCache.push(i); return this._token.type; } } - - //call token retriever method - token = this._getToken(); - - //if it should be hidden, don't save a token + token = this._getToken(); 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++; @@ -3505,78 +2338,28 @@ TokenStreamBase.prototype = { 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]; @@ -3589,6 +2372,7 @@ TokenStreamBase.prototype = { + parserlib.util = { StringReader: StringReader, SyntaxError : SyntaxError, @@ -3597,31 +2381,6 @@ 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, @@ -3629,6 +2388,7 @@ StringReader = parserlib.util.StringReader, SyntaxError = parserlib.util.SyntaxError, SyntaxUnit = parserlib.util.SyntaxUnit; + var Colors = { aliceblue :"#f0f8ff", antiquewhite :"#faebd7", @@ -3697,7 +2457,7 @@ var Colors = { lightcoral :"#f08080", lightcyan :"#e0ffff", lightgoldenrodyellow :"#fafad2", - lightgrey :"#d3d3d3", + lightgray :"#d3d3d3", lightgreen :"#90ee90", lightpink :"#ffb6c1", lightsalmon :"#ffa07a", @@ -3771,28 +2531,10 @@ var Colors = { 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 - */ + SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE); this.type = "unknown"; - - //pretty simple if (/^\s+$/.test(text)){ this.type = "descendant"; } else if (text == ">"){ @@ -3807,274 +2549,27 @@ function Combinator(text, line, col){ 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 - */ + SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol, Parser.MEDIA_FEATURE_TYPE); 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 - */ + SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType + " " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE); 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.mediaType = mediaType; this.features = features; } MediaQuery.prototype = new SyntaxUnit(); MediaQuery.prototype.constructor = MediaQuery; - - -/** - * A CSS3 parser. - * @namespace parserlib.css - * @class Parser - * @constructor - * @param {Object} options (Optional) Various options for the parser: - * starHack (true|false) to allow IE6 star hack as valid, - * underscoreHack (true|false) to interpret leading underscores - * as IE6-7 targeting for known properties, ieFilters (true|false) - * to indicate that IE < 8 filters should be accepted and not throw - * syntax errors. - */ function Parser(options){ - - //inherit event functionality EventTarget.call(this); @@ -4082,59 +2577,55 @@ function Parser(options){ this._tokenStream = null; } +Parser.DEFAULT_TYPE = 0; +Parser.COMBINATOR_TYPE = 1; +Parser.MEDIA_FEATURE_TYPE = 2; +Parser.MEDIA_QUERY_TYPE = 3; +Parser.PROPERTY_NAME_TYPE = 4; +Parser.PROPERTY_VALUE_TYPE = 5; +Parser.PROPERTY_VALUE_PART_TYPE = 6; +Parser.SELECTOR_TYPE = 7; +Parser.SELECTOR_PART_TYPE = 8; +Parser.SELECTOR_SUB_PART_TYPE = 9; Parser.prototype = function(){ var proto = new EventTarget(), //new prototype prop, additions = { - - //restore constructor constructor: Parser, + DEFAULT_TYPE : 0, + COMBINATOR_TYPE : 1, + MEDIA_FEATURE_TYPE : 2, + MEDIA_QUERY_TYPE : 3, + PROPERTY_NAME_TYPE : 4, + PROPERTY_VALUE_TYPE : 5, + PROPERTY_VALUE_PART_TYPE : 6, + SELECTOR_TYPE : 7, + SELECTOR_PART_TYPE : 8, + SELECTOR_SUB_PART_TYPE : 9, - //----------------------------------------------------------------- - // Grammar - //----------------------------------------------------------------- - - _stylesheet: function(){ - - /* - * stylesheet - * : [ CHARSET_SYM S* STRING S* ';' ]? - * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* - * [ namespace [S|CDO|CDC]* ]* - * [ [ ruleset | media | page | font_face | keyframes ] [S|CDO|CDC]* ]* - * ; - */ + _stylesheet: function(){ var tokenStream = this._tokenStream, charset = null, + count, token, tt; this.fire("startstylesheet"); - - //try to read character set this._charset(); this._skipCruft(); - - //try to read imports - may be more than one while (tokenStream.peek() == Tokens.IMPORT_SYM){ this._import(); this._skipCruft(); } - - //try to read namespaces - may be more than one while (tokenStream.peek() == Tokens.NAMESPACE_SYM){ this._namespace(); this._skipCruft(); } - - //get the next token tt = tokenStream.peek(); - - //try to read the rest while(tt > Tokens.EOF){ try { @@ -4155,14 +2646,36 @@ Parser.prototype = function(){ case Tokens.KEYFRAMES_SYM: this._keyframes(); this._skipCruft(); - break; + break; + case Tokens.UNKNOWN_SYM: //unknown @ rule + tokenStream.get(); + if (!this.options.strict){ + this.fire({ + type: "error", + error: null, + message: "Unknown @ rule: " + tokenStream.LT(0).value + ".", + line: tokenStream.LT(0).startLine, + col: tokenStream.LT(0).startCol + }); + count=0; + while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) == Tokens.LBRACE){ + count++; //keep track of nesting depth + } + + while(count){ + tokenStream.advance([Tokens.RBRACE]); + count--; + } + + } else { + throw new SyntaxError("Unknown @ rule.", tokenStream.LT(0).startLine, tokenStream.LT(0).startCol); + } + break; case Tokens.S: this._readWhitespace(); break; default: if(!this._ruleset()){ - - //error handling for known issues switch(tt){ case Tokens.CHARSET_SYM: token = tokenStream.LT(1); @@ -4238,34 +2751,23 @@ Parser.prototype = function(){ } }, - _import: function(emit){ - /* - * import - * : IMPORT_SYM S* - * [STRING|URI] S* media_query_list? ';' S* - */ + _import: function(emit){ 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(); @@ -4281,41 +2783,26 @@ Parser.prototype = function(){ }, - _namespace: function(emit){ - /* - * namespace - * : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* - */ + _namespace: function(emit){ 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(); @@ -4332,17 +2819,10 @@ Parser.prototype = function(){ }, _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; @@ -4379,15 +2859,7 @@ Parser.prototype = function(){ col: col }); }, - - - //CSS3 Media Queries _media_query_list: function(){ - /* - * media_query_list - * : S* [media_query [ ',' S* media_query ]* ]? - * ; - */ var tokenStream = this._tokenStream, mediaList = []; @@ -4405,19 +2877,7 @@ Parser.prototype = function(){ 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, @@ -4426,8 +2886,6 @@ Parser.prototype = function(){ 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; @@ -4466,31 +2924,10 @@ Parser.prototype = function(){ 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, @@ -4512,36 +2949,19 @@ Parser.prototype = function(){ 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* - * ; - */ + _page: function(){ 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; @@ -4550,14 +2970,10 @@ Parser.prototype = function(){ 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(); } @@ -4583,14 +2999,7 @@ Parser.prototype = function(){ }); }, - - //CSS3 Paged Media _margin: function(){ - /* - * margin : - * margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* - * ; - */ var tokenStream = this._tokenStream, line, col, @@ -4620,31 +3029,8 @@ Parser.prototype = function(){ 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, @@ -4663,34 +3049,19 @@ Parser.prototype = function(){ }, _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* - * ; - */ + _font_face: function(){ var tokenStream = this._tokenStream, line, col; - - //look for @page tokenStream.mustMatch(Tokens.FONT_FACE_SYM); line = tokenStream.token().startLine; col = tokenStream.token().startCol; @@ -4712,13 +3083,7 @@ Parser.prototype = function(){ }); }, - _operator: function(){ - - /* - * operator - * : '/' S* | ',' S* | /( empty )/ - * ; - */ + _operator: function(){ var tokenStream = this._tokenStream, token = null; @@ -4731,13 +3096,7 @@ Parser.prototype = function(){ }, - _combinator: function(){ - - /* - * combinator - * : PLUS S* | GREATER S* | TILDE S* | S+ - * ; - */ + _combinator: function(){ var tokenStream = this._tokenStream, value = null, @@ -4753,12 +3112,6 @@ Parser.prototype = function(){ }, _unary_operator: function(){ - - /* - * unary_operator - * : '-' | '+' - * ; - */ var tokenStream = this._tokenStream; @@ -4770,12 +3123,6 @@ Parser.prototype = function(){ }, _property: function(){ - - /* - * property - * : IDENT S* - * ; - */ var tokenStream = this._tokenStream, value = null, @@ -4784,8 +3131,6 @@ Parser.prototype = function(){ 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(); @@ -4797,8 +3142,6 @@ Parser.prototype = function(){ 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); @@ -4810,31 +3153,15 @@ Parser.prototype = function(){ return value; }, - - //Augmented with CSS3 Selectors - _ruleset: function(){ - /* - * ruleset - * : selectors_group - * '{' S* declaration? [ ';' S* declaration? ]* '}' S* - * ; - */ + _ruleset: function(){ 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, @@ -4842,26 +3169,17 @@ Parser.prototype = function(){ 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({ @@ -4885,15 +3203,7 @@ Parser.prototype = function(){ return selectors; }, - - //CSS3 Selectors - _selectors_group: function(){ - - /* - * selectors_group - * : selector [ COMMA S* selector ]* - * ; - */ + _selectors_group: function(){ var tokenStream = this._tokenStream, selectors = [], selector; @@ -4915,22 +3225,13 @@ Parser.prototype = function(){ 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; @@ -4939,34 +3240,20 @@ Parser.prototype = function(){ 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)); + this._unexpectedToken(tokenStream.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){ @@ -4991,29 +3278,13 @@ Parser.prototype = function(){ 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) : @@ -5030,9 +3301,6 @@ Parser.prototype = function(){ found = false, line, col; - - - //get starting line and column for the selector line = tokenStream.LT(1).startLine; col = tokenStream.LT(1).startCol; @@ -5046,20 +3314,14 @@ Parser.prototype = function(){ } 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 { @@ -5078,26 +3340,13 @@ Parser.prototype = function(){ 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){ @@ -5114,14 +3363,7 @@ Parser.prototype = function(){ return elementName; } }, - - //CSS3 Selectors - _class: function(){ - /* - * class - * : '.' IDENT - * ; - */ + _class: function(){ var tokenStream = this._tokenStream, token; @@ -5135,14 +3377,7 @@ Parser.prototype = function(){ } }, - - //CSS3 Selectors - _element_name: function(){ - /* - * element_name - * : IDENT - * ; - */ + _element_name: function(){ var tokenStream = this._tokenStream, token; @@ -5155,18 +3390,9 @@ Parser.prototype = function(){ 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])){ @@ -5180,14 +3406,7 @@ Parser.prototype = function(){ return value.length ? value : null; }, - - //CSS3 Selectors _universal: function(){ - /* - * universal - * : [ namespace_prefix ]? '*' - * ; - */ var tokenStream = this._tokenStream, value = "", ns; @@ -5204,21 +3423,7 @@ Parser.prototype = function(){ 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, @@ -5258,15 +3463,7 @@ Parser.prototype = function(){ return null; } }, - - //CSS3 Selectors - _pseudo: function(){ - - /* - * pseudo - * : ':' ':'? [ IDENT | functional_pseudo ] - * ; - */ + _pseudo: function(){ var tokenStream = this._tokenStream, pseudo = null, @@ -5297,14 +3494,7 @@ Parser.prototype = function(){ return pseudo; }, - - //CSS3 Selectors - _functional_pseudo: function(){ - /* - * functional_pseudo - * : FUNCTION S* expression ')' - * ; - */ + _functional_pseudo: function(){ var tokenStream = this._tokenStream, value = null; @@ -5319,14 +3509,7 @@ Parser.prototype = function(){ return value; }, - - //CSS3 Selectors _expression: function(){ - /* - * expression - * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ - * ; - */ var tokenStream = this._tokenStream, value = ""; @@ -5343,14 +3526,7 @@ Parser.prototype = function(){ return value.length ? value : null; }, - - //CSS3 Selectors _negation: function(){ - /* - * negation - * : NOT S* negation_arg S* ')' - * ; - */ var tokenStream = this._tokenStream, line, @@ -5376,14 +3552,7 @@ Parser.prototype = function(){ return subpart; }, - - //CSS3 Selectors - _negation_arg: function(){ - /* - * negation_arg - * : type_selector | universal | HASH | class | attrib | pseudo - * ; - */ + _negation_arg: function(){ var tokenStream = this._tokenStream, args = [ @@ -5414,13 +3583,9 @@ Parser.prototype = function(){ 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 { @@ -5430,19 +3595,15 @@ Parser.prototype = function(){ return part; }, - _declaration: function(){ - - /* - * declaration - * : property ':' S* expr prio? - * | /( empty )/ - * ; - */ + _declaration: function(){ var tokenStream = this._tokenStream, property = null, expr = null, - prio = null; + prio = null, + error = null, + invalid = null, + propertyName= ""; property = this._property(); if (property !== null){ @@ -5451,13 +3612,23 @@ Parser.prototype = function(){ 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(); + propertyName = property.toString(); + if (this.options.starHack && property.hack == "*" || + this.options.underscoreHack && property.hack == "_") { + + propertyName = property.text; + } + + try { + this._validateProperty(propertyName, expr); + } catch (ex) { + invalid = ex; + } this.fire({ type: "property", @@ -5465,7 +3636,8 @@ Parser.prototype = function(){ value: expr, important: prio, line: property.line, - col: property.col + col: property.col, + invalid: invalid }); return true; @@ -5475,11 +3647,6 @@ Parser.prototype = function(){ }, _prio: function(){ - /* - * prio - * : IMPORTANT_SYM S* - * ; - */ var tokenStream = this._tokenStream, result = tokenStream.match(Tokens.IMPORTANT_SYM); @@ -5489,15 +3656,9 @@ Parser.prototype = function(){ }, _expr: function(){ - /* - * expr - * : term [ operator term ]* - * ; - */ var tokenStream = this._tokenStream, values = [], - //valueParts = [], value = null, operator = null; @@ -5508,12 +3669,9 @@ Parser.prototype = function(){ 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 = []; }*/ @@ -5527,40 +3685,23 @@ Parser.prototype = function(){ } } 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; + return values.length > 0 ? new PropertyValue(values, values[0].line, values[0].col) : null; }, - _term: function(){ - - /* - * term - * : unary_operator? - * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* | - * TIME S* | FREQ S* | function | ie_function ] - * | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor - * ; - */ + _term: function(){ var tokenStream = this._tokenStream, unary = null, value = null, + token, line, col; - - //returns the operator or null unary = this._unary_operator(); if (unary !== null){ line = tokenStream.token().startLine; col = tokenStream.token().startCol; } - - //exception for IE filters if (tokenStream.peek() == Tokens.IE_FUNCTION && this.options.ieFilters){ value = this._ie_function(); @@ -5568,8 +3709,6 @@ Parser.prototype = function(){ 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])){ @@ -5581,40 +3720,25 @@ Parser.prototype = function(){ } 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 + token = this._hexcolor(); + if (token === null){ if (unary === null){ line = tokenStream.LT(1).startLine; col = tokenStream.LT(1).startCol; } - - //has to be a function if (value === null){ - - /* - * This checks for alpha(opacity=0) style of IE - * functions. IE_FUNCTION only presents progid: style. - */ if (tokenStream.LA(3) == Tokens.EQUALS && this.options.ieFilters){ value = this._ie_function(); } else { value = this._function(); } } - - /*if (value === null){ - return null; - //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + "."); - }*/ } else { + value = token.value; if (unary === null){ - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; + line = token.startLine; + col = token.startCol; } } @@ -5627,24 +3751,43 @@ Parser.prototype = function(){ }, _function: function(){ - - /* - * function - * : FUNCTION S* expr ')' S* - * ; - */ var tokenStream = this._tokenStream, functionText = null, - expr = null; + expr = null, + lt; if (tokenStream.match(Tokens.FUNCTION)){ functionText = tokenStream.token().value; this._readWhitespace(); expr = this._expr(); + functionText += expr; + if (this.options.ieFilters && tokenStream.peek() == Tokens.EQUALS){ + do { + + if (this._readWhitespace()){ + functionText += tokenStream.token().value; + } + 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; + 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 += expr + ")"; + functionText += ")"; this._readWhitespace(); } @@ -5652,19 +3795,11 @@ Parser.prototype = function(){ }, _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; @@ -5673,8 +3808,6 @@ Parser.prototype = function(){ if (this._readWhitespace()){ functionText += tokenStream.token().value; } - - //might be second time in the loop if (tokenStream.LA(0) == Tokens.COMMA){ functionText += tokenStream.token().value; } @@ -5684,8 +3817,6 @@ Parser.prototype = function(){ 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(); @@ -5703,23 +3834,12 @@ Parser.prototype = function(){ }, _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; - + token = null, + color; + if(tokenStream.match(Tokens.HASH)){ - - //need to do some validation here token = tokenStream.token(); color = token.value; @@ -5729,26 +3849,22 @@ Parser.prototype = function(){ this._readWhitespace(); } - return color; + return token; }, - //----------------------------------------------------------------- - // Animations methods - //----------------------------------------------------------------- - _keyframes: function(){ - - /* - * keyframes: - * : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' { - * ; - */ var tokenStream = this._tokenStream, token, tt, - name; + name, + prefix = ""; tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); + token = tokenStream.token(); + if (/^@\-([^\-]+)\-/.test(token.value)) { + prefix = RegExp.$1; + } + this._readWhitespace(); name = this._keyframe_name(); @@ -5758,14 +3874,13 @@ Parser.prototype = function(){ this.fire({ type: "startkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); tt = tokenStream.peek(); - - //check for key while(tt == Tokens.IDENT || tt == Tokens.PERCENTAGE) { this._keyframe_rule(); this._readWhitespace(); @@ -5775,8 +3890,9 @@ Parser.prototype = function(){ this.fire({ type: "endkeyframes", name: name, - line: name.line, - col: name.col + prefix: prefix, + line: token.startLine, + col: token.startCol }); this._readWhitespace(); @@ -5785,13 +3901,6 @@ Parser.prototype = function(){ }, _keyframe_name: function(){ - - /* - * keyframe_name: - * : IDENT - * | STRING - * ; - */ var tokenStream = this._tokenStream, token; @@ -5800,13 +3909,6 @@ Parser.prototype = function(){ }, _keyframe_rule: function(){ - - /* - * keyframe_rule: - * : key_list S* - * '{' S* declaration [ ';' S* declaration ]* '}' S* - * ; - */ var tokenStream = this._tokenStream, token, keyList = this._key_list(); @@ -5830,18 +3932,10 @@ Parser.prototype = function(){ }, _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(); @@ -5856,14 +3950,6 @@ Parser.prototype = function(){ }, _key: function(){ - /* - * There is a restriction that IDENT can be only "from" or "to". - * - * key - * : PERCENTAGE - * | IDENT - * ; - */ var tokenStream = this._tokenStream, token; @@ -5879,50 +3965,13 @@ Parser.prototype = function(){ 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; @@ -5939,8 +3988,7 @@ Parser.prototype = function(){ while(true){ - if (readMargins && this._margin()){ - //noop + if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())){ } else if (this._declaration()){ if (!tokenStream.match(Tokens.SEMICOLON)){ break; @@ -5948,10 +3996,6 @@ Parser.prototype = function(){ } else { break; } - - //if ((!this._margin() && !this._declaration()) || !tokenStream.match(Tokens.SEMICOLON)){ - // break; - //} this._readWhitespace(); } @@ -5960,8 +4004,6 @@ Parser.prototype = function(){ } catch (ex) { if (ex instanceof SyntaxError && !this.options.strict){ - - //fire error event this.fire({ type: "error", error: ex, @@ -5969,36 +4011,19 @@ Parser.prototype = function(){ 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 + this._readDeclarations(false, readMargins); + } else if (tt != Tokens.RBRACE){ 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, @@ -6010,34 +4035,17 @@ Parser.prototype = function(){ 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 - //----------------------------------------------------------------- + _validateProperty: function(property, value){ + Validation.validate(property, value); + }, parse: function(input){ this._tokenStream = new TokenStream(input, Tokens); @@ -6045,202 +4053,558 @@ Parser.prototype = function(){ }, 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; + }, + parseStyleAttribute: function(input){ + input += "}"; // for error recovery in _readDeclarations() + this._tokenStream = new TokenStream(input, Tokens); + this._readDeclarations(); } - }; - - //copy over onto prototype for (prop in additions){ - proto[prop] = additions[prop]; + if (additions.hasOwnProperty(prop)){ + 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. - */ +var Properties = { + "alignment-adjust" : "auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | <percentage> | <length>", + "alignment-baseline" : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", + "animation" : 1, + "animation-delay" : { multi: "<time>", comma: true }, + "animation-direction" : { multi: "normal | alternate", comma: true }, + "animation-duration" : { multi: "<time>", comma: true }, + "animation-iteration-count" : { multi: "<number> | infinite", comma: true }, + "animation-name" : { multi: "none | <ident>", comma: true }, + "animation-play-state" : { multi: "running | paused", comma: true }, + "animation-timing-function" : 1, + "-moz-animation-delay" : { multi: "<time>", comma: true }, + "-moz-animation-direction" : { multi: "normal | alternate", comma: true }, + "-moz-animation-duration" : { multi: "<time>", comma: true }, + "-moz-animation-iteration-count" : { multi: "<number> | infinite", comma: true }, + "-moz-animation-name" : { multi: "none | <ident>", comma: true }, + "-moz-animation-play-state" : { multi: "running | paused", comma: true }, + + "-ms-animation-delay" : { multi: "<time>", comma: true }, + "-ms-animation-direction" : { multi: "normal | alternate", comma: true }, + "-ms-animation-duration" : { multi: "<time>", comma: true }, + "-ms-animation-iteration-count" : { multi: "<number> | infinite", comma: true }, + "-ms-animation-name" : { multi: "none | <ident>", comma: true }, + "-ms-animation-play-state" : { multi: "running | paused", comma: true }, + + "-webkit-animation-delay" : { multi: "<time>", comma: true }, + "-webkit-animation-direction" : { multi: "normal | alternate", comma: true }, + "-webkit-animation-duration" : { multi: "<time>", comma: true }, + "-webkit-animation-iteration-count" : { multi: "<number> | infinite", comma: true }, + "-webkit-animation-name" : { multi: "none | <ident>", comma: true }, + "-webkit-animation-play-state" : { multi: "running | paused", comma: true }, + + "-o-animation-delay" : { multi: "<time>", comma: true }, + "-o-animation-direction" : { multi: "normal | alternate", comma: true }, + "-o-animation-duration" : { multi: "<time>", comma: true }, + "-o-animation-iteration-count" : { multi: "<number> | infinite", comma: true }, + "-o-animation-name" : { multi: "none | <ident>", comma: true }, + "-o-animation-play-state" : { multi: "running | paused", comma: true }, + + "appearance" : "icon | window | desktop | workspace | document | tooltip | dialog | button | push-button | hyperlink | radio-button | checkbox | menu-item | tab | menu | menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | outline-tree | range | field | combo-box | signature | password | normal | inherit", + "azimuth" : function (expression) { + var simple = "<angle> | leftwards | rightwards | inherit", + direction = "left-side | far-left | left | center-left | center | center-right | right | far-right | right-side", + behind = false, + valid = false, + part; + + if (!ValidationTypes.isAny(expression, simple)) { + if (ValidationTypes.isAny(expression, "behind")) { + behind = true; + valid = true; + } + + if (ValidationTypes.isAny(expression, direction)) { + valid = true; + if (!behind) { + ValidationTypes.isAny(expression, "behind"); + } + } + } + + if (expression.hasNext()) { + part = expression.next(); + if (valid) { + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + throw new ValidationError("Expected (<'azimuth'>) but found '" + part + "'.", part.line, part.col); + } + } + }, + "backface-visibility" : "visible | hidden", + "background" : 1, + "background-attachment" : { multi: "<attachment>", comma: true }, + "background-clip" : { multi: "<box>", comma: true }, + "background-color" : "<color> | inherit", + "background-image" : { multi: "<bg-image>", comma: true }, + "background-origin" : { multi: "<box>", comma: true }, + "background-position" : { multi: "<bg-position>", comma: true }, + "background-repeat" : { multi: "<repeat-style>" }, + "background-size" : { multi: "<bg-size>", comma: true }, + "baseline-shift" : "baseline | sub | super | <percentage> | <length>", + "behavior" : 1, + "binding" : 1, + "bleed" : "<length>", + "bookmark-label" : "<content> | <attr> | <string>", + "bookmark-level" : "none | <integer>", + "bookmark-state" : "open | closed", + "bookmark-target" : "none | <uri> | <attr>", + "border" : "<border-width> || <border-style> || <color>", + "border-bottom" : "<border-width> || <border-style> || <color>", + "border-bottom-color" : "<color>", + "border-bottom-left-radius" : "<x-one-radius>", + "border-bottom-right-radius" : "<x-one-radius>", + "border-bottom-style" : "<border-style>", + "border-bottom-width" : "<border-width>", + "border-collapse" : "collapse | separate | inherit", + "border-color" : { multi: "<color> | inherit", max: 4 }, + "border-image" : 1, + "border-image-outset" : { multi: "<length> | <number>", max: 4 }, + "border-image-repeat" : { multi: "stretch | repeat | round", max: 2 }, + "border-image-slice" : function(expression) { + + var valid = false, + numeric = "<number> | <percentage>", + fill = false, + count = 0, + max = 4, + part; + + if (ValidationTypes.isAny(expression, "fill")) { + fill = true; + valid = true; + } + + while (expression.hasNext() && count < max) { + valid = ValidationTypes.isAny(expression, numeric); + if (!valid) { + break; + } + count++; + } + + + if (!fill) { + ValidationTypes.isAny(expression, "fill"); + } else { + valid = true; + } + + if (expression.hasNext()) { + part = expression.next(); + if (valid) { + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + throw new ValidationError("Expected ([<number> | <percentage>]{1,4} && fill?) but found '" + part + "'.", part.line, part.col); + } + } + }, + "border-image-source" : "<image> | none", + "border-image-width" : { multi: "<length> | <percentage> | <number> | auto", max: 4 }, + "border-left" : "<border-width> || <border-style> || <color>", + "border-left-color" : "<color> | inherit", + "border-left-style" : "<border-style>", + "border-left-width" : "<border-width>", + "border-radius" : function(expression) { + + var valid = false, + numeric = "<length> | <percentage>", + slash = false, + fill = false, + count = 0, + max = 8, + part; + + while (expression.hasNext() && count < max) { + valid = ValidationTypes.isAny(expression, numeric); + if (!valid) { + + if (expression.peek() == "/" && count > 1 && !slash) { + slash = true; + max = count + 5; + expression.next(); + } else { + break; + } + } + count++; + } + + if (expression.hasNext()) { + part = expression.next(); + if (valid) { + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + throw new ValidationError("Expected (<'border-radius'>) but found '" + part + "'.", part.line, part.col); + } + } + }, + "border-right" : "<border-width> || <border-style> || <color>", + "border-right-color" : "<color> | inherit", + "border-right-style" : "<border-style>", + "border-right-width" : "<border-width>", + "border-spacing" : { multi: "<length> | inherit", max: 2 }, + "border-style" : { multi: "<border-style>", max: 4 }, + "border-top" : "<border-width> || <border-style> || <color>", + "border-top-color" : "<color> | inherit", + "border-top-left-radius" : "<x-one-radius>", + "border-top-right-radius" : "<x-one-radius>", + "border-top-style" : "<border-style>", + "border-top-width" : "<border-width>", + "border-width" : { multi: "<border-width>", max: 4 }, + "bottom" : "<margin-width> | inherit", + "box-align" : "start | end | center | baseline | stretch", //http://www.w3.org/TR/2009/WD-css3-flexbox-20090723/ + "box-decoration-break" : "slice |clone", + "box-direction" : "normal | reverse | inherit", + "box-flex" : "<number>", + "box-flex-group" : "<integer>", + "box-lines" : "single | multiple", + "box-ordinal-group" : "<integer>", + "box-orient" : "horizontal | vertical | inline-axis | block-axis | inherit", + "box-pack" : "start | end | center | justify", + "box-shadow" : function (expression) { + var result = false, + part; + + if (!ValidationTypes.isAny(expression, "none")) { + Validation.multiProperty("<shadow>", expression, true, Infinity); + } else { + if (expression.hasNext()) { + part = expression.next(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } + } + }, + "box-sizing" : "content-box | border-box | inherit", + "break-after" : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column", + "break-before" : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column", + "break-inside" : "auto | avoid | avoid-page | avoid-column", + "caption-side" : "top | bottom | inherit", + "clear" : "none | right | left | both | inherit", + "clip" : 1, + "color" : "<color> | inherit", + "color-profile" : 1, + "column-count" : "<integer> | auto", //http://www.w3.org/TR/css3-multicol/ + "column-fill" : "auto | balance", + "column-gap" : "<length> | normal", + "column-rule" : "<border-width> || <border-style> || <color>", + "column-rule-color" : "<color>", + "column-rule-style" : "<border-style>", + "column-rule-width" : "<border-width>", + "column-span" : "none | all", + "column-width" : "<length> | auto", + "columns" : 1, + "content" : 1, + "counter-increment" : 1, + "counter-reset" : 1, + "crop" : "<shape> | auto", + "cue" : "cue-after | cue-before | inherit", + "cue-after" : 1, + "cue-before" : 1, + "cursor" : 1, + "direction" : "ltr | rtl | inherit", + "display" : "inline | block | list-item | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | box | inline-box | grid | inline-grid | none | inherit", + "dominant-baseline" : 1, + "drop-initial-after-adjust" : "central | middle | after-edge | text-after-edge | ideographic | alphabetic | mathematical | <percentage> | <length>", + "drop-initial-after-align" : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", + "drop-initial-before-adjust" : "before-edge | text-before-edge | central | middle | hanging | mathematical | <percentage> | <length>", + "drop-initial-before-align" : "caps-height | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", + "drop-initial-size" : "auto | line | <length> | <percentage>", + "drop-initial-value" : "initial | <integer>", + "elevation" : "<angle> | below | level | above | higher | lower | inherit", + "empty-cells" : "show | hide | inherit", + "filter" : 1, + "fit" : "fill | hidden | meet | slice", + "fit-position" : 1, + "float" : "left | right | none | inherit", + "float-offset" : 1, + "font" : 1, + "font-family" : 1, + "font-size" : "<absolute-size> | <relative-size> | <length> | <percentage> | inherit", + "font-size-adjust" : "<number> | none | inherit", + "font-stretch" : "normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit", + "font-style" : "normal | italic | oblique | inherit", + "font-variant" : "normal | small-caps | inherit", + "font-weight" : "normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit", + "grid-cell-stacking" : "columns | rows | layer", + "grid-column" : 1, + "grid-columns" : 1, + "grid-column-align" : "start | end | center | stretch", + "grid-column-sizing" : 1, + "grid-column-span" : "<integer>", + "grid-flow" : "none | rows | columns", + "grid-layer" : "<integer>", + "grid-row" : 1, + "grid-rows" : 1, + "grid-row-align" : "start | end | center | stretch", + "grid-row-span" : "<integer>", + "grid-row-sizing" : 1, + "hanging-punctuation" : 1, + "height" : "<margin-width> | inherit", + "hyphenate-after" : "<integer> | auto", + "hyphenate-before" : "<integer> | auto", + "hyphenate-character" : "<string> | auto", + "hyphenate-lines" : "no-limit | <integer>", + "hyphenate-resource" : 1, + "hyphens" : "none | manual | auto", + "icon" : 1, + "image-orientation" : "angle | auto", + "image-rendering" : 1, + "image-resolution" : 1, + "inline-box-align" : "initial | last | <integer>", + "left" : "<margin-width> | inherit", + "letter-spacing" : "<length> | normal | inherit", + "line-height" : "<number> | <length> | <percentage> | normal | inherit", + "line-break" : "auto | loose | normal | strict", + "line-stacking" : 1, + "line-stacking-ruby" : "exclude-ruby | include-ruby", + "line-stacking-shift" : "consider-shifts | disregard-shifts", + "line-stacking-strategy" : "inline-line-height | block-line-height | max-height | grid-height", + "list-style" : 1, + "list-style-image" : "<uri> | none | inherit", + "list-style-position" : "inside | outside | inherit", + "list-style-type" : "disc | circle | square | decimal | decimal-leading-zero | lower-roman | upper-roman | lower-greek | lower-latin | upper-latin | armenian | georgian | lower-alpha | upper-alpha | none | inherit", + "margin" : { multi: "<margin-width> | inherit", max: 4 }, + "margin-bottom" : "<margin-width> | inherit", + "margin-left" : "<margin-width> | inherit", + "margin-right" : "<margin-width> | inherit", + "margin-top" : "<margin-width> | inherit", + "mark" : 1, + "mark-after" : 1, + "mark-before" : 1, + "marks" : 1, + "marquee-direction" : 1, + "marquee-play-count" : 1, + "marquee-speed" : 1, + "marquee-style" : 1, + "max-height" : "<length> | <percentage> | none | inherit", + "max-width" : "<length> | <percentage> | none | inherit", + "min-height" : "<length> | <percentage> | inherit", + "min-width" : "<length> | <percentage> | inherit", + "move-to" : 1, + "nav-down" : 1, + "nav-index" : 1, + "nav-left" : 1, + "nav-right" : 1, + "nav-up" : 1, + "opacity" : "<number> | inherit", + "orphans" : "<integer> | inherit", + "outline" : 1, + "outline-color" : "<color> | invert | inherit", + "outline-offset" : 1, + "outline-style" : "<border-style> | inherit", + "outline-width" : "<border-width> | inherit", + "overflow" : "visible | hidden | scroll | auto | inherit", + "overflow-style" : 1, + "overflow-x" : 1, + "overflow-y" : 1, + "padding" : { multi: "<padding-width> | inherit", max: 4 }, + "padding-bottom" : "<padding-width> | inherit", + "padding-left" : "<padding-width> | inherit", + "padding-right" : "<padding-width> | inherit", + "padding-top" : "<padding-width> | inherit", + "page" : 1, + "page-break-after" : "auto | always | avoid | left | right | inherit", + "page-break-before" : "auto | always | avoid | left | right | inherit", + "page-break-inside" : "auto | avoid | inherit", + "page-policy" : 1, + "pause" : 1, + "pause-after" : 1, + "pause-before" : 1, + "perspective" : 1, + "perspective-origin" : 1, + "phonemes" : 1, + "pitch" : 1, + "pitch-range" : 1, + "play-during" : 1, + "pointer-events" : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit", + "position" : "static | relative | absolute | fixed | inherit", + "presentation-level" : 1, + "punctuation-trim" : 1, + "quotes" : 1, + "rendering-intent" : 1, + "resize" : 1, + "rest" : 1, + "rest-after" : 1, + "rest-before" : 1, + "richness" : 1, + "right" : "<margin-width> | inherit", + "rotation" : 1, + "rotation-point" : 1, + "ruby-align" : 1, + "ruby-overhang" : 1, + "ruby-position" : 1, + "ruby-span" : 1, + "size" : 1, + "speak" : "normal | none | spell-out | inherit", + "speak-header" : "once | always | inherit", + "speak-numeral" : "digits | continuous | inherit", + "speak-punctuation" : "code | none | inherit", + "speech-rate" : 1, + "src" : 1, + "stress" : 1, + "string-set" : 1, + + "table-layout" : "auto | fixed | inherit", + "tab-size" : "<integer> | <length>", + "target" : 1, + "target-name" : 1, + "target-new" : 1, + "target-position" : 1, + "text-align" : "left | right | center | justify | inherit" , + "text-align-last" : 1, + "text-decoration" : 1, + "text-emphasis" : 1, + "text-height" : 1, + "text-indent" : "<length> | <percentage> | inherit", + "text-justify" : "auto | none | inter-word | inter-ideograph | inter-cluster | distribute | kashida", + "text-outline" : 1, + "text-overflow" : 1, + "text-rendering" : "auto | optimizeSpeed | optimizeLegibility | geometricPrecision | inherit", + "text-shadow" : 1, + "text-transform" : "capitalize | uppercase | lowercase | none | inherit", + "text-wrap" : "normal | none | avoid", + "top" : "<margin-width> | inherit", + "transform" : 1, + "transform-origin" : 1, + "transform-style" : 1, + "transition" : 1, + "transition-delay" : 1, + "transition-duration" : 1, + "transition-property" : 1, + "transition-timing-function" : 1, + "unicode-bidi" : "normal | embed | bidi-override | inherit", + "user-modify" : "read-only | read-write | write-only | inherit", + "user-select" : "none | text | toggle | element | elements | all | inherit", + "vertical-align" : "<percentage> | <length> | baseline | sub | super | top | text-top | middle | bottom | text-bottom | inherit", + "visibility" : "visible | hidden | collapse | inherit", + "voice-balance" : 1, + "voice-duration" : 1, + "voice-family" : 1, + "voice-pitch" : 1, + "voice-pitch-range" : 1, + "voice-rate" : 1, + "voice-stress" : 1, + "voice-volume" : 1, + "volume" : 1, + "white-space" : "normal | pre | nowrap | pre-wrap | pre-line | inherit", + "white-space-collapse" : 1, + "widows" : "<integer> | inherit", + "width" : "<length> | <percentage> | auto | inherit" , + "word-break" : "normal | keep-all | break-all", + "word-spacing" : "<length> | normal | inherit", + "word-wrap" : 1, + "z-index" : "<integer> | auto | inherit", + "zoom" : "<number> | <percentage> | normal" +}; 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 - */ + SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_NAME_TYPE); 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 - */ +PropertyName.prototype.toString = function(){ + return (this.hack ? this.hack : "") + this.text; +}; function PropertyValue(parts, line, col){ - SyntaxUnit.call(this, parts.join(" "), line, col); - - /** - * The parts that make up the selector. - * @type Array - * @property parts - */ + SyntaxUnit.call(this, parts.join(" "), line, col, Parser.PROPERTY_VALUE_TYPE); 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 PropertyValueIterator(value){ + this._i = 0; + this._parts = value.parts; + this._marks = []; + this.value = value; + +} +PropertyValueIterator.prototype.count = function(){ + return this._parts.length; +}; +PropertyValueIterator.prototype.isFirst = function(){ + return this._i === 0; +}; +PropertyValueIterator.prototype.hasNext = function(){ + return (this._i < this._parts.length); +}; +PropertyValueIterator.prototype.mark = function(){ + this._marks.push(this._i); +}; +PropertyValueIterator.prototype.peek = function(count){ + return this.hasNext() ? this._parts[this._i + (count || 0)] : null; +}; +PropertyValueIterator.prototype.next = function(){ + return this.hasNext() ? this._parts[this._i++] : null; +}; +PropertyValueIterator.prototype.previous = function(){ + return this._i > 0 ? this._parts[--this._i] : null; +}; +PropertyValueIterator.prototype.restore = function(){ + if (this._marks.length){ + this._i = this._marks.pop(); + } +}; function PropertyValuePart(text, line, col){ - SyntaxUnit.apply(this,arguments); - - /** - * Indicates the type of value unit. - * @type String - * @property type - */ + SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_VALUE_PART_TYPE); this.type = "unknown"; - - //figure out what type of data it is var temp; - - //it is a measurement? if (/^([+\-]?[\d\.]+)([a-z]+)$/i.test(text)){ //dimension this.type = "dimension"; this.value = +RegExp.$1; this.units = RegExp.$2; - - //try to narrow down switch(this.units.toLowerCase()){ case "em": @@ -6252,6 +4616,7 @@ function PropertyValuePart(text, line, col){ case "in": case "pt": case "pc": + case "ch": this.type = "length"; break; @@ -6276,8 +4641,6 @@ function PropertyValuePart(text, line, col){ this.type = "resolution"; break; - //default - } } else if (/^([+\-]?[\d\.]+)%$/i.test(text)){ //percentage @@ -6315,9 +4678,36 @@ function PropertyValuePart(text, line, col){ this.red = +RegExp.$1 * 255 / 100; this.green = +RegExp.$2 * 255 / 100; this.blue = +RegExp.$3 * 255 / 100; + } else if (/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //rgba() color with absolute numbers + this.type = "color"; + this.red = +RegExp.$1; + this.green = +RegExp.$2; + this.blue = +RegExp.$3; + this.alpha = +RegExp.$4; + } else if (/^rgba\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //rgba() color with percentages + this.type = "color"; + this.red = +RegExp.$1 * 255 / 100; + this.green = +RegExp.$2 * 255 / 100; + this.blue = +RegExp.$3 * 255 / 100; + this.alpha = +RegExp.$4; + } else if (/^hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)){ //hsl() + this.type = "color"; + this.hue = +RegExp.$1; + this.saturation = +RegExp.$2 / 100; + this.lightness = +RegExp.$3 / 100; + } else if (/^hsla\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //hsla() color with percentages + this.type = "color"; + this.hue = +RegExp.$1; + this.saturation = +RegExp.$2 / 100; + this.lightness = +RegExp.$3 / 100; + this.alpha = +RegExp.$4; } else if (/^url\(["']?([^\)"']+)["']?\)/i.test(text)){ //URI this.type = "uri"; this.uri = RegExp.$1; + } else if (/^([^\(]+)\(/i.test(text)){ + this.type = "function"; + this.name = RegExp.$1; + this.value = text; } else if (/^["'][^"']*["']/.test(text)){ //string this.type = "string"; this.value = eval(text); @@ -6338,163 +4728,170 @@ function PropertyValuePart(text, line, col){ } 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.prototype.constructor = PropertyValuePart; 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. - */ +var Pseudos = { + ":first-letter": 1, + ":first-line": 1, + ":before": 1, + ":after": 1 +}; + +Pseudos.ELEMENT = 1; +Pseudos.CLASS = 2; + +Pseudos.isElement = function(pseudo){ + return pseudo.indexOf("::") === 0 || Pseudos[pseudo.toLowerCase()] == Pseudos.ELEMENT; +}; function Selector(parts, line, col){ - SyntaxUnit.call(this, parts.join(" "), line, col); - - /** - * The parts that make up the selector. - * @type Array - * @property parts - */ + SyntaxUnit.call(this, parts.join(" "), line, col, Parser.SELECTOR_TYPE); this.parts = parts; + this.specificity = Specificity.calculate(this); } Selector.prototype = new SyntaxUnit(); Selector.prototype.constructor = Selector; - - -/** - * Represents a single part of a selector string, meaning a single set of - * element name and modifiers. This does not include combinators such as - * spaces, +, >, etc. - * @namespace parserlib.css - * @class SelectorPart - * @extends parserlib.util.SyntaxUnit - * @constructor - * @param {String} elementName The element name in the selector or null - * if there is no element name. - * @param {Array} modifiers Array of individual modifiers for the element. - * May be empty if there are none. - * @param {String} text The text representation of the unit. - * @param {int} line The line of text on which the unit resides. - * @param {int} col The column of text on which the unit resides. - */ function SelectorPart(elementName, modifiers, text, line, col){ - SyntaxUnit.call(this, text, line, col); - - /** - * The tag name of the element to which this part - * of the selector affects. - * @type String - * @property elementName - */ + SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_PART_TYPE); 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 - */ + SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_SUB_PART_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; +function Specificity(a, b, c, d){ + this.a = a; + this.b = b; + this.c = c; + this.d = d; +} +Specificity.prototype = { + constructor: Specificity, + compare: function(other){ + var comps = ["a", "b", "c", "d"], + i, len; + + for (i=0, len=comps.length; i < len; i++){ + if (this[comps[i]] < other[comps[i]]){ + return -1; + } else if (this[comps[i]] > other[comps[i]]){ + return 1; + } + } + + return 0; + }, + valueOf: function(){ + return (this.a * 1000) + (this.b * 100) + (this.c * 10) + this.d; + }, + toString: function(){ + return this.a + "," + this.b + "," + this.c + "," + this.d; + } +}; +Specificity.calculate = function(selector){ + var i, len, + part, + b=0, c=0, d=0; + + function updateValues(part){ + + var i, j, len, num, + elementName = part.elementName ? part.elementName.text : "", + modifier; + + if (elementName && elementName.charAt(elementName.length-1) != "*") { + d++; + } + + for (i=0, len=part.modifiers.length; i < len; i++){ + modifier = part.modifiers[i]; + switch(modifier.type){ + case "class": + case "attribute": + c++; + break; + + case "id": + b++; + break; + + case "pseudo": + if (Pseudos.isElement(modifier.text)){ + d++; + } else { + c++; + } + break; + + case "not": + for (j=0, num=modifier.args.length; j < num; j++){ + updateValues(modifier.args[j]); + } + } + } + } + + for (i=0, len=selector.parts.length; i < len; i++){ + part = selector.parts[i]; + + if (part instanceof SelectorPart){ + updateValues(part); + } + } + + return new Specificity(0, b, c, d); +}; var h = /^[0-9a-fA-F]$/, nonascii = /^[\u0080-\uFFFF]$/, nl = /\n|\r\n|\r|\f/; -//----------------------------------------------------------------------------- -// Helper functions -//----------------------------------------------------------------------------- - function isHexDigit(c){ - return c != null && h.test(c); + return c !== null && h.test(c); } function isDigit(c){ - return c != null && /\d/.test(c); + return c !== null && /\d/.test(c); } function isWhitespace(c){ - return c != null && /\s/.test(c); + return c !== null && /\s/.test(c); } function isNewLine(c){ - return c != null && nl.test(c); + return c !== null && nl.test(c); } function isNameStart(c){ - return c != null && (/[a-z_\u0080-\uFFFF\\]/i.test(c)); + return c !== null && (/[a-z_\u0080-\uFFFF\\]/i.test(c)); } function isNameChar(c){ - return c != null && (isNameStart(c) || /[0-9\-\\]/.test(c)); + return c !== null && (isNameStart(c) || /[0-9\-\\]/.test(c)); } function isIdentStart(c){ - return c != null && (isNameStart(c) || /\-\\/.test(c)); + return c !== null && (isNameStart(c) || /\-\\/.test(c)); } function mix(receiver, supplier){ @@ -6505,34 +4902,11 @@ function mix(receiver, supplier){ } 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, @@ -6546,13 +4920,6 @@ TokenStream.prototype = mix(new TokenStreamBase(), { while(c){ switch(c){ - - /* - * Potential tokens: - * - COMMENT - * - SLASH - * - CHAR - */ case "/": if(reader.peek() == "*"){ @@ -6561,16 +4928,6 @@ TokenStream.prototype = mix(new TokenStreamBase(), { token = this.charToken(c, startLine, startCol); } break; - - /* - * Potential tokens: - * - DASHMATCH - * - INCLUDES - * - PREFIXMATCH - * - SUFFIXMATCH - * - SUBSTRINGMATCH - * - CHAR - */ case "|": case "~": case "^": @@ -6582,22 +4939,10 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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); @@ -6605,14 +4950,6 @@ TokenStream.prototype = mix(new TokenStreamBase(), { token = this.charToken(c, startLine, startCol); } break; - - /* - * Potential tokens: - * - DOT - * - NUMBER - * - DIMENSION - * - PERCENTAGE - */ case ".": if (isDigit(reader.peek())){ token = this.numberToken(c, startLine, startCol); @@ -6620,15 +4957,6 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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); @@ -6638,93 +4966,34 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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); } @@ -6735,40 +5004,15 @@ TokenStream.prototype = mix(new TokenStreamBase(), { } - - //make sure this token is wanted - //TODO: check channel break; - - c = reader.read(); } - if (!token && c == null){ + 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 || {}; @@ -6784,20 +5028,6 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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, @@ -6805,41 +5035,22 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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(); + if (rule.length > 1){ + tt = Tokens.UNKNOWN_SYM; + } else { + tt = Tokens.CHAR; + rule = first; + reader.reset(); + } } return this.createToken(tt, rule, startLine, startCol); }, - - /** - * Produces a character token based on the given character - * and location in the stream. If there's a special (non-standard) - * token name, this is used; otherwise CHAR is used. - * @param {String} c The character for the token. - * @param {int} startLine The beginning line for the character. - * @param {int} startCol The beginning column for the character. - * @return {Object} A token object. - * @method charToken - */ charToken: function(c, startLine, startCol){ var tt = Tokens.type(c); @@ -6849,34 +5060,12 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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(), @@ -6884,34 +5073,12 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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; @@ -6926,17 +5093,6 @@ TokenStream.prototype = mix(new TokenStreamBase(), { return this.charToken(first, startLine, startCol); } }, - - /** - * Produces a CDC or CHAR token based on the specified information. The - * first character is provided and the rest is read by the function to determine - * the correct token to create. - * @param {String} first The first character in the token. - * @param {int} startLine The beginning line for the character. - * @param {int} startCol The beginning column for the character. - * @return {Object} A token object. - * @method htmlCommentEndToken - */ htmlCommentEndToken: function(first, startLine, startCol){ var reader = this._reader, text = first; @@ -6951,30 +5107,15 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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; } @@ -6982,8 +5123,6 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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; @@ -6992,17 +5131,6 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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, @@ -7014,16 +5142,12 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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! + if (temp === ""){ //broken! break; } } @@ -7053,17 +5177,6 @@ TokenStream.prototype = mix(new TokenStreamBase(), { }, - - /** - * 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; @@ -7078,18 +5191,6 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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), @@ -7122,20 +5223,6 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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, @@ -7146,25 +5233,17 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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){ + if (c === null){ tt = Tokens.INVALID; } @@ -7176,29 +5255,21 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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 { @@ -7212,43 +5283,21 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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(); @@ -7257,8 +5306,6 @@ TokenStream.prototype = mix(new TokenStreamBase(), { } } - //there can't be any other characters after this point - return part; }, @@ -7311,25 +5358,17 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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){ + if (c === null){ string = ""; } @@ -7342,14 +5381,10 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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 { @@ -7357,15 +5392,11 @@ TokenStream.prototype = mix(new TokenStreamBase(), { } 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 != ")"){ + if (inner === "" || c != ")"){ uri = first; reader.reset(); } else { @@ -7378,8 +5409,6 @@ TokenStream.prototype = mix(new TokenStreamBase(), { var reader = this._reader, url = "", c = reader.peek(); - - //TODO: Check for escape and nonascii while (/^[!#$%&\\*-~]$/.test(c)){ url += reader.read(); c = reader.peek(); @@ -7439,9 +5468,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), { if (c == "*"){ while(c){ comment += c; - - //look for end of comment - if (c == "*" && reader.peek() == "/"){ + if (comment.length > 2 && c == "*" && reader.peek() == "/"){ comment += reader.read(); break; } @@ -7459,47 +5486,27 @@ TokenStream.prototype = mix(new TokenStreamBase(), { 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: "UNKNOWN_SYM" }, + { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-o-keyframes" ] }, { name: "IMPORTANT_SYM"}, - - //measurements { name: "LENGTH"}, { name: "ANGLE"}, { name: "TIME"}, @@ -7507,33 +5514,15 @@ var Tokens = [ { 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: "UNICODE_RANGE"}, { 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"}, @@ -7550,27 +5539,9 @@ var Tokens = [ { 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: "|" @@ -7661,9 +5632,472 @@ var Tokens = [ }; })(); +var Validation = { + + validate: function(property, value){ + var name = property.toString().toLowerCase(), + parts = value.parts, + expression = new PropertyValueIterator(value), + spec = Properties[name], + part, + valid, + j, count, + msg, + types, + last, + literals, + max, multi, group; + + if (!spec) { + if (name.indexOf("-") !== 0){ //vendor prefixed are ok + throw new ValidationError("Unknown property '" + property + "'.", property.line, property.col); + } + } else if (typeof spec != "number"){ + if (typeof spec == "string"){ + if (spec.indexOf("||") > -1) { + this.groupProperty(spec, expression); + } else { + this.singleProperty(spec, expression, 1); + } + + } else if (spec.multi) { + this.multiProperty(spec.multi, expression, spec.comma, spec.max || Infinity); + } else if (typeof spec == "function") { + spec(expression); + } + + } + + }, + + singleProperty: function(types, expression, max, partial) { + + var result = false, + value = expression.value, + count = 0, + part; + + while (expression.hasNext() && count < max) { + result = ValidationTypes.isAny(expression, types); + if (!result) { + break; + } + count++; + } + + if (!result) { + if (expression.hasNext() && !expression.isFirst()) { + part = expression.peek(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col); + } + } else if (expression.hasNext()) { + part = expression.next(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } + + }, + + multiProperty: function (types, expression, comma, max) { + + var result = false, + value = expression.value, + count = 0, + sep = false, + part; + + while(expression.hasNext() && !result && count < max) { + if (ValidationTypes.isAny(expression, types)) { + count++; + if (!expression.hasNext()) { + result = true; + + } else if (comma) { + if (expression.peek() == ",") { + part = expression.next(); + } else { + break; + } + } + } else { + break; + + } + } + + if (!result) { + if (expression.hasNext() && !expression.isFirst()) { + part = expression.peek(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + part = expression.previous(); + if (comma && part == ",") { + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col); + } + } + + } else if (expression.hasNext()) { + part = expression.next(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } + + }, + + groupProperty: function (types, expression, comma) { + + var result = false, + value = expression.value, + typeCount = types.split("||").length, + groups = { count: 0 }, + partial = false, + name, + part; + + while(expression.hasNext() && !result) { + name = ValidationTypes.isAnyOfGroup(expression, types); + if (name) { + if (groups[name]) { + break; + } else { + groups[name] = 1; + groups.count++; + partial = true; + + if (groups.count == typeCount || !expression.hasNext()) { + result = true; + } + } + } else { + break; + } + } + + if (!result) { + if (partial && expression.hasNext()) { + part = expression.peek(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } else { + throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col); + } + } else if (expression.hasNext()) { + part = expression.next(); + throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); + } + } + + + +}; +function ValidationError(message, line, col){ + this.col = col; + this.line = line; + this.message = message; + +} +ValidationError.prototype = new Error(); +var ValidationTypes = { + + isLiteral: function (part, literals) { + var text = part.text.toString().toLowerCase(), + args = literals.split(" | "), + i, len, found = false; + + for (i=0,len=args.length; i < len && !found; i++){ + if (text == args[i].toLowerCase()){ + found = true; + } + } + + return found; + }, + + isSimple: function(type) { + return !!this.simple[type]; + }, + + isComplex: function(type) { + return !!this.complex[type]; + }, + isAny: function (expression, types) { + var args = types.split(" | "), + i, len, found = false; + + for (i=0,len=args.length; i < len && !found && expression.hasNext(); i++){ + found = this.isType(expression, args[i]); + } + + return found; + }, + isAnyOfGroup: function(expression, types) { + var args = types.split(" || "), + i, len, found = false; + + for (i=0,len=args.length; i < len && !found; i++){ + found = this.isType(expression, args[i]); + } + + return found ? args[i-1] : false; + }, + isType: function (expression, type) { + var part = expression.peek(), + result = false; + + if (type.charAt(0) != "<") { + result = this.isLiteral(part, type); + if (result) { + expression.next(); + } + } else if (this.simple[type]) { + result = this.simple[type](part); + if (result) { + expression.next(); + } + } else { + result = this.complex[type](expression); + } + + return result; + }, + + + + simple: { + + "<absolute-size>": function(part){ + return ValidationTypes.isLiteral(part, "xx-small | x-small | small | medium | large | x-large | xx-large"); + }, + + "<attachment>": function(part){ + return ValidationTypes.isLiteral(part, "scroll | fixed | local"); + }, + + "<attr>": function(part){ + return part.type == "function" && part.name == "attr"; + }, + + "<bg-image>": function(part){ + return this["<image>"](part) || this["<gradient>"](part) || part == "none"; + }, + + "<gradient>": function(part) { + return part.type == "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?(?:repeating\-)?(?:radial\-|linear\-)?gradient/i.test(part); + }, + + "<box>": function(part){ + return ValidationTypes.isLiteral(part, "padding-box | border-box | content-box"); + }, + + "<content>": function(part){ + return part.type == "function" && part.name == "content"; + }, + + "<relative-size>": function(part){ + return ValidationTypes.isLiteral(part, "smaller | larger"); + }, + "<ident>": function(part){ + return part.type == "identifier"; + }, + + "<length>": function(part){ + return part.type == "length" || part.type == "number" || part.type == "integer" || part == "0"; + }, + + "<color>": function(part){ + return part.type == "color" || part == "transparent"; + }, + + "<number>": function(part){ + return part.type == "number" || this["<integer>"](part); + }, + + "<integer>": function(part){ + return part.type == "integer"; + }, + + "<line>": function(part){ + return part.type == "integer"; + }, + + "<angle>": function(part){ + return part.type == "angle"; + }, + + "<uri>": function(part){ + return part.type == "uri"; + }, + + "<image>": function(part){ + return this["<uri>"](part); + }, + + "<percentage>": function(part){ + return part.type == "percentage" || part == "0"; + }, + + "<border-width>": function(part){ + return this["<length>"](part) || ValidationTypes.isLiteral(part, "thin | medium | thick"); + }, + + "<border-style>": function(part){ + return ValidationTypes.isLiteral(part, "none | hidden | dotted | dashed | solid | double | groove | ridge | inset | outset"); + }, + + "<margin-width>": function(part){ + return this["<length>"](part) || this["<percentage>"](part) || ValidationTypes.isLiteral(part, "auto"); + }, + + "<padding-width>": function(part){ + return this["<length>"](part) || this["<percentage>"](part); + }, + + "<shape>": function(part){ + return part.type == "function" && (part.name == "rect" || part.name == "inset-rect"); + }, + + "<time>": function(part) { + return part.type == "time"; + } + }, + + complex: { + + "<bg-position>": function(expression){ + var types = this, + result = false, + numeric = "<percentage> | <length>", + xDir = "left | center | right", + yDir = "top | center | bottom", + part, + i, len; + + if (ValidationTypes.isAny(expression, "top | bottom")) { + result = true; + } else { + if (ValidationTypes.isAny(expression, numeric)){ + if (expression.hasNext()){ + result = ValidationTypes.isAny(expression, numeric + " | " + yDir); + } + } else if (ValidationTypes.isAny(expression, xDir)){ + if (expression.hasNext()){ + if (ValidationTypes.isAny(expression, yDir)){ + result = true; + + ValidationTypes.isAny(expression, numeric); + + } else if (ValidationTypes.isAny(expression, numeric)){ + if (ValidationTypes.isAny(expression, yDir)){ + ValidationTypes.isAny(expression, numeric); + } + + result = true; + } + } + } + } + + return result; + }, + + "<bg-size>": function(expression){ + var types = this, + result = false, + numeric = "<percentage> | <length> | auto", + part, + i, len; + + if (ValidationTypes.isAny(expression, "cover | contain")) { + result = true; + } else if (ValidationTypes.isAny(expression, numeric)) { + result = true; + ValidationTypes.isAny(expression, numeric); + } + + return result; + }, + + "<repeat-style>": function(expression){ + var result = false, + values = "repeat | space | round | no-repeat", + part; + + if (expression.hasNext()){ + part = expression.next(); + + if (ValidationTypes.isLiteral(part, "repeat-x | repeat-y")) { + result = true; + } else if (ValidationTypes.isLiteral(part, values)) { + result = true; + if (expression.hasNext() && ValidationTypes.isLiteral(expression.peek(), values)) { + expression.next(); + } + } + } + + return result; + + }, + + "<shadow>": function(expression) { + var result = false, + count = 0, + inset = false, + color = false, + part; + + if (expression.hasNext()) { + + if (ValidationTypes.isAny(expression, "inset")){ + inset = true; + } + + if (ValidationTypes.isAny(expression, "<color>")) { + color = true; + } + + while (ValidationTypes.isAny(expression, "<length>") && count < 4) { + count++; + } + + + if (expression.hasNext()) { + if (!color) { + ValidationTypes.isAny(expression, "<color>"); + } + + if (!inset) { + ValidationTypes.isAny(expression, "inset"); + } + } + + result = (count >= 2 && count <= 4); + + } + + return result; + }, + + "<x-one-radius>": function(expression) { + var result = false, + count = 0, + numeric = "<length> | <percentage>", + part; + + if (ValidationTypes.isAny(expression, numeric)){ + result = true; + + ValidationTypes.isAny(expression, numeric); + } + + return result; + } + } +}; parserlib.css = { @@ -7678,206 +6112,123 @@ MediaQuery :MediaQuery, Selector :Selector, SelectorPart :SelectorPart, SelectorSubPart :SelectorSubPart, +Specificity :Specificity, TokenStream :TokenStream, -Tokens :Tokens +Tokens :Tokens, +ValidationError :ValidationError }; })(); - -/** - * 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.version = "0.9.9"; 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.getRules = function(){ + return [].concat(rules).sort(function(a,b){ + return a.id > b.id ? 1 : 0; + }); + }; + api.getRuleset = function() { + var ruleset = {}, + i = 0, + len = rules.length; + + while (i < len){ + ruleset[rules[i++].id] = 1; //by default, everything is a warning + } + + return ruleset; + }; 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) { + api.format = function(results, filename, formatId, options) { var formatter = this.getFormatter(formatId), result = null; if (formatter){ result = formatter.startFormat(); - result += formatter.formatResults(results, filename); + result += formatter.formatResults(results, filename, options || {}); 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, + report, parser = new parserlib.css.Parser({ starHack: true, ieFilters: true, underscoreHack: true, strict: false }); - - lines = text.split(/\n\r?/g); - reporter = new Reporter(lines); - + lines = text.replace(/\n\r?/g, "$split$").split('$split$'); + 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); - } + ruleset = this.getRuleset(); + } + + reporter = new Reporter(lines, ruleset); + + ruleset.errors = 2; //always report parsing errors as errors + for (i in ruleset){ + if(ruleset.hasOwnProperty(i)){ + if (rules[i]){ + rules[i].init(parser, reporter); } } } - - //capture most horrible error type try { parser.parse(text); } catch (ex) { - reporter.error("Fatal error, cannot continue: " + ex.message, ex.line, ex.col); + reporter.error("Fatal error, cannot continue: " + ex.message, ex.line, ex.col, {}); } - return { + report = { messages : reporter.messages, stats : reporter.stats }; + report.messages.sort(function (a, b){ + if (a.rollup && !b.rollup){ + return 1; + } else if (!a.rollup && b.rollup){ + return -1; + } else { + return a.line - b.line; + } + }); + + return report; }; - //------------------------------------------------------------------------- - // Publish the API - //------------------------------------------------------------------------- - return api; })(); -/** - * An instance of Report is used to report results of the - * verification back to the main API. - * @class Reporter - * @constructor - * @param {String[]} lines The text lines of the source. - */ -function Reporter(lines){ - - /** - * List of messages being reported. - * @property messages - * @type String[] - */ +function Reporter(lines, ruleset){ 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; + this.ruleset = ruleset; } Reporter.prototype = { - - //restore constructor constructor: Reporter, - - /** - * Report an error. - * @param {String} message The message to store. - * @param {int} line The line number. - * @param {int} col The column number. - * @param {Object} rule The rule this message relates to. - * @method error - */ error: function(message, line, col, rule){ this.messages.push({ type : "error", @@ -7885,21 +6236,15 @@ Reporter.prototype = { col : col, message : message, evidence: this.lines[line-1], - rule : rule + 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.report(message, line, col, rule); + }, + report: function(message, line, col, rule){ this.messages.push({ - type : "warning", + type : this.ruleset[rule.id] == 2 ? "error" : "warning", line : line, col : col, message : message, @@ -7907,15 +6252,6 @@ Reporter.prototype = { 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", @@ -7926,13 +6262,6 @@ Reporter.prototype = { 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", @@ -7941,13 +6270,6 @@ Reporter.prototype = { 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", @@ -7956,71 +6278,50 @@ Reporter.prototype = { 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]; +CSSLint._Reporter = Reporter; +CSSLint.Util = { + mix: function(receiver, supplier){ + var prop; + + for (prop in supplier){ + if (supplier.hasOwnProperty(prop)){ + receiver[prop] = supplier[prop]; + } } - } - return prop; -} - -/* - * Polyfill for array indexOf() method. - * @param {Array} values The array to search. - * @param {Variant} value The value to search for. - * @return {int} The index of the value if found, -1 if not. - */ -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 prop; + }, + indexOf: function(values, value){ + if (values.indexOf){ + return values.indexOf(value); + } else { + for (var i=0, len=values.length; i < len; i++){ + if (values[i] === value){ + return i; + } + } + return -1; + } + }, + forEach: function(values, func) { + if (values.forEach){ + return values.forEach(func); + } else { + for (var i=0, len=values.length; i < len; i++){ + func(values[i], i, values); } } - return -1; } -} -/* - * Rule: Don't use adjoining classes (.foo.bar). - */ +}; CSSLint.addRule({ - - //rule information id: "adjoining-classes", - name: "Adjoining Classes", + name: "Disallow adjoining classes", desc: "Don't use adjoining classes.", browsers: "IE6", - - //initialization init: function(parser, reporter){ var rule = this; parser.addListener("startrule", function(event){ @@ -8035,7 +6336,7 @@ CSSLint.addRule({ selector = selectors[i]; for (j=0; j < selector.parts.length; j++){ part = selector.parts[j]; - if (part instanceof parserlib.css.SelectorPart){ + if (part.type == parser.SELECTOR_PART_TYPE){ classCount = 0; for (k=0; k < part.modifiers.length; k++){ modifier = part.modifiers[k]; @@ -8043,7 +6344,7 @@ CSSLint.addRule({ classCount++; } if (classCount > 1){ - reporter.warn("Don't use adjoining classes.", part.line, part.col, rule); + reporter.report("Don't use adjoining classes.", part.line, part.col, rule); } } } @@ -8053,18 +6354,11 @@ CSSLint.addRule({ } }); -/* - * Rule: Don't use width or height when using padding or border. - */ CSSLint.addRule({ - - //rule information id: "box-model", - name: "Box Model", + name: "Beware of broken box size", desc: "Don't use width or height when using padding or border.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this, widthProperties = { @@ -8083,12 +6377,48 @@ CSSLint.addRule({ "padding-bottom": 1, "padding-top": 1 }, - properties; + properties, + boxSizing = false; - parser.addListener("startrule", function(){ - properties = { - }; - }); + function startRule(){ + properties = {}; + boxSizing = false; + } + + function endRule(){ + var prop, value; + + if (!boxSizing) { + if (properties.height){ + for (prop in heightProperties){ + if (heightProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + if (!(prop == "padding" && value.parts.length === 2 && value.parts[0].value === 0)){ + reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } + } + } + } + + if (properties.width){ + for (prop in widthProperties){ + if (widthProperties.hasOwnProperty(prop) && properties[prop]){ + value = properties[prop].value; + + if (!(prop == "padding" && value.parts.length === 2 && value.parts[1].value === 0)){ + reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule); + } + } + } + } + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + parser.addListener("startpage", startRule); + parser.addListener("startpagemargin", startRule); + parser.addListener("startkeyframerule", startRule); parser.addListener("property", function(event){ var name = event.property.text.toLowerCase(); @@ -8098,60 +6428,47 @@ CSSLint.addRule({ properties[name] = { line: event.property.line, col: event.property.col, value: event.value }; } } else { - if (name == "width" || name == "height"){ + if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)){ properties[name] = 1; + } else if (name == "box-sizing") { + boxSizing = true; } } }); - 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); - } - } - } - } + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + parser.addListener("endpage", endRule); + parser.addListener("endpagemargin", endRule); + parser.addListener("endkeyframerule", endRule); + } - if (properties["width"]){ - for (prop in widthProperties){ - if (widthProperties.hasOwnProperty(prop) && properties[prop]){ +}); +CSSLint.addRule({ + id: "box-sizing", + name: "Disallow use of box-sizing", + desc: "The box-sizing properties isn't supported in IE6 and IE7.", + browsers: "IE6, IE7", + tags: ["Compatibility"], + init: function(parser, reporter){ + var rule = this; - 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); - } - } - } + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (name == "box-sizing"){ + reporter.report("The box-sizing property isn't supported in IE6 and IE7.", event.line, event.col, rule); } - - }); + }); } -}); -/* - * 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", + name: "Require compatible vendor prefixes", desc: "Include all compatible vendor prefixes to reach a wider range of users.", browsers: "All", - - //initialization init: function (parser, reporter) { var rule = this, compatiblePrefixes, @@ -8161,10 +6478,9 @@ CSSLint.addRule({ prefixed, i, len, + inKeyFrame = false, 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", @@ -8195,13 +6511,13 @@ CSSLint.addRule({ "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", + "column-count" : "webkit moz ms", + "column-gap" : "webkit moz ms", + "column-rule" : "webkit moz ms", + "column-rule-color" : "webkit moz ms", + "column-rule-style" : "webkit moz ms", + "column-rule-width" : "webkit moz ms", + "column-width" : "webkit moz ms", "hyphens" : "epub moz", "line-break" : "webkit ms", "margin-end" : "webkit moz", @@ -8220,11 +6536,12 @@ CSSLint.addRule({ "transition-property" : "webkit moz o", "transition-timing-function" : "webkit moz o", "user-modify" : "webkit moz", - "user-select" : "webkit moz", + "user-select" : "webkit moz ms", "word-break" : "epub ms", "writing-mode" : "epub ms" }; + for (prop in compatiblePrefixes) { if (compatiblePrefixes.hasOwnProperty(prop)) { variations = []; @@ -8236,14 +6553,26 @@ CSSLint.addRule({ arrayPush.apply(applyTo, variations); } } + parser.addListener("startrule", function () { properties = []; }); + parser.addListener("startkeyframes", function (event) { + inKeyFrame = event.prefix || true; + }); + + parser.addListener("endkeyframes", function (event) { + inKeyFrame = false; + }); + parser.addListener("property", function (event) { - var name = event.property.text; - if (applyTo.indexOf(name) > -1) { - properties.push(name); + var name = event.property; + if (CSSLint.Util.indexOf(applyTo, name.text) > -1) { + if (!inKeyFrame || typeof inKeyFrame != "string" || + name.text.indexOf("-" + inKeyFrame + "-") !== 0) { + properties.push(name); + } } }); @@ -8270,15 +6599,17 @@ CSSLint.addRule({ for (prop in compatiblePrefixes) { if (compatiblePrefixes.hasOwnProperty(prop)) { variations = compatiblePrefixes[prop]; - if (variations.indexOf(name) > -1) { - if (propertyGroups[prop] === undefined) { + if (CSSLint.Util.indexOf(variations, name.text) > -1) { + if (!propertyGroups[prop]) { propertyGroups[prop] = { full : variations.slice(0), - actual : [] + actual : [], + actualNodes: [] }; } - if (propertyGroups[prop].actual.indexOf(name) === -1) { - propertyGroups[prop].actual.push(name); + if (CSSLint.Util.indexOf(propertyGroups[prop].actual, name.text) === -1) { + propertyGroups[prop].actual.push(name.text); + propertyGroups[prop].actualNodes.push(name); } } } @@ -8294,9 +6625,9 @@ CSSLint.addRule({ if (full.length > actual.length) { for (i = 0, len = full.length; i < len; i++) { item = full[i]; - if (actual.indexOf(item) === -1) { + if (CSSLint.Util.indexOf(actual, 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); + reporter.report("The property " + item + " is compatible with " + propertiesSpecified + " and should be included as well.", value.actualNodes[0].line, value.actualNodes[0].col, rule); } } @@ -8306,22 +6637,11 @@ CSSLint.addRule({ }); } }); -/* - * 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", + name: "Require properties appropriate for display", desc: "Certain properties shouldn't be used with certain display property values.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this; @@ -8344,26 +6664,25 @@ CSSLint.addRule({ }, 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 }; + function reportProperty(name, display, msg){ + if (properties[name]){ + if (typeof propertiesToCheck[name] != "string" || properties[name].value.toLowerCase() != propertiesToCheck[name]){ + reporter.report(msg || name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule); + } } - }); + } + + function startRule(){ + properties = {}; + } - parser.addListener("endrule", function(){ + function endRule(){ var display = properties.display ? properties.display.value : null; if (display){ switch(display){ case "inline": - //height, width, margin-top, margin-bottom, float should not be used with inline reportProperty("height", display); reportProperty("width", display); reportProperty("margin", display); @@ -8373,18 +6692,15 @@ CSSLint.addRule({ 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){ + if (display.indexOf("table-") === 0){ reportProperty("margin", display); reportProperty("margin-left", display); reportProperty("margin-right", display); @@ -8392,37 +6708,68 @@ CSSLint.addRule({ reportProperty("margin-bottom", display); reportProperty("float", display); } - - //otherwise do nothing } } - }); + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + parser.addListener("startkeyframerule", startRule); + parser.addListener("startpagemargin", startRule); + parser.addListener("startpage", startRule); + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); - 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); - } + if (propertiesToCheck[name]){ + properties[name] = { value: event.value.text, line: event.property.line, col: event.property.col }; } - } + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + parser.addListener("endkeyframerule", endRule); + parser.addListener("endpagemargin", endRule); + parser.addListener("endpage", endRule); + } }); -/* - * 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({ + id: "duplicate-background-images", + name: "Disallow duplicate background images", + desc: "Every background-image should be unique. Use a common class for e.g. sprites.", + browsers: "All", + init: function(parser, reporter){ + var rule = this, + stack = {}; - //rule information + parser.addListener("property", function(event){ + var name = event.property.text, + value = event.value, + i, len; + + if (name.match(/background/i)) { + for (i=0, len=value.parts.length; i < len; i++) { + if (value.parts[i].type == 'uri') { + if (typeof stack[value.parts[i].uri] === 'undefined') { + stack[value.parts[i].uri] = event; + } + else { + reporter.report("Background image '" + value.parts[i].uri + "' was used multiple times, first declared at line " + stack[value.parts[i].uri].line + ", col " + stack[value.parts[i].uri].col + ".", event.line, event.col, rule); + } + } + } + } + }); + } +}); +CSSLint.addRule({ id: "duplicate-properties", - name: "Duplicate Properties", + name: "Disallow duplicate properties", desc: "Duplicate properties must appear one after the other.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this, properties, @@ -8435,13 +6782,15 @@ CSSLint.addRule({ parser.addListener("startrule", startRule); parser.addListener("startfontface", startRule); parser.addListener("startpage", startRule); + parser.addListener("startpagemargin", startRule); + parser.addListener("startkeyframerule", startRule); parser.addListener("property", function(event){ var property = event.property, name = property.text.toLowerCase(); if (properties[name] && (lastProperty != name || properties[name] == event.value.text)){ - reporter.warn("Duplicate property '" + event.property + "' found.", event.line, event.col, rule); + reporter.report("Duplicate property '" + event.property + "' found.", event.line, event.col, rule); } properties[name] = event.value.text; @@ -8453,18 +6802,11 @@ CSSLint.addRule({ } }); -/* - * Rule: Style rules without any properties defined should be removed. - */ CSSLint.addRule({ - - //rule information id: "empty-rules", - name: "Empty Rules", + name: "Disallow empty rules", desc: "Rules without any properties specified should be removed.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this, count = 0; @@ -8479,25 +6821,18 @@ CSSLint.addRule({ parser.addListener("endrule", function(event){ var selectors = event.selectors; - if (count == 0){ - reporter.warn("Rule is empty.", selectors[0].line, selectors[0].col, rule); + if (count === 0){ + reporter.report("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; @@ -8508,32 +6843,81 @@ CSSLint.addRule({ } }); -/* - * Rule: You shouldn't use more than 10 floats. If you do, there's probably - * room for some abstraction. - */ CSSLint.addRule({ + id: "fallback-colors", + name: "Require fallback colors", + desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.", + browsers: "IE6,IE7,IE8", + init: function(parser, reporter){ + var rule = this, + lastProperty, + propertiesToCheck = { + color: 1, + background: 1, + "background-color": 1 + }, + properties; + + function startRule(event){ + properties = {}; + lastProperty = null; + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + parser.addListener("startpage", startRule); + parser.addListener("startpagemargin", startRule); + parser.addListener("startkeyframerule", startRule); + + parser.addListener("property", function(event){ + var property = event.property, + name = property.text.toLowerCase(), + parts = event.value.parts, + i = 0, + colorType = "", + len = parts.length; + + if(propertiesToCheck[name]){ + while(i < len){ + if (parts[i].type == "color"){ + if ("alpha" in parts[i] || "hue" in parts[i]){ + + if (/([^\)]+)\(/.test(parts[i])){ + colorType = RegExp.$1.toUpperCase(); + } + + if (!lastProperty || (lastProperty.property.text.toLowerCase() != name || lastProperty.colorType != "compat")){ + reporter.report("Fallback " + name + " (hex or RGB) should precede " + colorType + " " + name + ".", event.line, event.col, rule); + } + } else { + event.colorType = "compat"; + } + } + + i++; + } + } + + lastProperty = event; + }); + + } - //rule information +}); +CSSLint.addRule({ id: "floats", - name: "Floats", + name: "Disallow too many floats", desc: "This rule tests if the float property is used too many times", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this; var count = 0; - - //count how many times "float" is used parser.addListener("property", function(event){ if (event.property.text.toLowerCase() == "float" && event.value.text.toLowerCase() != "none"){ count++; } }); - - //report the results parser.addListener("endstylesheet", function(){ reporter.stat("floats", count); if (count >= 10){ @@ -8543,18 +6927,11 @@ CSSLint.addRule({ } }); -/* - * Rule: Avoid too many @font-face declarations in the same stylesheet. - */ CSSLint.addRule({ - - //rule information id: "font-faces", - name: "Font Faces", + name: "Don't use too many web fonts", desc: "Too many different web fonts in the same stylesheet.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this, count = 0; @@ -8572,31 +6949,19 @@ CSSLint.addRule({ } }); -/* - * Rule: You shouldn't need more than 9 font-size declarations. - */ - CSSLint.addRule({ - - //rule information id: "font-sizes", - name: "Font Sizes", + name: "Disallow too many font sizes", desc: "Checks the number of font-size declarations.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this, count = 0; - - //check for use of "font-size" parser.addListener("property", function(event){ if (event.property == "font-size"){ count++; } }); - - //report the results parser.addListener("endstylesheet", function(){ reporter.stat("font-sizes", count); if (count >= 10){ @@ -8606,18 +6971,11 @@ CSSLint.addRule({ } }); -/* - * Rule: When using a vendor-prefixed gradient, make sure to use them all. - */ CSSLint.addRule({ - - //rule information id: "gradients", - name: "Gradients", + name: "Require all gradient definitions", desc: "When using a vendor-prefixed gradient, make sure to use them all.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this, gradients; @@ -8626,6 +6984,7 @@ CSSLint.addRule({ gradients = { moz: 0, webkit: 0, + oldWebkit: 0, ms: 0, o: 0 }; @@ -8633,8 +6992,10 @@ CSSLint.addRule({ parser.addListener("property", function(event){ - if (/\-(moz|ms|o|webkit)(?:\-(?:linear|radial))\-gradient/.test(event.value)){ + if (/\-(moz|ms|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)){ gradients[RegExp.$1] = 1; + } else if (/\-webkit\-gradient/i.test(event.value)){ + gradients.oldWebkit = 1; } }); @@ -8647,7 +7008,11 @@ CSSLint.addRule({ } if (!gradients.webkit){ - missing.push("Webkit (Safari, Chrome)"); + missing.push("Webkit (Safari 5+, Chrome)"); + } + + if (!gradients.oldWebkit){ + missing.push("Old Webkit (Safari 4+, Chrome)"); } if (!gradients.ms){ @@ -8658,8 +7023,8 @@ CSSLint.addRule({ 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); + if (missing.length && missing.length < 5){ + reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule); } }); @@ -8667,18 +7032,11 @@ CSSLint.addRule({ } }); -/* - * Rule: Don't use IDs for selectors. - */ CSSLint.addRule({ - - //rule information id: "ids", - name: "IDs", + name: "Disallow IDs in selectors", desc: "Selectors should not contain IDs.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this; parser.addListener("startrule", function(event){ @@ -8695,7 +7053,7 @@ CSSLint.addRule({ for (j=0; j < selector.parts.length; j++){ part = selector.parts[j]; - if (part instanceof parserlib.css.SelectorPart){ + if (part.type == parser.SELECTOR_PART_TYPE){ for (k=0; k < part.modifiers.length; k++){ modifier = part.modifiers[k]; if (modifier.type == "id"){ @@ -8706,9 +7064,9 @@ CSSLint.addRule({ } if (idCount == 1){ - reporter.warn("Don't use IDs in selectors.", selector.line, selector.col, rule); + reporter.report("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); + reporter.report(idCount + " IDs in the selector, really?", selector.line, selector.col, rule); } } @@ -8716,383 +7074,131 @@ CSSLint.addRule({ } }); -/* - * Rule: Don't use @import, use <link> instead. - */ CSSLint.addRule({ - - //rule information id: "import", - name: "@import", + name: "Disallow @import", desc: "Don't use @import, use <link> instead.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this; parser.addListener("import", function(event){ - reporter.warn("@import prevents parallel downloads, use <link> instead.", event.line, event.col, rule); + reporter.report("@import prevents parallel downloads, use <link> 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", + name: "Disallow !important", desc: "Be careful when using !important declaration", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this, count = 0; - - //warn that important is used and increment the declaration counter parser.addListener("property", function(event){ if (event.important === true){ count++; - reporter.warn("Use of !important", event.line, event.col, rule); + reporter.report("Use of !important", event.line, event.col, rule); } }); - - //if there are more than 10, show an error parser.addListener("endstylesheet", function(){ reporter.stat("important", count); if (count >= 10){ - reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specifity issues.", rule); + reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specificity 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.", + name: "Require use of known properties", + desc: "Properties should be known (listed in CSS3 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 - }; + var rule = this; 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); + if (event.invalid) { + reporter.report(event.invalid.message, event.line, event.col, rule); } }); } }); -/* - * Rule: Don't use classes or IDs with elements (a.foo or a#foo). - */ CSSLint.addRule({ + id: "outline-none", + name: "Disallow outline: none", + desc: "Use of outline: none or outline: 0 should be limited to :focus rules.", + browsers: "All", + tags: ["Accessibility"], + init: function(parser, reporter){ + var rule = this, + lastRule; + + function startRule(event){ + if (event.selectors){ + lastRule = { + line: event.line, + col: event.col, + selectors: event.selectors, + propCount: 0, + outline: false + }; + } else { + lastRule = null; + } + } + + function endRule(event){ + if (lastRule){ + if (lastRule.outline){ + if (lastRule.selectors.toString().toLowerCase().indexOf(":focus") == -1){ + reporter.report("Outlines should only be modified using :focus.", lastRule.line, lastRule.col, rule); + } else if (lastRule.propCount == 1) { + reporter.report("Outlines shouldn't be hidden unless other visual changes are made.", lastRule.line, lastRule.col, rule); + } + } + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + parser.addListener("startpage", startRule); + parser.addListener("startpagemargin", startRule); + parser.addListener("startkeyframerule", startRule); + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(), + value = event.value; + + if (lastRule){ + lastRule.propCount++; + if (name == "outline" && (value == "none" || value == "0")){ + lastRule.outline = true; + } + } + + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + parser.addListener("endpage", endRule); + parser.addListener("endpagemargin", endRule); + parser.addListener("endkeyframerule", endRule); + + } - //rule information +}); +CSSLint.addRule({ id: "overqualified-elements", - name: "Overqualified Elements", + name: "Disallow overqualified elements", desc: "Don't use classes or IDs with elements (a.foo or a#foo).", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this, classes = {}; @@ -9109,11 +7215,11 @@ CSSLint.addRule({ for (j=0; j < selector.parts.length; j++){ part = selector.parts[j]; - if (part instanceof parserlib.css.SelectorPart){ + if (part.type == parser.SELECTOR_PART_TYPE){ for (k=0; k < part.modifiers.length; k++){ modifier = part.modifiers[k]; if (part.elementName && modifier.type == "id"){ - reporter.warn("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule); + reporter.report("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule); } else if (modifier.type == "class"){ if (!classes[modifier]){ @@ -9132,10 +7238,8 @@ CSSLint.addRule({ 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); + reporter.report("Element (" + classes[prop][0].part + ") is overqualified, just use " + classes[prop][0].modifier + " without element name.", classes[prop][0].part.line, classes[prop][0].part.col, rule); } } } @@ -9143,18 +7247,11 @@ CSSLint.addRule({ } }); -/* - * Rule: Headings (h1-h6) should not be qualified (namespaced). - */ CSSLint.addRule({ - - //rule information id: "qualified-headings", - name: "Qualified Headings", + name: "Disallow qualified headings", desc: "Headings should not be qualified (namespaced).", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this; @@ -9169,9 +7266,9 @@ CSSLint.addRule({ for (j=0; j < selector.parts.length; j++){ part = selector.parts[j]; - if (part instanceof parserlib.css.SelectorPart){ + if (part.type == parser.SELECTOR_PART_TYPE){ 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); + reporter.report("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule); } } } @@ -9180,18 +7277,11 @@ CSSLint.addRule({ } }); -/* - * Rule: Selectors that look like regular expressions are slow and should be avoided. - */ CSSLint.addRule({ - - //rule information id: "regex-selectors", - name: "Regex Selectors", + name: "Disallow selectors that look like regexs", desc: "Selectors that look like regular expressions are slow and should be avoided.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this; @@ -9206,12 +7296,12 @@ CSSLint.addRule({ selector = selectors[i]; for (j=0; j < selector.parts.length; j++){ part = selector.parts[j]; - if (part instanceof parserlib.css.SelectorPart){ + if (part.type == parser.SELECTOR_PART_TYPE){ for (k=0; k < part.modifiers.length; k++){ modifier = part.modifiers[k]; if (modifier.type == "attribute"){ if (/([\~\|\^\$\*]=)/.test(modifier)){ - reporter.warn("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule); + reporter.report("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule); } } @@ -9223,23 +7313,14 @@ CSSLint.addRule({ } }); -/* - * 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++; }); @@ -9250,50 +7331,153 @@ CSSLint.addRule({ } }); -/* - * 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({ + id: "shorthand", + name: "Require shorthand properties", + desc: "Use shorthand properties where possible.", + browsers: "All", + init: function(parser, reporter){ + var rule = this, + prop, i, len, + propertiesToCheck = {}, + properties, + mapping = { + "margin": [ + "margin-top", + "margin-bottom", + "margin-left", + "margin-right" + ], + "padding": [ + "padding-top", + "padding-bottom", + "padding-left", + "padding-right" + ] + }; + for (prop in mapping){ + if (mapping.hasOwnProperty(prop)){ + for (i=0, len=mapping[prop].length; i < len; i++){ + propertiesToCheck[mapping[prop][i]] = prop; + } + } + } + + function startRule(event){ + properties = {}; + } + function endRule(event){ + + var prop, i, len, total; + for (prop in mapping){ + if (mapping.hasOwnProperty(prop)){ + total=0; + + for (i=0, len=mapping[prop].length; i < len; i++){ + total += properties[mapping[prop][i]] ? 1 : 0; + } + + if (total == mapping[prop].length){ + reporter.report("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule); + } + } + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + parser.addListener("property", function(event){ + var name = event.property.toString().toLowerCase(), + value = event.value.parts[0].value; + + if (propertiesToCheck[name]){ + properties[name] = 1; + } + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + + } + +}); +CSSLint.addRule({ + id: "star-property-hack", + name: "Disallow properties with a star prefix", + desc: "Checks for the star property hack (targets IE6/7)", + browsers: "All", + init: function(parser, reporter){ + var rule = this; + parser.addListener("property", function(event){ + var property = event.property; - //rule information + if (property.hack == "*") { + reporter.report("Property with star prefix found.", event.property.line, event.property.col, rule); + } + }); + } +}); +CSSLint.addRule({ id: "text-indent", - name: "Text Indent", + name: "Disallow negative text-indent", desc: "Checks for text indent less than -99px", browsers: "All", - - //initialization init: function(parser, reporter){ - var rule = this; - - //check for use of "font-size" + var rule = this, + textIndent, + direction; + + + function startRule(event){ + textIndent = false; + direction = "inherit"; + } + function endRule(event){ + if (textIndent && direction != "ltr"){ + reporter.report("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set direction for that item to ltr.", textIndent.line, textIndent.col, rule); + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); parser.addListener("property", function(event){ - var name = event.property, - value = event.value.parts[0].value; + var name = event.property.toString().toLowerCase(), + value = event.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); + if (name == "text-indent" && value.parts[0].value < -99){ + textIndent = event.property; + } else if (name == "direction" && value == "ltr"){ + direction = "ltr"; } }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + } }); -/* - * Rule: Headings (h1-h6) should be defined only once. - */ CSSLint.addRule({ + id: "underscore-property-hack", + name: "Disallow properties with an underscore prefix", + desc: "Checks for the underscore property hack (targets IE6)", + browsers: "All", + init: function(parser, reporter){ + var rule = this; + parser.addListener("property", function(event){ + var property = event.property; - //rule information + if (property.hack == "_") { + reporter.report("Property with underscore prefix found.", event.property.line, event.property.col, rule); + } + }); + } +}); +CSSLint.addRule({ id: "unique-headings", - name: "Unique Headings", + name: "Headings should only be defined once", desc: "Headings should be defined only once.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this; @@ -9310,35 +7494,56 @@ CSSLint.addRule({ var selectors = event.selectors, selector, part, - i; + pseudo, + i, j; for (i=0; i < selectors.length; i++){ selector = selectors[i]; part = selector.parts[selector.parts.length-1]; - if (part.elementName && /(h[1-6])/.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); + if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())){ + + for (j=0; j < part.modifiers.length; j++){ + if (part.modifiers[j].type == "pseudo"){ + pseudo = true; + break; + } + } + + if (!pseudo){ + headings[RegExp.$1]++; + if (headings[RegExp.$1] > 1) { + reporter.report("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule); + } } } } }); + + parser.addListener("endstylesheet", function(event){ + var prop, + messages = []; + + for (prop in headings){ + if (headings.hasOwnProperty(prop)){ + if (headings[prop] > 1){ + messages.push(headings[prop] + " " + prop + "s"); + } + } + } + + if (messages.length){ + reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule); + } + }); } }); -/* - * Rule: Don't use universal selector because it's slow. - */ CSSLint.addRule({ - - //rule information id: "universal-selector", - name: "Universal Selector", + name: "Disallow universal selector", desc: "The universal selector (*) is known to be slow.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this; @@ -9354,61 +7559,123 @@ CSSLint.addRule({ part = selector.parts[selector.parts.length-1]; if (part.elementName == "*"){ - reporter.warn(rule.desc, part.line, part.col, rule); + reporter.report(rule.desc, part.line, part.col, rule); } } }); } }); -/* - * Rule: When using a vendor-prefixed property, make sure to - * include the standard one. - */ CSSLint.addRule({ + id: "unqualified-attributes", + name: "Disallow unqualified attribute selectors", + desc: "Unqualified attribute selectors are known to be slow.", + browsers: "All", + init: function(parser, reporter){ + var rule = this; + + parser.addListener("startrule", function(event){ + + var selectors = event.selectors, + selector, + part, + modifier, + i, j, k; - //rule information + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + + part = selector.parts[selector.parts.length-1]; + if (part.type == parser.SELECTOR_PART_TYPE){ + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (modifier.type == "attribute" && (!part.elementName || part.elementName == "*")){ + reporter.report(rule.desc, part.line, part.col, rule); + } + } + } + + } + }); + } + +}); +CSSLint.addRule({ id: "vendor-prefix", - name: "Vendor Prefix", + name: "Require standard property with vendor prefix", desc: "When using a vendor-prefixed property, make sure to include the standard one.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this, properties, num, propertiesToCheck = { - "-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", + + "-o-border-radius": "border-radius", + "-o-border-top-left-radius": "border-top-left-radius", + "-o-border-top-right-radius": "border-top-right-radius", + "-o-border-bottom-left-radius": "border-bottom-left-radius", + "-o-border-bottom-right-radius": "border-bottom-right-radius", + + "-moz-border-radius": "border-radius", "-moz-border-radius-topleft": "border-top-left-radius", "-moz-border-radius-topright": "border-top-right-radius", "-moz-border-radius-bottomleft": "border-bottom-left-radius", - "-moz-border-radius-bottomright": "border-bottom-right-radius", + "-moz-border-radius-bottomright": "border-bottom-right-radius", + + "-moz-column-count": "column-count", + "-webkit-column-count": "column-count", + + "-moz-column-gap": "column-gap", + "-webkit-column-gap": "column-gap", + + "-moz-column-rule": "column-rule", + "-webkit-column-rule": "column-rule", + + "-moz-column-rule-style": "column-rule-style", + "-webkit-column-rule-style": "column-rule-style", + + "-moz-column-rule-color": "column-rule-color", + "-webkit-column-rule-color": "column-rule-color", + + "-moz-column-rule-width": "column-rule-width", + "-webkit-column-rule-width": "column-rule-width", + + "-moz-column-width": "column-width", + "-webkit-column-width": "column-width", + + "-webkit-column-span": "column-span", + "-webkit-columns": "columns", + "-moz-box-shadow": "box-shadow", "-webkit-box-shadow": "box-shadow", + "-moz-transform" : "transform", "-webkit-transform" : "transform", "-o-transform" : "transform", "-ms-transform" : "transform", + + "-moz-transform-origin" : "transform-origin", + "-webkit-transform-origin" : "transform-origin", + "-o-transform-origin" : "transform-origin", + "-ms-transform-origin" : "transform-origin", + "-moz-box-sizing" : "box-sizing", "-webkit-box-sizing" : "box-sizing", + "-moz-user-select" : "user-select", "-khtml-user-select" : "user-select", "-webkit-user-select" : "user-select" }; - - //event handler for beginning of rules function startRule(){ properties = {}; num=1; } - - //event handler for end of rules function endRule(event){ var prop, i, len, @@ -9428,11 +7695,10 @@ CSSLint.addRule({ actual = needsStandard[i].actual; if (!properties[needed]){ - reporter.warn("Missing standard property '" + needed + "' to go along with '" + actual + "'.", event.line, event.col, rule); + reporter.report("Missing standard property '" + needed + "' to go along with '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule); } else { - //make sure standard property is last if (properties[needed][0].pos < properties[actual][0].pos){ - reporter.warn("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", event.line, event.col, rule); + reporter.report("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule); } } } @@ -9441,6 +7707,9 @@ CSSLint.addRule({ parser.addListener("startrule", startRule); parser.addListener("startfontface", startRule); + parser.addListener("startpage", startRule); + parser.addListener("startpagemargin", startRule); + parser.addListener("startkeyframerule", startRule); parser.addListener("property", function(event){ var name = event.property.text.toLowerCase(); @@ -9454,76 +7723,27 @@ CSSLint.addRule({ parser.addListener("endrule", endRule); parser.addListener("endfontface", endRule); + parser.addListener("endpage", endRule); + parser.addListener("endpagemargin", endRule); + parser.addListener("endkeyframerule", 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", + name: "Disallow units for 0 values", desc: "You don't need to specify units when a value is 0.", browsers: "All", - - //initialization init: function(parser, reporter){ var rule = this; - - //count how many times "float" is used parser.addListener("property", function(event){ var parts = event.value.parts, i = 0, len = parts.length; while(i < len){ - if ((parts[i].units || parts[i].type == "percentage") && parts[i].value === 0){ - reporter.warn("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule); + if ((parts[i].units || parts[i].type == "percentage") && parts[i].value === 0 && parts[i].type != "time"){ + reporter.report("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule); } i++; } @@ -9534,5 +7754,8 @@ CSSLint.addRule({ }); + exports.CSSLint = CSSLint; + + }); |