gitlabhq/vendor/assets/javascripts/ace-src-noconflict/worker-css.js

10940 lines
370 KiB
JavaScript
Raw Normal View History

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