
The new ACE editor introduces full support for emojis (and, thereby, UTF-16 characters with high- and low-surrogates). Hence, we can remove our custom fix. Further, this update will allow emojis to be used in pair programming sessions.
8762 lines
290 KiB
JavaScript
8762 lines
290 KiB
JavaScript
"no use strict";
|
|
!(function(window) {
|
|
if (typeof window.window != "undefined" && window.document)
|
|
return;
|
|
if (window.require && window.define)
|
|
return;
|
|
|
|
if (!window.console) {
|
|
window.console = function() {
|
|
var msgs = Array.prototype.slice.call(arguments, 0);
|
|
postMessage({type: "log", data: msgs});
|
|
};
|
|
window.console.error =
|
|
window.console.warn =
|
|
window.console.log =
|
|
window.console.trace = window.console;
|
|
}
|
|
window.window = window;
|
|
window.ace = window;
|
|
|
|
window.onerror = function(message, file, line, col, err) {
|
|
postMessage({type: "error", data: {
|
|
message: message,
|
|
data: err.data,
|
|
file: file,
|
|
line: line,
|
|
col: col,
|
|
stack: err.stack
|
|
}});
|
|
};
|
|
|
|
window.normalizeModule = function(parentId, moduleName) {
|
|
// normalize plugin requires
|
|
if (moduleName.indexOf("!") !== -1) {
|
|
var chunks = moduleName.split("!");
|
|
return window.normalizeModule(parentId, chunks[0]) + "!" + window.normalizeModule(parentId, chunks[1]);
|
|
}
|
|
// normalize relative requires
|
|
if (moduleName.charAt(0) == ".") {
|
|
var base = parentId.split("/").slice(0, -1).join("/");
|
|
moduleName = (base ? base + "/" : "") + moduleName;
|
|
|
|
while (moduleName.indexOf(".") !== -1 && previous != moduleName) {
|
|
var previous = moduleName;
|
|
moduleName = moduleName.replace(/^\.\//, "").replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, "");
|
|
}
|
|
}
|
|
|
|
return moduleName;
|
|
};
|
|
|
|
window.require = function require(parentId, id) {
|
|
if (!id) {
|
|
id = parentId;
|
|
parentId = null;
|
|
}
|
|
if (!id.charAt)
|
|
throw new Error("worker.js require() accepts only (parentId, id) as arguments");
|
|
|
|
id = window.normalizeModule(parentId, id);
|
|
|
|
var module = window.require.modules[id];
|
|
if (module) {
|
|
if (!module.initialized) {
|
|
module.initialized = true;
|
|
module.exports = module.factory().exports;
|
|
}
|
|
return module.exports;
|
|
}
|
|
|
|
if (!window.require.tlns)
|
|
return console.log("unable to load " + id);
|
|
|
|
var path = resolveModuleId(id, window.require.tlns);
|
|
if (path.slice(-3) != ".js") path += ".js";
|
|
|
|
window.require.id = id;
|
|
window.require.modules[id] = {}; // prevent infinite loop on broken modules
|
|
importScripts(path);
|
|
return window.require(parentId, id);
|
|
};
|
|
function resolveModuleId(id, paths) {
|
|
var testPath = id, tail = "";
|
|
while (testPath) {
|
|
var alias = paths[testPath];
|
|
if (typeof alias == "string") {
|
|
return alias + tail;
|
|
} else if (alias) {
|
|
return alias.location.replace(/\/*$/, "/") + (tail || alias.main || alias.name);
|
|
} else if (alias === false) {
|
|
return "";
|
|
}
|
|
var i = testPath.lastIndexOf("/");
|
|
if (i === -1) break;
|
|
tail = testPath.substr(i) + tail;
|
|
testPath = testPath.slice(0, i);
|
|
}
|
|
return id;
|
|
}
|
|
window.require.modules = {};
|
|
window.require.tlns = {};
|
|
|
|
window.define = function(id, deps, factory) {
|
|
if (arguments.length == 2) {
|
|
factory = deps;
|
|
if (typeof id != "string") {
|
|
deps = id;
|
|
id = window.require.id;
|
|
}
|
|
} else if (arguments.length == 1) {
|
|
factory = id;
|
|
deps = [];
|
|
id = window.require.id;
|
|
}
|
|
|
|
if (typeof factory != "function") {
|
|
window.require.modules[id] = {
|
|
exports: factory,
|
|
initialized: true
|
|
};
|
|
return;
|
|
}
|
|
|
|
if (!deps.length)
|
|
// If there is no dependencies, we inject "require", "exports" and
|
|
// "module" as dependencies, to provide CommonJS compatibility.
|
|
deps = ["require", "exports", "module"];
|
|
|
|
var req = function(childId) {
|
|
return window.require(id, childId);
|
|
};
|
|
|
|
window.require.modules[id] = {
|
|
exports: {},
|
|
factory: function() {
|
|
var module = this;
|
|
var returnExports = factory.apply(this, deps.map(function(dep) {
|
|
switch (dep) {
|
|
// Because "require", "exports" and "module" aren't actual
|
|
// dependencies, we must handle them seperately.
|
|
case "require": return req;
|
|
case "exports": return module.exports;
|
|
case "module": return module;
|
|
// But for all other dependencies, we can just go ahead and
|
|
// require them.
|
|
default: return req(dep);
|
|
}
|
|
}));
|
|
if (returnExports)
|
|
module.exports = returnExports;
|
|
return module;
|
|
}
|
|
};
|
|
};
|
|
window.define.amd = {};
|
|
require.tlns = {};
|
|
window.initBaseUrls = function initBaseUrls(topLevelNamespaces) {
|
|
for (var i in topLevelNamespaces)
|
|
require.tlns[i] = topLevelNamespaces[i];
|
|
};
|
|
|
|
window.initSender = function initSender() {
|
|
|
|
var EventEmitter = window.require("ace/lib/event_emitter").EventEmitter;
|
|
var oop = window.require("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 = window.main = null;
|
|
var sender = window.sender = null;
|
|
|
|
window.onmessage = function(e) {
|
|
var msg = e.data;
|
|
if (msg.event && sender) {
|
|
sender._signal(msg.event, msg.data);
|
|
}
|
|
else if (msg.command) {
|
|
if (main[msg.command])
|
|
main[msg.command].apply(main, msg.args);
|
|
else if (window[msg.command])
|
|
window[msg.command].apply(window, msg.args);
|
|
else
|
|
throw new Error("Unknown command:" + msg.command);
|
|
}
|
|
else if (msg.init) {
|
|
window.initBaseUrls(msg.tlns);
|
|
require("ace/lib/es5-shim");
|
|
sender = window.sender = window.initSender();
|
|
var clazz = require(msg.module)[msg.classname];
|
|
main = window.main = new clazz(sender);
|
|
}
|
|
};
|
|
})(this);
|
|
|
|
define("ace/lib/oop",["require","exports","module"], function(require, exports, module) {
|
|
"use strict";
|
|
|
|
exports.inherits = function(ctor, superCtor) {
|
|
ctor.super_ = superCtor;
|
|
ctor.prototype = Object.create(superCtor.prototype, {
|
|
constructor: {
|
|
value: ctor,
|
|
enumerable: false,
|
|
writable: true,
|
|
configurable: true
|
|
}
|
|
});
|
|
};
|
|
|
|
exports.mixin = function(obj, mixin) {
|
|
for (var key in mixin) {
|
|
obj[key] = mixin[key];
|
|
}
|
|
return obj;
|
|
};
|
|
|
|
exports.implement = function(proto, mixin) {
|
|
exports.mixin(proto, mixin);
|
|
};
|
|
|
|
});
|
|
|
|
define("ace/lib/lang",["require","exports","module"], function(require, exports, module) {
|
|
"use strict";
|
|
|
|
exports.last = function(a) {
|
|
return a[a.length - 1];
|
|
};
|
|
|
|
exports.stringReverse = function(string) {
|
|
return string.split("").reverse().join("");
|
|
};
|
|
|
|
exports.stringRepeat = function (string, count) {
|
|
var result = '';
|
|
while (count > 0) {
|
|
if (count & 1)
|
|
result += string;
|
|
|
|
if (count >>= 1)
|
|
string += string;
|
|
}
|
|
return result;
|
|
};
|
|
|
|
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 deepCopy(obj) {
|
|
if (typeof obj !== "object" || !obj)
|
|
return obj;
|
|
var copy;
|
|
if (Array.isArray(obj)) {
|
|
copy = [];
|
|
for (var key = 0; key < obj.length; key++) {
|
|
copy[key] = deepCopy(obj[key]);
|
|
}
|
|
return copy;
|
|
}
|
|
if (Object.prototype.toString.call(obj) !== "[object Object]")
|
|
return obj;
|
|
|
|
copy = {};
|
|
for (var key in obj)
|
|
copy[key] = deepCopy(obj[key]);
|
|
return copy;
|
|
};
|
|
|
|
exports.arrayToMap = function(arr) {
|
|
var map = {};
|
|
for (var i=0; i<arr.length; i++) {
|
|
map[arr[i]] = 1;
|
|
}
|
|
return map;
|
|
|
|
};
|
|
|
|
exports.createMap = function(props) {
|
|
var map = Object.create(null);
|
|
for (var i in props) {
|
|
map[i] = props[i];
|
|
}
|
|
return map;
|
|
};
|
|
exports.arrayRemove = function(array, value) {
|
|
for (var i = 0; i <= array.length; i++) {
|
|
if (value === array[i]) {
|
|
array.splice(i, 1);
|
|
}
|
|
}
|
|
};
|
|
|
|
exports.escapeRegExp = function(str) {
|
|
return str.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
|
|
};
|
|
|
|
exports.escapeHTML = function(str) {
|
|
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<");
|
|
};
|
|
|
|
exports.getMatchOffsets = function(string, regExp) {
|
|
var matches = [];
|
|
|
|
string.replace(regExp, function(str) {
|
|
matches.push({
|
|
offset: arguments[arguments.length-2],
|
|
length: str.length
|
|
});
|
|
});
|
|
|
|
return matches;
|
|
};
|
|
exports.deferredCall = function(fcn) {
|
|
var timer = null;
|
|
var callback = function() {
|
|
timer = null;
|
|
fcn();
|
|
};
|
|
|
|
var deferred = function(timeout) {
|
|
deferred.cancel();
|
|
timer = setTimeout(callback, timeout || 0);
|
|
return deferred;
|
|
};
|
|
|
|
deferred.schedule = deferred;
|
|
|
|
deferred.call = function() {
|
|
this.cancel();
|
|
fcn();
|
|
return deferred;
|
|
};
|
|
|
|
deferred.cancel = function() {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
return deferred;
|
|
};
|
|
|
|
deferred.isPending = function() {
|
|
return timer;
|
|
};
|
|
|
|
return deferred;
|
|
};
|
|
|
|
|
|
exports.delayedCall = function(fcn, defaultTimeout) {
|
|
var timer = null;
|
|
var callback = function() {
|
|
timer = null;
|
|
fcn();
|
|
};
|
|
|
|
var _self = function(timeout) {
|
|
if (timer == null)
|
|
timer = setTimeout(callback, timeout || defaultTimeout);
|
|
};
|
|
|
|
_self.delay = function(timeout) {
|
|
timer && clearTimeout(timer);
|
|
timer = setTimeout(callback, timeout || defaultTimeout);
|
|
};
|
|
_self.schedule = _self;
|
|
|
|
_self.call = function() {
|
|
this.cancel();
|
|
fcn();
|
|
};
|
|
|
|
_self.cancel = function() {
|
|
timer && clearTimeout(timer);
|
|
timer = null;
|
|
};
|
|
|
|
_self.isPending = function() {
|
|
return timer;
|
|
};
|
|
|
|
return _self;
|
|
};
|
|
});
|
|
|
|
define("ace/range",["require","exports","module"], function(require, exports, module) {
|
|
"use strict";
|
|
var comparePoints = function(p1, p2) {
|
|
return p1.row - p2.row || p1.column - p2.column;
|
|
};
|
|
var Range = function(startRow, startColumn, endRow, endColumn) {
|
|
this.start = {
|
|
row: startRow,
|
|
column: startColumn
|
|
};
|
|
|
|
this.end = {
|
|
row: endRow,
|
|
column: endColumn
|
|
};
|
|
};
|
|
|
|
(function() {
|
|
this.isEqual = function(range) {
|
|
return this.start.row === range.start.row &&
|
|
this.end.row === range.end.row &&
|
|
this.start.column === range.start.column &&
|
|
this.end.column === range.end.column;
|
|
};
|
|
this.toString = function() {
|
|
return ("Range: [" + this.start.row + "/" + this.start.column +
|
|
"] -> [" + this.end.row + "/" + this.end.column + "]");
|
|
};
|
|
|
|
this.contains = function(row, column) {
|
|
return this.compare(row, column) == 0;
|
|
};
|
|
this.compareRange = function(range) {
|
|
var cmp,
|
|
end = range.end,
|
|
start = range.start;
|
|
|
|
cmp = this.compare(end.row, end.column);
|
|
if (cmp == 1) {
|
|
cmp = this.compare(start.row, start.column);
|
|
if (cmp == 1) {
|
|
return 2;
|
|
} else if (cmp == 0) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else if (cmp == -1) {
|
|
return -2;
|
|
} else {
|
|
cmp = this.compare(start.row, start.column);
|
|
if (cmp == -1) {
|
|
return -1;
|
|
} else if (cmp == 1) {
|
|
return 42;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
};
|
|
this.comparePoint = function(p) {
|
|
return this.compare(p.row, p.column);
|
|
};
|
|
this.containsRange = function(range) {
|
|
return this.comparePoint(range.start) == 0 && this.comparePoint(range.end) == 0;
|
|
};
|
|
this.intersects = function(range) {
|
|
var cmp = this.compareRange(range);
|
|
return (cmp == -1 || cmp == 0 || cmp == 1);
|
|
};
|
|
this.isEnd = function(row, column) {
|
|
return this.end.row == row && this.end.column == column;
|
|
};
|
|
this.isStart = function(row, column) {
|
|
return this.start.row == row && this.start.column == column;
|
|
};
|
|
this.setStart = function(row, column) {
|
|
if (typeof row == "object") {
|
|
this.start.column = row.column;
|
|
this.start.row = row.row;
|
|
} else {
|
|
this.start.row = row;
|
|
this.start.column = column;
|
|
}
|
|
};
|
|
this.setEnd = function(row, column) {
|
|
if (typeof row == "object") {
|
|
this.end.column = row.column;
|
|
this.end.row = row.row;
|
|
} else {
|
|
this.end.row = row;
|
|
this.end.column = column;
|
|
}
|
|
};
|
|
this.inside = function(row, column) {
|
|
if (this.compare(row, column) == 0) {
|
|
if (this.isEnd(row, column) || this.isStart(row, column)) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
this.insideStart = function(row, column) {
|
|
if (this.compare(row, column) == 0) {
|
|
if (this.isEnd(row, column)) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
this.insideEnd = function(row, column) {
|
|
if (this.compare(row, column) == 0) {
|
|
if (this.isStart(row, column)) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
this.compare = function(row, column) {
|
|
if (!this.isMultiLine()) {
|
|
if (row === this.start.row) {
|
|
return column < this.start.column ? -1 : (column > this.end.column ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
if (row < this.start.row)
|
|
return -1;
|
|
|
|
if (row > this.end.row)
|
|
return 1;
|
|
|
|
if (this.start.row === row)
|
|
return column >= this.start.column ? 0 : -1;
|
|
|
|
if (this.end.row === row)
|
|
return column <= this.end.column ? 0 : 1;
|
|
|
|
return 0;
|
|
};
|
|
this.compareStart = function(row, column) {
|
|
if (this.start.row == row && this.start.column == column) {
|
|
return -1;
|
|
} else {
|
|
return this.compare(row, column);
|
|
}
|
|
};
|
|
this.compareEnd = function(row, column) {
|
|
if (this.end.row == row && this.end.column == column) {
|
|
return 1;
|
|
} else {
|
|
return this.compare(row, column);
|
|
}
|
|
};
|
|
this.compareInside = function(row, column) {
|
|
if (this.end.row == row && this.end.column == column) {
|
|
return 1;
|
|
} else if (this.start.row == row && this.start.column == column) {
|
|
return -1;
|
|
} else {
|
|
return this.compare(row, column);
|
|
}
|
|
};
|
|
this.clipRows = function(firstRow, lastRow) {
|
|
if (this.end.row > lastRow)
|
|
var end = {row: lastRow + 1, column: 0};
|
|
else if (this.end.row < firstRow)
|
|
var end = {row: firstRow, column: 0};
|
|
|
|
if (this.start.row > lastRow)
|
|
var start = {row: lastRow + 1, column: 0};
|
|
else if (this.start.row < firstRow)
|
|
var start = {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
|
|
);
|
|
};
|
|
this.moveBy = function(row, column) {
|
|
this.start.row += row;
|
|
this.start.column += column;
|
|
this.end.row += row;
|
|
this.end.column += column;
|
|
};
|
|
|
|
}).call(Range.prototype);
|
|
Range.fromPoints = function(start, end) {
|
|
return new Range(start.row, start.column, end.row, end.column);
|
|
};
|
|
Range.comparePoints = comparePoints;
|
|
|
|
Range.comparePoints = function(p1, p2) {
|
|
return p1.row - p2.row || p1.column - p2.column;
|
|
};
|
|
|
|
|
|
exports.Range = Range;
|
|
});
|
|
|
|
define("ace/apply_delta",["require","exports","module"], function(require, exports, module) {
|
|
"use strict";
|
|
|
|
function throwDeltaError(delta, errorText){
|
|
console.log("Invalid Delta:", delta);
|
|
throw "Invalid Delta: " + errorText;
|
|
}
|
|
|
|
function positionInDocument(docLines, position) {
|
|
return position.row >= 0 && position.row < docLines.length &&
|
|
position.column >= 0 && position.column <= docLines[position.row].length;
|
|
}
|
|
|
|
function validateDelta(docLines, delta) {
|
|
if (delta.action != "insert" && delta.action != "remove")
|
|
throwDeltaError(delta, "delta.action must be 'insert' or 'remove'");
|
|
if (!(delta.lines instanceof Array))
|
|
throwDeltaError(delta, "delta.lines must be an Array");
|
|
if (!delta.start || !delta.end)
|
|
throwDeltaError(delta, "delta.start/end must be an present");
|
|
var start = delta.start;
|
|
if (!positionInDocument(docLines, delta.start))
|
|
throwDeltaError(delta, "delta.start must be contained in document");
|
|
var end = delta.end;
|
|
if (delta.action == "remove" && !positionInDocument(docLines, end))
|
|
throwDeltaError(delta, "delta.end must contained in document for 'remove' actions");
|
|
var numRangeRows = end.row - start.row;
|
|
var numRangeLastLineChars = (end.column - (numRangeRows == 0 ? start.column : 0));
|
|
if (numRangeRows != delta.lines.length - 1 || delta.lines[numRangeRows].length != numRangeLastLineChars)
|
|
throwDeltaError(delta, "delta.range must match delta lines");
|
|
}
|
|
|
|
exports.applyDelta = function(docLines, delta, doNotValidate) {
|
|
|
|
var row = delta.start.row;
|
|
var startColumn = delta.start.column;
|
|
var line = docLines[row] || "";
|
|
switch (delta.action) {
|
|
case "insert":
|
|
var lines = delta.lines;
|
|
if (lines.length === 1) {
|
|
docLines[row] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn);
|
|
} else {
|
|
var args = [row, 1].concat(delta.lines);
|
|
docLines.splice.apply(docLines, args);
|
|
docLines[row] = line.substring(0, startColumn) + docLines[row];
|
|
docLines[row + delta.lines.length - 1] += line.substring(startColumn);
|
|
}
|
|
break;
|
|
case "remove":
|
|
var endColumn = delta.end.column;
|
|
var endRow = delta.end.row;
|
|
if (row === endRow) {
|
|
docLines[row] = line.substring(0, startColumn) + line.substring(endColumn);
|
|
} else {
|
|
docLines.splice(
|
|
row, endRow - row + 1,
|
|
line.substring(0, startColumn) + docLines[endRow].substring(endColumn)
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
});
|
|
|
|
define("ace/lib/event_emitter",["require","exports","module"], function(require, exports, module) {
|
|
"use strict";
|
|
|
|
var EventEmitter = {};
|
|
var stopPropagation = function() { this.propagationStopped = true; };
|
|
var preventDefault = function() { this.defaultPrevented = true; };
|
|
|
|
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 = stopPropagation;
|
|
if (!e.preventDefault)
|
|
e.preventDefault = preventDefault;
|
|
|
|
listeners = listeners.slice();
|
|
for (var i=0; i<listeners.length; i++) {
|
|
listeners[i](e, this);
|
|
if (e.propagationStopped)
|
|
break;
|
|
}
|
|
|
|
if (defaultHandler && !e.defaultPrevented)
|
|
return defaultHandler(e, this);
|
|
};
|
|
|
|
|
|
EventEmitter._signal = function(eventName, e) {
|
|
var listeners = (this._eventRegistry || {})[eventName];
|
|
if (!listeners)
|
|
return;
|
|
listeners = listeners.slice();
|
|
for (var i=0; i<listeners.length; i++)
|
|
listeners[i](e, this);
|
|
};
|
|
|
|
EventEmitter.once = function(eventName, callback) {
|
|
var _self = this;
|
|
callback && this.addEventListener(eventName, function newCallback() {
|
|
_self.removeEventListener(eventName, newCallback);
|
|
callback.apply(null, arguments);
|
|
});
|
|
};
|
|
|
|
|
|
EventEmitter.setDefaultHandler = function(eventName, callback) {
|
|
var handlers = this._defaultHandlers;
|
|
if (!handlers)
|
|
handlers = this._defaultHandlers = {_disabled_: {}};
|
|
|
|
if (handlers[eventName]) {
|
|
var old = handlers[eventName];
|
|
var disabled = handlers._disabled_[eventName];
|
|
if (!disabled)
|
|
handlers._disabled_[eventName] = disabled = [];
|
|
disabled.push(old);
|
|
var i = disabled.indexOf(callback);
|
|
if (i != -1)
|
|
disabled.splice(i, 1);
|
|
}
|
|
handlers[eventName] = callback;
|
|
};
|
|
EventEmitter.removeDefaultHandler = function(eventName, callback) {
|
|
var handlers = this._defaultHandlers;
|
|
if (!handlers)
|
|
return;
|
|
var disabled = handlers._disabled_[eventName];
|
|
|
|
if (handlers[eventName] == callback) {
|
|
var old = handlers[eventName];
|
|
if (disabled)
|
|
this.setDefaultHandler(eventName, disabled.pop());
|
|
} else if (disabled) {
|
|
var i = disabled.indexOf(callback);
|
|
if (i != -1)
|
|
disabled.splice(i, 1);
|
|
}
|
|
};
|
|
|
|
EventEmitter.on =
|
|
EventEmitter.addEventListener = function(eventName, callback, capturing) {
|
|
this._eventRegistry = this._eventRegistry || {};
|
|
|
|
var listeners = this._eventRegistry[eventName];
|
|
if (!listeners)
|
|
listeners = this._eventRegistry[eventName] = [];
|
|
|
|
if (listeners.indexOf(callback) == -1)
|
|
listeners[capturing ? "unshift" : "push"](callback);
|
|
return callback;
|
|
};
|
|
|
|
EventEmitter.off =
|
|
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/anchor",["require","exports","module","ace/lib/oop","ace/lib/event_emitter"], function(require, exports, module) {
|
|
"use strict";
|
|
|
|
var oop = require("./lib/oop");
|
|
var EventEmitter = require("./lib/event_emitter").EventEmitter;
|
|
|
|
var Anchor = exports.Anchor = function(doc, row, column) {
|
|
this.$onChange = this.onChange.bind(this);
|
|
this.attach(doc);
|
|
|
|
if (typeof column == "undefined")
|
|
this.setPosition(row.row, row.column);
|
|
else
|
|
this.setPosition(row, column);
|
|
};
|
|
|
|
(function() {
|
|
|
|
oop.implement(this, EventEmitter);
|
|
this.getPosition = function() {
|
|
return this.$clipPositionToDocument(this.row, this.column);
|
|
};
|
|
this.getDocument = function() {
|
|
return this.document;
|
|
};
|
|
this.$insertRight = false;
|
|
this.onChange = function(delta) {
|
|
if (delta.start.row == delta.end.row && delta.start.row != this.row)
|
|
return;
|
|
|
|
if (delta.start.row > this.row)
|
|
return;
|
|
|
|
var point = $getTransformedPoint(delta, {row: this.row, column: this.column}, this.$insertRight);
|
|
this.setPosition(point.row, point.column, true);
|
|
};
|
|
|
|
function $pointsInOrder(point1, point2, equalPointsInOrder) {
|
|
var bColIsAfter = equalPointsInOrder ? point1.column <= point2.column : point1.column < point2.column;
|
|
return (point1.row < point2.row) || (point1.row == point2.row && bColIsAfter);
|
|
}
|
|
|
|
function $getTransformedPoint(delta, point, moveIfEqual) {
|
|
var deltaIsInsert = delta.action == "insert";
|
|
var deltaRowShift = (deltaIsInsert ? 1 : -1) * (delta.end.row - delta.start.row);
|
|
var deltaColShift = (deltaIsInsert ? 1 : -1) * (delta.end.column - delta.start.column);
|
|
var deltaStart = delta.start;
|
|
var deltaEnd = deltaIsInsert ? deltaStart : delta.end; // Collapse insert range.
|
|
if ($pointsInOrder(point, deltaStart, moveIfEqual)) {
|
|
return {
|
|
row: point.row,
|
|
column: point.column
|
|
};
|
|
}
|
|
if ($pointsInOrder(deltaEnd, point, !moveIfEqual)) {
|
|
return {
|
|
row: point.row + deltaRowShift,
|
|
column: point.column + (point.row == deltaEnd.row ? deltaColShift : 0)
|
|
};
|
|
}
|
|
|
|
return {
|
|
row: deltaStart.row,
|
|
column: deltaStart.column
|
|
};
|
|
}
|
|
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._signal("change", {
|
|
old: old,
|
|
value: pos
|
|
});
|
|
};
|
|
this.detach = function() {
|
|
this.document.removeEventListener("change", this.$onChange);
|
|
};
|
|
this.attach = function(doc) {
|
|
this.document = doc || this.document;
|
|
this.document.on("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/document",["require","exports","module","ace/lib/oop","ace/apply_delta","ace/lib/event_emitter","ace/range","ace/anchor"], function(require, exports, module) {
|
|
"use strict";
|
|
|
|
var oop = require("./lib/oop");
|
|
var applyDelta = require("./apply_delta").applyDelta;
|
|
var EventEmitter = require("./lib/event_emitter").EventEmitter;
|
|
var Range = require("./range").Range;
|
|
var Anchor = require("./anchor").Anchor;
|
|
|
|
var Document = function(textOrLines) {
|
|
this.$lines = [""];
|
|
if (textOrLines.length === 0) {
|
|
this.$lines = [""];
|
|
} else if (Array.isArray(textOrLines)) {
|
|
this.insertMergedLines({row: 0, column: 0}, textOrLines);
|
|
} else {
|
|
this.insert({row: 0, column:0}, textOrLines);
|
|
}
|
|
};
|
|
|
|
(function() {
|
|
|
|
oop.implement(this, EventEmitter);
|
|
this.setValue = function(text) {
|
|
var len = this.getLength() - 1;
|
|
this.remove(new Range(0, 0, len, this.getLine(len).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);
|
|
};
|
|
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);
|
|
this.$autoNewLine = match ? match[1] : "\n";
|
|
this._signal("changeNewLineMode");
|
|
};
|
|
this.getNewLineCharacter = function() {
|
|
switch (this.$newLineMode) {
|
|
case "windows":
|
|
return "\r\n";
|
|
case "unix":
|
|
return "\n";
|
|
default:
|
|
return this.$autoNewLine || "\n";
|
|
}
|
|
};
|
|
|
|
this.$autoNewLine = "";
|
|
this.$newLineMode = "auto";
|
|
this.setNewLineMode = function(newLineMode) {
|
|
if (this.$newLineMode === newLineMode)
|
|
return;
|
|
|
|
this.$newLineMode = newLineMode;
|
|
this._signal("changeNewLineMode");
|
|
};
|
|
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) {
|
|
return this.getLinesForRange(range).join(this.getNewLineCharacter());
|
|
};
|
|
this.getLinesForRange = function(range) {
|
|
var lines;
|
|
if (range.start.row === range.end.row) {
|
|
lines = [this.getLine(range.start.row).substring(range.start.column, range.end.column)];
|
|
} else {
|
|
lines = this.getLines(range.start.row, range.end.row);
|
|
lines[0] = (lines[0] || "").substring(range.start.column);
|
|
var l = lines.length - 1;
|
|
if (range.end.row - range.start.row == l)
|
|
lines[l] = lines[l].substring(0, range.end.column);
|
|
}
|
|
return lines;
|
|
};
|
|
this.insertLines = function(row, lines) {
|
|
console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead.");
|
|
return this.insertFullLines(row, lines);
|
|
};
|
|
this.removeLines = function(firstRow, lastRow) {
|
|
console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead.");
|
|
return this.removeFullLines(firstRow, lastRow);
|
|
};
|
|
this.insertNewLine = function(position) {
|
|
console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, ['', '']) instead.");
|
|
return this.insertMergedLines(position, ["", ""]);
|
|
};
|
|
this.insert = function(position, text) {
|
|
if (this.getLength() <= 1)
|
|
this.$detectNewLine(text);
|
|
|
|
return this.insertMergedLines(position, this.$split(text));
|
|
};
|
|
this.insertInLine = function(position, text) {
|
|
var start = this.clippedPos(position.row, position.column);
|
|
var end = this.pos(position.row, position.column + text.length);
|
|
|
|
this.applyDelta({
|
|
start: start,
|
|
end: end,
|
|
action: "insert",
|
|
lines: [text]
|
|
}, true);
|
|
|
|
return this.clonePos(end);
|
|
};
|
|
|
|
this.clippedPos = function(row, column) {
|
|
var length = this.getLength();
|
|
if (row === undefined) {
|
|
row = length;
|
|
} else if (row < 0) {
|
|
row = 0;
|
|
} else if (row >= length) {
|
|
row = length - 1;
|
|
column = undefined;
|
|
}
|
|
var line = this.getLine(row);
|
|
if (column == undefined)
|
|
column = line.length;
|
|
column = Math.min(Math.max(column, 0), line.length);
|
|
return {row: row, column: column};
|
|
};
|
|
|
|
this.clonePos = function(pos) {
|
|
return {row: pos.row, column: pos.column};
|
|
};
|
|
|
|
this.pos = function(row, column) {
|
|
return {row: row, column: column};
|
|
};
|
|
|
|
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;
|
|
} else {
|
|
position.row = Math.max(0, position.row);
|
|
position.column = Math.min(Math.max(position.column, 0), this.getLine(position.row).length);
|
|
}
|
|
return position;
|
|
};
|
|
this.insertFullLines = function(row, lines) {
|
|
row = Math.min(Math.max(row, 0), this.getLength());
|
|
var column = 0;
|
|
if (row < this.getLength()) {
|
|
lines = lines.concat([""]);
|
|
column = 0;
|
|
} else {
|
|
lines = [""].concat(lines);
|
|
row--;
|
|
column = this.$lines[row].length;
|
|
}
|
|
this.insertMergedLines({row: row, column: column}, lines);
|
|
};
|
|
this.insertMergedLines = function(position, lines) {
|
|
var start = this.clippedPos(position.row, position.column);
|
|
var end = {
|
|
row: start.row + lines.length - 1,
|
|
column: (lines.length == 1 ? start.column : 0) + lines[lines.length - 1].length
|
|
};
|
|
|
|
this.applyDelta({
|
|
start: start,
|
|
end: end,
|
|
action: "insert",
|
|
lines: lines
|
|
});
|
|
|
|
return this.clonePos(end);
|
|
};
|
|
this.remove = function(range) {
|
|
var start = this.clippedPos(range.start.row, range.start.column);
|
|
var end = this.clippedPos(range.end.row, range.end.column);
|
|
this.applyDelta({
|
|
start: start,
|
|
end: end,
|
|
action: "remove",
|
|
lines: this.getLinesForRange({start: start, end: end})
|
|
});
|
|
return this.clonePos(start);
|
|
};
|
|
this.removeInLine = function(row, startColumn, endColumn) {
|
|
var start = this.clippedPos(row, startColumn);
|
|
var end = this.clippedPos(row, endColumn);
|
|
|
|
this.applyDelta({
|
|
start: start,
|
|
end: end,
|
|
action: "remove",
|
|
lines: this.getLinesForRange({start: start, end: end})
|
|
}, true);
|
|
|
|
return this.clonePos(start);
|
|
};
|
|
this.removeFullLines = function(firstRow, lastRow) {
|
|
firstRow = Math.min(Math.max(0, firstRow), this.getLength() - 1);
|
|
lastRow = Math.min(Math.max(0, lastRow ), this.getLength() - 1);
|
|
var deleteFirstNewLine = lastRow == this.getLength() - 1 && firstRow > 0;
|
|
var deleteLastNewLine = lastRow < this.getLength() - 1;
|
|
var startRow = ( deleteFirstNewLine ? firstRow - 1 : firstRow );
|
|
var startCol = ( deleteFirstNewLine ? this.getLine(startRow).length : 0 );
|
|
var endRow = ( deleteLastNewLine ? lastRow + 1 : lastRow );
|
|
var endCol = ( deleteLastNewLine ? 0 : this.getLine(endRow).length );
|
|
var range = new Range(startRow, startCol, endRow, endCol);
|
|
var deletedLines = this.$lines.slice(firstRow, lastRow + 1);
|
|
|
|
this.applyDelta({
|
|
start: range.start,
|
|
end: range.end,
|
|
action: "remove",
|
|
lines: this.getLinesForRange(range)
|
|
});
|
|
return deletedLines;
|
|
};
|
|
this.removeNewLine = function(row) {
|
|
if (row < this.getLength() - 1 && row >= 0) {
|
|
this.applyDelta({
|
|
start: this.pos(row, this.getLine(row).length),
|
|
end: this.pos(row + 1, 0),
|
|
action: "remove",
|
|
lines: ["", ""]
|
|
});
|
|
}
|
|
};
|
|
this.replace = function(range, text) {
|
|
if (!(range instanceof Range))
|
|
range = Range.fromPoints(range.start, range.end);
|
|
if (text.length === 0 && range.isEmpty())
|
|
return range.start;
|
|
if (text == this.getTextRange(range))
|
|
return range.end;
|
|
|
|
this.remove(range);
|
|
var end;
|
|
if (text) {
|
|
end = this.insert(range.start, text);
|
|
}
|
|
else {
|
|
end = range.start;
|
|
}
|
|
|
|
return end;
|
|
};
|
|
this.applyDeltas = function(deltas) {
|
|
for (var i=0; i<deltas.length; i++) {
|
|
this.applyDelta(deltas[i]);
|
|
}
|
|
};
|
|
this.revertDeltas = function(deltas) {
|
|
for (var i=deltas.length-1; i>=0; i--) {
|
|
this.revertDelta(deltas[i]);
|
|
}
|
|
};
|
|
this.applyDelta = function(delta, doNotValidate) {
|
|
var isInsert = delta.action == "insert";
|
|
if (isInsert ? delta.lines.length <= 1 && !delta.lines[0]
|
|
: !Range.comparePoints(delta.start, delta.end)) {
|
|
return;
|
|
}
|
|
|
|
if (isInsert && delta.lines.length > 20000)
|
|
this.$splitAndapplyLargeDelta(delta, 20000);
|
|
applyDelta(this.$lines, delta, doNotValidate);
|
|
this._signal("change", delta);
|
|
};
|
|
|
|
this.$splitAndapplyLargeDelta = function(delta, MAX) {
|
|
var lines = delta.lines;
|
|
var l = lines.length;
|
|
var row = delta.start.row;
|
|
var column = delta.start.column;
|
|
var from = 0, to = 0;
|
|
do {
|
|
from = to;
|
|
to += MAX - 1;
|
|
var chunk = lines.slice(from, to);
|
|
if (to > l) {
|
|
delta.lines = chunk;
|
|
delta.start.row = row + from;
|
|
delta.start.column = column;
|
|
break;
|
|
}
|
|
chunk.push("");
|
|
this.applyDelta({
|
|
start: this.pos(row + from, column),
|
|
end: this.pos(row + to, column = 0),
|
|
action: delta.action,
|
|
lines: chunk
|
|
}, true);
|
|
} while(true);
|
|
};
|
|
this.revertDelta = function(delta) {
|
|
this.applyDelta({
|
|
start: this.clonePos(delta.start),
|
|
end: this.clonePos(delta.end),
|
|
action: (delta.action == "insert" ? "remove" : "insert"),
|
|
lines: delta.lines.slice()
|
|
});
|
|
};
|
|
this.indexToPosition = function(index, startRow) {
|
|
var lines = this.$lines || this.getAllLines();
|
|
var newlineLength = this.getNewLineCharacter().length;
|
|
for (var i = startRow || 0, l = lines.length; i < l; i++) {
|
|
index -= lines[i].length + newlineLength;
|
|
if (index < 0)
|
|
return {row: i, column: index + lines[i].length + newlineLength};
|
|
}
|
|
return {row: l-1, column: lines[l-1].length};
|
|
};
|
|
this.positionToIndex = function(pos, startRow) {
|
|
var lines = this.$lines || this.getAllLines();
|
|
var newlineLength = this.getNewLineCharacter().length;
|
|
var index = 0;
|
|
var row = Math.min(pos.row, lines.length);
|
|
for (var i = startRow || 0; i < row; ++i)
|
|
index += lines[i].length + newlineLength;
|
|
|
|
return index + pos.column;
|
|
};
|
|
|
|
}).call(Document.prototype);
|
|
|
|
exports.Document = Document;
|
|
});
|
|
|
|
define("ace/worker/mirror",["require","exports","module","ace/range","ace/document","ace/lib/lang"], function(require, exports, module) {
|
|
"use strict";
|
|
|
|
var Range = require("../range").Range;
|
|
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.delayedCall(this.onUpdate.bind(this));
|
|
|
|
var _self = this;
|
|
sender.on("change", function(e) {
|
|
var data = e.data;
|
|
if (data[0].start) {
|
|
doc.applyDeltas(data);
|
|
} else {
|
|
for (var i = 0; i < data.length; i += 2) {
|
|
if (Array.isArray(data[i+1])) {
|
|
var d = {action: "insert", start: data[i], lines: data[i+1]};
|
|
} else {
|
|
var d = {action: "remove", start: data[i], end: data[i+1]};
|
|
}
|
|
doc.applyDelta(d, true);
|
|
}
|
|
}
|
|
if (_self.$timeout)
|
|
return deferredUpdate.schedule(_self.$timeout);
|
|
_self.onUpdate();
|
|
});
|
|
};
|
|
|
|
(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() {
|
|
};
|
|
|
|
this.isPending = function() {
|
|
return this.deferredUpdate.isPending();
|
|
};
|
|
|
|
}).call(Mirror.prototype);
|
|
|
|
});
|
|
|
|
define("ace/mode/css/csslint",["require","exports","module"], function(require, exports, module) {
|
|
var parserlib = {};
|
|
(function(){
|
|
function EventTarget(){
|
|
this._listeners = {};
|
|
}
|
|
|
|
EventTarget.prototype = {
|
|
constructor: EventTarget,
|
|
addListener: function(type, listener){
|
|
if (!this._listeners[type]){
|
|
this._listeners[type] = [];
|
|
}
|
|
|
|
this._listeners[type].push(listener);
|
|
},
|
|
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]){
|
|
var listeners = this._listeners[event.type].concat();
|
|
for (var i=0, len=listeners.length; i < len; i++){
|
|
listeners[i].call(this, event);
|
|
}
|
|
}
|
|
},
|
|
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){
|
|
this._input = text.replace(/\n\r?/g, "\n");
|
|
this._line = 1;
|
|
this._col = 1;
|
|
this._cursor = 0;
|
|
}
|
|
|
|
StringReader.prototype = {
|
|
constructor: StringReader,
|
|
getCol: function(){
|
|
return this._col;
|
|
},
|
|
getLine: function(){
|
|
return this._line ;
|
|
},
|
|
eof: function(){
|
|
return (this._cursor == this._input.length);
|
|
},
|
|
peek: function(count){
|
|
var c = null;
|
|
count = (typeof count == "undefined" ? 1 : count);
|
|
if (this._cursor < this._input.length){
|
|
c = this._input.charAt(this._cursor + count - 1);
|
|
}
|
|
|
|
return c;
|
|
},
|
|
read: function(){
|
|
var c = null;
|
|
if (this._cursor < this._input.length){
|
|
if (this._input.charAt(this._cursor) == "\n"){
|
|
this._line++;
|
|
this._col=1;
|
|
} else {
|
|
this._col++;
|
|
}
|
|
c = this._input.charAt(this._cursor++);
|
|
}
|
|
|
|
return c;
|
|
},
|
|
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;
|
|
}
|
|
},
|
|
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;
|
|
|
|
},
|
|
readWhile: function(filter){
|
|
|
|
var buffer = "",
|
|
c = this.read();
|
|
|
|
while(c !== null && filter(c)){
|
|
buffer += c;
|
|
c = this.read();
|
|
}
|
|
|
|
return buffer;
|
|
|
|
},
|
|
readMatch: function(matcher){
|
|
|
|
var source = this._input.substring(this._cursor),
|
|
value = null;
|
|
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;
|
|
},
|
|
readCount: function(count){
|
|
var buffer = "";
|
|
|
|
while(count--){
|
|
buffer += this.read();
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
};
|
|
function SyntaxError(message, line, col){
|
|
this.col = col;
|
|
this.line = line;
|
|
this.message = message;
|
|
|
|
}
|
|
SyntaxError.prototype = new Error();
|
|
function SyntaxUnit(text, line, col, type){
|
|
this.col = col;
|
|
this.line = line;
|
|
this.text = text;
|
|
this.type = type;
|
|
}
|
|
SyntaxUnit.fromToken = function(token){
|
|
return new SyntaxUnit(token.value, token.startLine, token.startCol);
|
|
};
|
|
|
|
SyntaxUnit.prototype = {
|
|
constructor: SyntaxUnit,
|
|
valueOf: function(){
|
|
return this.text;
|
|
},
|
|
toString: function(){
|
|
return this.text;
|
|
}
|
|
|
|
};
|
|
function TokenStreamBase(input, tokenData){
|
|
this._reader = input ? new StringReader(input.toString()) : null;
|
|
this._token = null;
|
|
this._tokenData = tokenData;
|
|
this._lt = [];
|
|
this._ltIndex = 0;
|
|
|
|
this._ltIndexCache = [];
|
|
}
|
|
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 = {
|
|
constructor: TokenStreamBase,
|
|
match: function(tokenTypes, channel){
|
|
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;
|
|
}
|
|
}
|
|
this.unget();
|
|
return false;
|
|
},
|
|
mustMatch: function(tokenTypes, channel){
|
|
|
|
var token;
|
|
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);
|
|
}
|
|
},
|
|
advance: function(tokenTypes, channel){
|
|
|
|
while(this.LA(0) !== 0 && !this.match(tokenTypes, channel)){
|
|
this.get();
|
|
}
|
|
|
|
return this.LA(0);
|
|
},
|
|
get: function(channel){
|
|
|
|
var tokenInfo = this._tokenData,
|
|
reader = this._reader,
|
|
value,
|
|
i =0,
|
|
len = tokenInfo.length,
|
|
found = false,
|
|
token,
|
|
info;
|
|
if (this._lt.length && this._ltIndex >= 0 && this._ltIndex < this._lt.length){
|
|
|
|
i++;
|
|
this._token = this._lt[this._ltIndex++];
|
|
info = tokenInfo[this._token.type];
|
|
while((info.channel !== undefined && channel !== info.channel) &&
|
|
this._ltIndex < this._lt.length){
|
|
this._token = this._lt[this._ltIndex++];
|
|
info = tokenInfo[this._token.type];
|
|
i++;
|
|
}
|
|
if ((info.channel === undefined || channel === info.channel) &&
|
|
this._ltIndex <= this._lt.length){
|
|
this._ltIndexCache.push(i);
|
|
return this._token.type;
|
|
}
|
|
}
|
|
token = this._getToken();
|
|
if (token.type > -1 && !tokenInfo[token.type].hide){
|
|
token.channel = tokenInfo[token.type].channel;
|
|
this._token = token;
|
|
this._lt.push(token);
|
|
this._ltIndexCache.push(this._lt.length - this._ltIndex + i);
|
|
if (this._lt.length > 5){
|
|
this._lt.shift();
|
|
}
|
|
if (this._ltIndexCache.length > 5){
|
|
this._ltIndexCache.shift();
|
|
}
|
|
this._ltIndex = this._lt.length;
|
|
}
|
|
info = tokenInfo[token.type];
|
|
if (info &&
|
|
(info.hide ||
|
|
(info.channel !== undefined && channel !== info.channel))){
|
|
return this.get(channel);
|
|
} else {
|
|
return token.type;
|
|
}
|
|
},
|
|
LA: function(index){
|
|
var total = index,
|
|
tt;
|
|
if (index > 0){
|
|
if (index > 5){
|
|
throw new Error("Too much lookahead.");
|
|
}
|
|
while(total){
|
|
tt = this.get();
|
|
total--;
|
|
}
|
|
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;
|
|
|
|
},
|
|
LT: function(index){
|
|
this.LA(index);
|
|
return this._lt[this._ltIndex+index-1];
|
|
},
|
|
peek: function(){
|
|
return this.LA(1);
|
|
},
|
|
token: function(){
|
|
return this._token;
|
|
},
|
|
tokenName: function(tokenType){
|
|
if (tokenType < 0 || tokenType > this._tokenData.length){
|
|
return "UNKNOWN_TOKEN";
|
|
} else {
|
|
return this._tokenData[tokenType].name;
|
|
}
|
|
},
|
|
tokenType: function(tokenName){
|
|
return this._tokenData[tokenName] || -1;
|
|
},
|
|
unget: function(){
|
|
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
|
|
};
|
|
})();
|
|
(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",
|
|
darkgrey :"#a9a9a9",
|
|
darkgreen :"#006400",
|
|
darkkhaki :"#bdb76b",
|
|
darkmagenta :"#8b008b",
|
|
darkolivegreen :"#556b2f",
|
|
darkorange :"#ff8c00",
|
|
darkorchid :"#9932cc",
|
|
darkred :"#8b0000",
|
|
darksalmon :"#e9967a",
|
|
darkseagreen :"#8fbc8f",
|
|
darkslateblue :"#483d8b",
|
|
darkslategray :"#2f4f4f",
|
|
darkslategrey :"#2f4f4f",
|
|
darkturquoise :"#00ced1",
|
|
darkviolet :"#9400d3",
|
|
deeppink :"#ff1493",
|
|
deepskyblue :"#00bfff",
|
|
dimgray :"#696969",
|
|
dimgrey :"#696969",
|
|
dodgerblue :"#1e90ff",
|
|
firebrick :"#b22222",
|
|
floralwhite :"#fffaf0",
|
|
forestgreen :"#228b22",
|
|
fuchsia :"#ff00ff",
|
|
gainsboro :"#dcdcdc",
|
|
ghostwhite :"#f8f8ff",
|
|
gold :"#ffd700",
|
|
goldenrod :"#daa520",
|
|
gray :"#808080",
|
|
grey :"#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",
|
|
lightgrey :"#d3d3d3",
|
|
lightgreen :"#90ee90",
|
|
lightpink :"#ffb6c1",
|
|
lightsalmon :"#ffa07a",
|
|
lightseagreen :"#20b2aa",
|
|
lightskyblue :"#87cefa",
|
|
lightslategray :"#778899",
|
|
lightslategrey :"#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",
|
|
slategrey :"#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",
|
|
activeBorder :"Active window border.",
|
|
activecaption :"Active window caption.",
|
|
appworkspace :"Background color of multiple document interface.",
|
|
background :"Desktop background.",
|
|
buttonface :"The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.",
|
|
buttonhighlight :"The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
|
|
buttonshadow :"The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
|
|
buttontext :"Text on push buttons.",
|
|
captiontext :"Text in caption, size box, and scrollbar arrow box.",
|
|
graytext :"Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.",
|
|
greytext :"Greyed (disabled) text. This color is set to #000 if the current display driver does not support a solid grey color.",
|
|
highlight :"Item(s) selected in a control.",
|
|
highlighttext :"Text of item(s) selected in a control.",
|
|
inactiveborder :"Inactive window border.",
|
|
inactivecaption :"Inactive window caption.",
|
|
inactivecaptiontext :"Color of text in an inactive caption.",
|
|
infobackground :"Background color for tooltip controls.",
|
|
infotext :"Text color for tooltip controls.",
|
|
menu :"Menu background.",
|
|
menutext :"Text in menus.",
|
|
scrollbar :"Scroll bar gray area.",
|
|
threeddarkshadow :"The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
|
|
threedface :"The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
|
|
threedhighlight :"The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
|
|
threedlightshadow :"The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
|
|
threedshadow :"The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
|
|
window :"Window background.",
|
|
windowframe :"Window frame.",
|
|
windowtext :"Text in windows."
|
|
};
|
|
function Combinator(text, line, col){
|
|
|
|
SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE);
|
|
this.type = "unknown";
|
|
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;
|
|
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;
|
|
function MediaQuery(modifier, mediaType, features, line, col){
|
|
|
|
SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType : "") + (mediaType && features.length > 0 ? " and " : "") + 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;
|
|
function Parser(options){
|
|
EventTarget.call(this);
|
|
|
|
|
|
this.options = options || {};
|
|
|
|
this._tokenStream = null;
|
|
}
|
|
Parser.DEFAULT_TYPE = 0;
|
|
Parser.COMBINATOR_TYPE = 1;
|
|
Parser.MEDIA_FEATURE_TYPE = 2;
|
|
Parser.MEDIA_QUERY_TYPE = 3;
|
|
Parser.PROPERTY_NAME_TYPE = 4;
|
|
Parser.PROPERTY_VALUE_TYPE = 5;
|
|
Parser.PROPERTY_VALUE_PART_TYPE = 6;
|
|
Parser.SELECTOR_TYPE = 7;
|
|
Parser.SELECTOR_PART_TYPE = 8;
|
|
Parser.SELECTOR_SUB_PART_TYPE = 9;
|
|
|
|
Parser.prototype = function(){
|
|
|
|
var proto = new EventTarget(), //new prototype
|
|
prop,
|
|
additions = {
|
|
constructor: Parser,
|
|
DEFAULT_TYPE : 0,
|
|
COMBINATOR_TYPE : 1,
|
|
MEDIA_FEATURE_TYPE : 2,
|
|
MEDIA_QUERY_TYPE : 3,
|
|
PROPERTY_NAME_TYPE : 4,
|
|
PROPERTY_VALUE_TYPE : 5,
|
|
PROPERTY_VALUE_PART_TYPE : 6,
|
|
SELECTOR_TYPE : 7,
|
|
SELECTOR_PART_TYPE : 8,
|
|
SELECTOR_SUB_PART_TYPE : 9,
|
|
|
|
_stylesheet: function(){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
charset = null,
|
|
count,
|
|
token,
|
|
tt;
|
|
|
|
this.fire("startstylesheet");
|
|
this._charset();
|
|
|
|
this._skipCruft();
|
|
while (tokenStream.peek() == Tokens.IMPORT_SYM){
|
|
this._import();
|
|
this._skipCruft();
|
|
}
|
|
while (tokenStream.peek() == Tokens.NAMESPACE_SYM){
|
|
this._namespace();
|
|
this._skipCruft();
|
|
}
|
|
tt = tokenStream.peek();
|
|
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.VIEWPORT_SYM:
|
|
this._viewport();
|
|
this._skipCruft();
|
|
break;
|
|
case Tokens.UNKNOWN_SYM: //unknown @ rule
|
|
tokenStream.get();
|
|
if (!this.options.strict){
|
|
this.fire({
|
|
type: "error",
|
|
error: null,
|
|
message: "Unknown @ rule: " + tokenStream.LT(0).value + ".",
|
|
line: tokenStream.LT(0).startLine,
|
|
col: tokenStream.LT(0).startCol
|
|
});
|
|
count=0;
|
|
while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) == Tokens.LBRACE){
|
|
count++; //keep track of nesting depth
|
|
}
|
|
|
|
while(count){
|
|
tokenStream.advance([Tokens.RBRACE]);
|
|
count--;
|
|
}
|
|
|
|
} else {
|
|
throw new SyntaxError("Unknown @ rule.", tokenStream.LT(0).startLine, tokenStream.LT(0).startCol);
|
|
}
|
|
break;
|
|
case Tokens.S:
|
|
this._readWhitespace();
|
|
break;
|
|
default:
|
|
if(!this._ruleset()){
|
|
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){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
tt,
|
|
uri,
|
|
importToken,
|
|
mediaList = [];
|
|
tokenStream.mustMatch(Tokens.IMPORT_SYM);
|
|
importToken = tokenStream.token();
|
|
this._readWhitespace();
|
|
|
|
tokenStream.mustMatch([Tokens.STRING, Tokens.URI]);
|
|
uri = tokenStream.token().value.replace(/^(?:url\()?["']?([^"']+?)["']?\)?$/, "$1");
|
|
|
|
this._readWhitespace();
|
|
|
|
mediaList = this._media_query_list();
|
|
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){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
line,
|
|
col,
|
|
prefix,
|
|
uri;
|
|
tokenStream.mustMatch(Tokens.NAMESPACE_SYM);
|
|
line = tokenStream.token().startLine;
|
|
col = tokenStream.token().startCol;
|
|
this._readWhitespace();
|
|
if (tokenStream.match(Tokens.IDENT)){
|
|
prefix = tokenStream.token().value;
|
|
this._readWhitespace();
|
|
}
|
|
|
|
tokenStream.mustMatch([Tokens.STRING, Tokens.URI]);
|
|
uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1");
|
|
|
|
this._readWhitespace();
|
|
tokenStream.mustMatch(Tokens.SEMICOLON);
|
|
this._readWhitespace();
|
|
|
|
if (emit !== false){
|
|
this.fire({
|
|
type: "namespace",
|
|
prefix: prefix,
|
|
uri: uri,
|
|
line: line,
|
|
col: col
|
|
});
|
|
}
|
|
|
|
},
|
|
|
|
_media: function(){
|
|
var tokenStream = this._tokenStream,
|
|
line,
|
|
col,
|
|
mediaList;// = [];
|
|
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 (tokenStream.peek() == Tokens.FONT_FACE_SYM){
|
|
this._font_face();
|
|
} else if (tokenStream.peek() == Tokens.VIEWPORT_SYM){
|
|
this._viewport();
|
|
} else if (!this._ruleset()){
|
|
break;
|
|
}
|
|
}
|
|
|
|
tokenStream.mustMatch(Tokens.RBRACE);
|
|
this._readWhitespace();
|
|
|
|
this.fire({
|
|
type: "endmedia",
|
|
media: mediaList,
|
|
line: line,
|
|
col: col
|
|
});
|
|
},
|
|
_media_query_list: function(){
|
|
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;
|
|
},
|
|
_media_query: function(){
|
|
var tokenStream = this._tokenStream,
|
|
type = null,
|
|
ident = null,
|
|
token = null,
|
|
expressions = [];
|
|
|
|
if (tokenStream.match(Tokens.IDENT)){
|
|
ident = tokenStream.token().value.toLowerCase();
|
|
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);
|
|
},
|
|
_media_type: function(){
|
|
return this._media_feature();
|
|
},
|
|
_media_expression: function(){
|
|
var tokenStream = this._tokenStream,
|
|
feature = null,
|
|
token,
|
|
expression = null;
|
|
|
|
tokenStream.mustMatch(Tokens.LPAREN);
|
|
this._readWhitespace();
|
|
|
|
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));
|
|
},
|
|
_media_feature: function(){
|
|
var tokenStream = this._tokenStream;
|
|
|
|
tokenStream.mustMatch(Tokens.IDENT);
|
|
|
|
return SyntaxUnit.fromToken(tokenStream.token());
|
|
},
|
|
_page: function(){
|
|
var tokenStream = this._tokenStream,
|
|
line,
|
|
col,
|
|
identifier = null,
|
|
pseudoPage = null;
|
|
tokenStream.mustMatch(Tokens.PAGE_SYM);
|
|
line = tokenStream.token().startLine;
|
|
col = tokenStream.token().startCol;
|
|
|
|
this._readWhitespace();
|
|
|
|
if (tokenStream.match(Tokens.IDENT)){
|
|
identifier = tokenStream.token().value;
|
|
if (identifier.toLowerCase() === "auto"){
|
|
this._unexpectedToken(tokenStream.token());
|
|
}
|
|
}
|
|
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
|
|
});
|
|
|
|
},
|
|
_margin: function(){
|
|
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;
|
|
}
|
|
},
|
|
_margin_sym: function(){
|
|
|
|
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(){
|
|
|
|
var tokenStream = this._tokenStream;
|
|
|
|
tokenStream.mustMatch(Tokens.COLON);
|
|
tokenStream.mustMatch(Tokens.IDENT);
|
|
|
|
return tokenStream.token().value;
|
|
},
|
|
|
|
_font_face: function(){
|
|
var tokenStream = this._tokenStream,
|
|
line,
|
|
col;
|
|
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
|
|
});
|
|
},
|
|
|
|
_viewport: function(){
|
|
var tokenStream = this._tokenStream,
|
|
line,
|
|
col;
|
|
|
|
tokenStream.mustMatch(Tokens.VIEWPORT_SYM);
|
|
line = tokenStream.token().startLine;
|
|
col = tokenStream.token().startCol;
|
|
|
|
this._readWhitespace();
|
|
|
|
this.fire({
|
|
type: "startviewport",
|
|
line: line,
|
|
col: col
|
|
});
|
|
|
|
this._readDeclarations(true);
|
|
|
|
this.fire({
|
|
type: "endviewport",
|
|
line: line,
|
|
col: col
|
|
});
|
|
|
|
},
|
|
|
|
_operator: function(inFunction){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
token = null;
|
|
|
|
if (tokenStream.match([Tokens.SLASH, Tokens.COMMA]) ||
|
|
(inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))){
|
|
token = tokenStream.token();
|
|
this._readWhitespace();
|
|
}
|
|
return token ? PropertyValuePart.fromToken(token) : null;
|
|
|
|
},
|
|
|
|
_combinator: function(){
|
|
|
|
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(){
|
|
|
|
var tokenStream = this._tokenStream;
|
|
|
|
if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])){
|
|
return tokenStream.token().value;
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
|
|
_property: function(){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
value = null,
|
|
hack = null,
|
|
tokenValue,
|
|
token,
|
|
line,
|
|
col;
|
|
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;
|
|
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;
|
|
},
|
|
_ruleset: function(){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
tt,
|
|
selectors;
|
|
try {
|
|
selectors = this._selectors_group();
|
|
} catch (ex){
|
|
if (ex instanceof SyntaxError && !this.options.strict){
|
|
this.fire({
|
|
type: "error",
|
|
error: ex,
|
|
message: ex.message,
|
|
line: ex.line,
|
|
col: ex.col
|
|
});
|
|
tt = tokenStream.advance([Tokens.RBRACE]);
|
|
if (tt == Tokens.RBRACE){
|
|
} else {
|
|
throw ex;
|
|
}
|
|
|
|
} else {
|
|
throw ex;
|
|
}
|
|
return true;
|
|
}
|
|
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;
|
|
|
|
},
|
|
_selectors_group: function(){
|
|
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;
|
|
},
|
|
_selector: function(){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
selector = [],
|
|
nextSelector = null,
|
|
combinator = null,
|
|
ws = null;
|
|
nextSelector = this._simple_selector_sequence();
|
|
if (nextSelector === null){
|
|
return null;
|
|
}
|
|
|
|
selector.push(nextSelector);
|
|
|
|
do {
|
|
combinator = this._combinator();
|
|
|
|
if (combinator !== null){
|
|
selector.push(combinator);
|
|
nextSelector = this._simple_selector_sequence();
|
|
if (nextSelector === null){
|
|
this._unexpectedToken(tokenStream.LT(1));
|
|
} else {
|
|
selector.push(nextSelector);
|
|
}
|
|
} else {
|
|
if (this._readWhitespace()){
|
|
ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol);
|
|
combinator = this._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);
|
|
},
|
|
_simple_selector_sequence: function(){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
elementName = null,
|
|
modifiers = [],
|
|
selectorText= "",
|
|
components = [
|
|
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;
|
|
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){
|
|
if (tokenStream.peek() === Tokens.S){
|
|
break;
|
|
}
|
|
while(i < len && component === null){
|
|
component = components[i++].call(this);
|
|
}
|
|
|
|
if (component === null){
|
|
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;
|
|
},
|
|
_type_selector: function(){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
ns = this._namespace_prefix(),
|
|
elementName = this._element_name();
|
|
|
|
if (!elementName){
|
|
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;
|
|
}
|
|
},
|
|
_class: function(){
|
|
|
|
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;
|
|
}
|
|
|
|
},
|
|
_element_name: function(){
|
|
|
|
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;
|
|
}
|
|
},
|
|
_namespace_prefix: function(){
|
|
var tokenStream = this._tokenStream,
|
|
value = "";
|
|
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;
|
|
},
|
|
_universal: function(){
|
|
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;
|
|
|
|
},
|
|
_attrib: function(){
|
|
|
|
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;
|
|
}
|
|
},
|
|
_pseudo: function(){
|
|
|
|
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;
|
|
},
|
|
_functional_pseudo: function(){
|
|
|
|
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;
|
|
},
|
|
_expression: function(){
|
|
|
|
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, Tokens.SLASH])){
|
|
|
|
value += tokenStream.token().value;
|
|
value += this._readWhitespace();
|
|
}
|
|
|
|
return value.length ? value : null;
|
|
|
|
},
|
|
_negation: function(){
|
|
|
|
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;
|
|
},
|
|
_negation_arg: function(){
|
|
|
|
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++;
|
|
}
|
|
if (arg === null){
|
|
this._unexpectedToken(tokenStream.LT(1));
|
|
}
|
|
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(){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
property = null,
|
|
expr = null,
|
|
prio = null,
|
|
error = null,
|
|
invalid = null,
|
|
propertyName= "";
|
|
|
|
property = this._property();
|
|
if (property !== null){
|
|
|
|
tokenStream.mustMatch(Tokens.COLON);
|
|
this._readWhitespace();
|
|
|
|
expr = this._expr();
|
|
if (!expr || expr.length === 0){
|
|
this._unexpectedToken(tokenStream.LT(1));
|
|
}
|
|
|
|
prio = this._prio();
|
|
propertyName = property.toString();
|
|
if (this.options.starHack && property.hack == "*" ||
|
|
this.options.underscoreHack && property.hack == "_") {
|
|
|
|
propertyName = property.text;
|
|
}
|
|
|
|
try {
|
|
this._validateProperty(propertyName, expr);
|
|
} catch (ex) {
|
|
invalid = ex;
|
|
}
|
|
|
|
this.fire({
|
|
type: "property",
|
|
property: property,
|
|
value: expr,
|
|
important: prio,
|
|
line: property.line,
|
|
col: property.col,
|
|
invalid: invalid
|
|
});
|
|
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
_prio: function(){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
result = tokenStream.match(Tokens.IMPORTANT_SYM);
|
|
|
|
this._readWhitespace();
|
|
return result;
|
|
},
|
|
|
|
_expr: function(inFunction){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
values = [],
|
|
value = null,
|
|
operator = null;
|
|
|
|
value = this._term(inFunction);
|
|
if (value !== null){
|
|
|
|
values.push(value);
|
|
|
|
do {
|
|
operator = this._operator(inFunction);
|
|
if (operator){
|
|
values.push(operator);
|
|
} /*else {
|
|
values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col));
|
|
valueParts = [];
|
|
}*/
|
|
|
|
value = this._term(inFunction);
|
|
|
|
if (value === null){
|
|
break;
|
|
} else {
|
|
values.push(value);
|
|
}
|
|
} while(true);
|
|
}
|
|
|
|
return values.length > 0 ? new PropertyValue(values, values[0].line, values[0].col) : null;
|
|
},
|
|
|
|
_term: function(inFunction){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
unary = null,
|
|
value = null,
|
|
endChar = null,
|
|
token,
|
|
line,
|
|
col;
|
|
unary = this._unary_operator();
|
|
if (unary !== null){
|
|
line = tokenStream.token().startLine;
|
|
col = tokenStream.token().startCol;
|
|
}
|
|
if (tokenStream.peek() == Tokens.IE_FUNCTION && this.options.ieFilters){
|
|
|
|
value = this._ie_function();
|
|
if (unary === null){
|
|
line = tokenStream.token().startLine;
|
|
col = tokenStream.token().startCol;
|
|
}
|
|
} else if (inFunction && tokenStream.match([Tokens.LPAREN, Tokens.LBRACE, Tokens.LBRACKET])){
|
|
|
|
token = tokenStream.token();
|
|
endChar = token.endChar;
|
|
value = token.value + this._expr(inFunction).text;
|
|
if (unary === null){
|
|
line = tokenStream.token().startLine;
|
|
col = tokenStream.token().startCol;
|
|
}
|
|
tokenStream.mustMatch(Tokens.type(endChar));
|
|
value += endChar;
|
|
this._readWhitespace();
|
|
} 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 {
|
|
token = this._hexcolor();
|
|
if (token === null){
|
|
if (unary === null){
|
|
line = tokenStream.LT(1).startLine;
|
|
col = tokenStream.LT(1).startCol;
|
|
}
|
|
if (value === null){
|
|
if (tokenStream.LA(3) == Tokens.EQUALS && this.options.ieFilters){
|
|
value = this._ie_function();
|
|
} else {
|
|
value = this._function();
|
|
}
|
|
}
|
|
|
|
} 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(){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
functionText = null,
|
|
expr = null,
|
|
lt;
|
|
|
|
if (tokenStream.match(Tokens.FUNCTION)){
|
|
functionText = tokenStream.token().value;
|
|
this._readWhitespace();
|
|
expr = this._expr(true);
|
|
functionText += expr;
|
|
if (this.options.ieFilters && tokenStream.peek() == Tokens.EQUALS){
|
|
do {
|
|
|
|
if (this._readWhitespace()){
|
|
functionText += tokenStream.token().value;
|
|
}
|
|
if (tokenStream.LA(0) == Tokens.COMMA){
|
|
functionText += tokenStream.token().value;
|
|
}
|
|
|
|
tokenStream.match(Tokens.IDENT);
|
|
functionText += tokenStream.token().value;
|
|
|
|
tokenStream.match(Tokens.EQUALS);
|
|
functionText += tokenStream.token().value;
|
|
lt = tokenStream.peek();
|
|
while(lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN){
|
|
tokenStream.get();
|
|
functionText += tokenStream.token().value;
|
|
lt = tokenStream.peek();
|
|
}
|
|
} while(tokenStream.match([Tokens.COMMA, Tokens.S]));
|
|
}
|
|
|
|
tokenStream.match(Tokens.RPAREN);
|
|
functionText += ")";
|
|
this._readWhitespace();
|
|
}
|
|
|
|
return functionText;
|
|
},
|
|
|
|
_ie_function: function(){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
functionText = null,
|
|
expr = null,
|
|
lt;
|
|
if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])){
|
|
functionText = tokenStream.token().value;
|
|
|
|
do {
|
|
|
|
if (this._readWhitespace()){
|
|
functionText += tokenStream.token().value;
|
|
}
|
|
if (tokenStream.LA(0) == Tokens.COMMA){
|
|
functionText += tokenStream.token().value;
|
|
}
|
|
|
|
tokenStream.match(Tokens.IDENT);
|
|
functionText += tokenStream.token().value;
|
|
|
|
tokenStream.match(Tokens.EQUALS);
|
|
functionText += tokenStream.token().value;
|
|
lt = tokenStream.peek();
|
|
while(lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN){
|
|
tokenStream.get();
|
|
functionText += tokenStream.token().value;
|
|
lt = tokenStream.peek();
|
|
}
|
|
} while(tokenStream.match([Tokens.COMMA, Tokens.S]));
|
|
|
|
tokenStream.match(Tokens.RPAREN);
|
|
functionText += ")";
|
|
this._readWhitespace();
|
|
}
|
|
|
|
return functionText;
|
|
},
|
|
|
|
_hexcolor: function(){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
token = null,
|
|
color;
|
|
|
|
if(tokenStream.match(Tokens.HASH)){
|
|
|
|
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;
|
|
},
|
|
|
|
_keyframes: function(){
|
|
var tokenStream = this._tokenStream,
|
|
token,
|
|
tt,
|
|
name,
|
|
prefix = "";
|
|
|
|
tokenStream.mustMatch(Tokens.KEYFRAMES_SYM);
|
|
token = tokenStream.token();
|
|
if (/^@\-([^\-]+)\-/.test(token.value)) {
|
|
prefix = RegExp.$1;
|
|
}
|
|
|
|
this._readWhitespace();
|
|
name = this._keyframe_name();
|
|
|
|
this._readWhitespace();
|
|
tokenStream.mustMatch(Tokens.LBRACE);
|
|
|
|
this.fire({
|
|
type: "startkeyframes",
|
|
name: name,
|
|
prefix: prefix,
|
|
line: token.startLine,
|
|
col: token.startCol
|
|
});
|
|
|
|
this._readWhitespace();
|
|
tt = tokenStream.peek();
|
|
while(tt == Tokens.IDENT || tt == Tokens.PERCENTAGE) {
|
|
this._keyframe_rule();
|
|
this._readWhitespace();
|
|
tt = tokenStream.peek();
|
|
}
|
|
|
|
this.fire({
|
|
type: "endkeyframes",
|
|
name: name,
|
|
prefix: prefix,
|
|
line: token.startLine,
|
|
col: token.startCol
|
|
});
|
|
|
|
this._readWhitespace();
|
|
tokenStream.mustMatch(Tokens.RBRACE);
|
|
|
|
},
|
|
|
|
_keyframe_name: function(){
|
|
var tokenStream = this._tokenStream,
|
|
token;
|
|
|
|
tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]);
|
|
return SyntaxUnit.fromToken(tokenStream.token());
|
|
},
|
|
|
|
_keyframe_rule: function(){
|
|
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(){
|
|
var tokenStream = this._tokenStream,
|
|
token,
|
|
key,
|
|
keyList = [];
|
|
keyList.push(this._key());
|
|
|
|
this._readWhitespace();
|
|
|
|
while(tokenStream.match(Tokens.COMMA)){
|
|
this._readWhitespace();
|
|
keyList.push(this._key());
|
|
this._readWhitespace();
|
|
}
|
|
|
|
return keyList;
|
|
},
|
|
|
|
_key: function(){
|
|
|
|
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();
|
|
}
|
|
this._unexpectedToken(tokenStream.LT(1));
|
|
},
|
|
_skipCruft: function(){
|
|
while(this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])){
|
|
}
|
|
},
|
|
_readDeclarations: function(checkStart, readMargins){
|
|
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())){
|
|
} else if (this._declaration()){
|
|
if (!tokenStream.match(Tokens.SEMICOLON)){
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
this._readWhitespace();
|
|
}
|
|
|
|
tokenStream.mustMatch(Tokens.RBRACE);
|
|
this._readWhitespace();
|
|
|
|
} catch (ex) {
|
|
if (ex instanceof SyntaxError && !this.options.strict){
|
|
this.fire({
|
|
type: "error",
|
|
error: ex,
|
|
message: ex.message,
|
|
line: ex.line,
|
|
col: ex.col
|
|
});
|
|
tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE]);
|
|
if (tt == Tokens.SEMICOLON){
|
|
this._readDeclarations(false, readMargins);
|
|
} else if (tt != Tokens.RBRACE){
|
|
throw ex;
|
|
}
|
|
|
|
} else {
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
},
|
|
_readWhitespace: function(){
|
|
|
|
var tokenStream = this._tokenStream,
|
|
ws = "";
|
|
|
|
while(tokenStream.match(Tokens.S)){
|
|
ws += tokenStream.token().value;
|
|
}
|
|
|
|
return ws;
|
|
},
|
|
_unexpectedToken: function(token){
|
|
throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
|
|
},
|
|
_verifyEnd: function(){
|
|
if (this._tokenStream.LA(1) != Tokens.EOF){
|
|
this._unexpectedToken(this._tokenStream.LT(1));
|
|
}
|
|
},
|
|
_validateProperty: function(property, value){
|
|
Validation.validate(property, value);
|
|
},
|
|
|
|
parse: function(input){
|
|
this._tokenStream = new TokenStream(input, Tokens);
|
|
this._stylesheet();
|
|
},
|
|
|
|
parseStyleSheet: function(input){
|
|
return this.parse(input);
|
|
},
|
|
|
|
parseMediaQuery: function(input){
|
|
this._tokenStream = new TokenStream(input, Tokens);
|
|
var result = this._media_query();
|
|
this._verifyEnd();
|
|
return result;
|
|
},
|
|
parsePropertyValue: function(input){
|
|
|
|
this._tokenStream = new TokenStream(input, Tokens);
|
|
this._readWhitespace();
|
|
|
|
var result = this._expr();
|
|
this._readWhitespace();
|
|
this._verifyEnd();
|
|
return result;
|
|
},
|
|
parseRule: function(input){
|
|
this._tokenStream = new TokenStream(input, Tokens);
|
|
this._readWhitespace();
|
|
|
|
var result = this._ruleset();
|
|
this._readWhitespace();
|
|
this._verifyEnd();
|
|
return result;
|
|
},
|
|
parseSelector: function(input){
|
|
|
|
this._tokenStream = new TokenStream(input, Tokens);
|
|
this._readWhitespace();
|
|
|
|
var result = this._selector();
|
|
this._readWhitespace();
|
|
this._verifyEnd();
|
|
return result;
|
|
},
|
|
parseStyleAttribute: function(input){
|
|
input += "}"; // for error recovery in _readDeclarations()
|
|
this._tokenStream = new TokenStream(input, Tokens);
|
|
this._readDeclarations();
|
|
}
|
|
};
|
|
for (prop in additions){
|
|
if (additions.hasOwnProperty(prop)){
|
|
proto[prop] = additions[prop];
|
|
}
|
|
}
|
|
|
|
return proto;
|
|
}();
|
|
var Properties = {
|
|
"align-items" : "flex-start | flex-end | center | baseline | stretch",
|
|
"align-content" : "flex-start | flex-end | center | space-between | space-around | stretch",
|
|
"align-self" : "auto | flex-start | flex-end | center | baseline | stretch",
|
|
"-webkit-align-items" : "flex-start | flex-end | center | baseline | stretch",
|
|
"-webkit-align-content" : "flex-start | flex-end | center | space-between | space-around | stretch",
|
|
"-webkit-align-self" : "auto | flex-start | flex-end | center | baseline | stretch",
|
|
"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 | reverse | alternate | alternate-reverse", comma: true },
|
|
"animation-duration" : { multi: "<time>", comma: true },
|
|
"animation-fill-mode" : { multi: "none | forwards | backwards | both", comma: true },
|
|
"animation-iteration-count" : { multi: "<number> | infinite", comma: true },
|
|
"animation-name" : { multi: "none | <ident>", comma: true },
|
|
"animation-play-state" : { multi: "running | paused", comma: true },
|
|
"animation-timing-function" : 1,
|
|
"-moz-animation-delay" : { multi: "<time>", comma: true },
|
|
"-moz-animation-direction" : { multi: "normal | reverse | alternate | alternate-reverse", 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 | reverse | alternate | alternate-reverse", 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 | reverse | alternate | alternate-reverse", comma: true },
|
|
"-webkit-animation-duration" : { multi: "<time>", comma: true },
|
|
"-webkit-animation-fill-mode" : { multi: "none | forwards | backwards | both", 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 | reverse | alternate | alternate-reverse", 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 | none | inherit",
|
|
"azimuth" : function (expression) {
|
|
var simple = "<angle> | leftwards | rightwards | inherit",
|
|
direction = "left-side | far-left | left | center-left | center | center-right | right | far-right | right-side",
|
|
behind = false,
|
|
valid = false,
|
|
part;
|
|
|
|
if (!ValidationTypes.isAny(expression, simple)) {
|
|
if (ValidationTypes.isAny(expression, "behind")) {
|
|
behind = true;
|
|
valid = true;
|
|
}
|
|
|
|
if (ValidationTypes.isAny(expression, direction)) {
|
|
valid = true;
|
|
if (!behind) {
|
|
ValidationTypes.isAny(expression, "behind");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (expression.hasNext()) {
|
|
part = expression.next();
|
|
if (valid) {
|
|
throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
|
|
} else {
|
|
throw new ValidationError("Expected (<'azimuth'>) but found '" + part + "'.", part.line, part.col);
|
|
}
|
|
}
|
|
},
|
|
"backface-visibility" : "visible | hidden",
|
|
"background" : 1,
|
|
"background-attachment" : { multi: "<attachment>", comma: true },
|
|
"background-clip" : { multi: "<box>", comma: true },
|
|
"background-color" : "<color> | inherit",
|
|
"background-image" : { multi: "<bg-image>", comma: true },
|
|
"background-origin" : { multi: "<box>", comma: true },
|
|
"background-position" : { multi: "<bg-position>", comma: true },
|
|
"background-repeat" : { multi: "<repeat-style>" },
|
|
"background-size" : { multi: "<bg-size>", comma: true },
|
|
"baseline-shift" : "baseline | sub | super | <percentage> | <length>",
|
|
"behavior" : 1,
|
|
"binding" : 1,
|
|
"bleed" : "<length>",
|
|
"bookmark-label" : "<content> | <attr> | <string>",
|
|
"bookmark-level" : "none | <integer>",
|
|
"bookmark-state" : "open | closed",
|
|
"bookmark-target" : "none | <uri> | <attr>",
|
|
"border" : "<border-width> || <border-style> || <color>",
|
|
"border-bottom" : "<border-width> || <border-style> || <color>",
|
|
"border-bottom-color" : "<color> | inherit",
|
|
"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,
|
|
simple = "<length> | <percentage> | inherit",
|
|
slash = false,
|
|
fill = false,
|
|
count = 0,
|
|
max = 8,
|
|
part;
|
|
|
|
while (expression.hasNext() && count < max) {
|
|
valid = ValidationTypes.isAny(expression, simple);
|
|
if (!valid) {
|
|
|
|
if (expression.peek() == "/" && count > 0 && !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",
|
|
"-moz-box-align" : "start | end | center | baseline | stretch",
|
|
"-moz-box-decoration-break" : "slice |clone",
|
|
"-moz-box-direction" : "normal | reverse | inherit",
|
|
"-moz-box-flex" : "<number>",
|
|
"-moz-box-flex-group" : "<integer>",
|
|
"-moz-box-lines" : "single | multiple",
|
|
"-moz-box-ordinal-group" : "<integer>",
|
|
"-moz-box-orient" : "horizontal | vertical | inline-axis | block-axis | inherit",
|
|
"-moz-box-pack" : "start | end | center | justify",
|
|
"-webkit-box-align" : "start | end | center | baseline | stretch",
|
|
"-webkit-box-decoration-break" : "slice |clone",
|
|
"-webkit-box-direction" : "normal | reverse | inherit",
|
|
"-webkit-box-flex" : "<number>",
|
|
"-webkit-box-flex-group" : "<integer>",
|
|
"-webkit-box-lines" : "single | multiple",
|
|
"-webkit-box-ordinal-group" : "<integer>",
|
|
"-webkit-box-orient" : "horizontal | vertical | inline-axis | block-axis | inherit",
|
|
"-webkit-box-pack" : "start | end | center | justify",
|
|
"box-shadow" : function (expression) {
|
|
var result = false,
|
|
part;
|
|
|
|
if (!ValidationTypes.isAny(expression, "none")) {
|
|
Validation.multiProperty("<shadow>", expression, true, Infinity);
|
|
} else {
|
|
if (expression.hasNext()) {
|
|
part = expression.next();
|
|
throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
|
|
}
|
|
}
|
|
},
|
|
"box-sizing" : "content-box | border-box | inherit",
|
|
"break-after" : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column",
|
|
"break-before" : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column",
|
|
"break-inside" : "auto | avoid | avoid-page | avoid-column",
|
|
"caption-side" : "top | bottom | inherit",
|
|
"clear" : "none | right | left | both | inherit",
|
|
"clip" : 1,
|
|
"color" : "<color> | inherit",
|
|
"color-profile" : 1,
|
|
"column-count" : "<integer> | auto", //http://www.w3.org/TR/css3-multicol/
|
|
"column-fill" : "auto | balance",
|
|
"column-gap" : "<length> | normal",
|
|
"column-rule" : "<border-width> || <border-style> || <color>",
|
|
"column-rule-color" : "<color>",
|
|
"column-rule-style" : "<border-style>",
|
|
"column-rule-width" : "<border-width>",
|
|
"column-span" : "none | all",
|
|
"column-width" : "<length> | auto",
|
|
"columns" : 1,
|
|
"content" : 1,
|
|
"counter-increment" : 1,
|
|
"counter-reset" : 1,
|
|
"crop" : "<shape> | auto",
|
|
"cue" : "cue-after | cue-before | inherit",
|
|
"cue-after" : 1,
|
|
"cue-before" : 1,
|
|
"cursor" : 1,
|
|
"direction" : "ltr | rtl | inherit",
|
|
"display" : "inline | block | list-item | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | grid | inline-grid | none | inherit | -moz-box | -moz-inline-block | -moz-inline-box | -moz-inline-grid | -moz-inline-stack | -moz-inline-table | -moz-grid | -moz-grid-group | -moz-grid-line | -moz-groupbox | -moz-deck | -moz-popup | -moz-stack | -moz-marker | -webkit-box | -webkit-inline-box | -ms-flexbox | -ms-inline-flexbox | flex | -webkit-flex | inline-flex | -webkit-inline-flex",
|
|
"dominant-baseline" : 1,
|
|
"drop-initial-after-adjust" : "central | middle | after-edge | text-after-edge | ideographic | alphabetic | mathematical | <percentage> | <length>",
|
|
"drop-initial-after-align" : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
|
|
"drop-initial-before-adjust" : "before-edge | text-before-edge | central | middle | hanging | mathematical | <percentage> | <length>",
|
|
"drop-initial-before-align" : "caps-height | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
|
|
"drop-initial-size" : "auto | line | <length> | <percentage>",
|
|
"drop-initial-value" : "initial | <integer>",
|
|
"elevation" : "<angle> | below | level | above | higher | lower | inherit",
|
|
"empty-cells" : "show | hide | inherit",
|
|
"filter" : 1,
|
|
"fit" : "fill | hidden | meet | slice",
|
|
"fit-position" : 1,
|
|
"flex" : "<flex>",
|
|
"flex-basis" : "<width>",
|
|
"flex-direction" : "row | row-reverse | column | column-reverse",
|
|
"flex-flow" : "<flex-direction> || <flex-wrap>",
|
|
"flex-grow" : "<number>",
|
|
"flex-shrink" : "<number>",
|
|
"flex-wrap" : "nowrap | wrap | wrap-reverse",
|
|
"-webkit-flex" : "<flex>",
|
|
"-webkit-flex-basis" : "<width>",
|
|
"-webkit-flex-direction" : "row | row-reverse | column | column-reverse",
|
|
"-webkit-flex-flow" : "<flex-direction> || <flex-wrap>",
|
|
"-webkit-flex-grow" : "<number>",
|
|
"-webkit-flex-shrink" : "<number>",
|
|
"-webkit-flex-wrap" : "nowrap | wrap | wrap-reverse",
|
|
"-ms-flex" : "<flex>",
|
|
"-ms-flex-align" : "start | end | center | stretch | baseline",
|
|
"-ms-flex-direction" : "row | row-reverse | column | column-reverse | inherit",
|
|
"-ms-flex-order" : "<number>",
|
|
"-ms-flex-pack" : "start | end | center | justify",
|
|
"-ms-flex-wrap" : "nowrap | wrap | wrap-reverse",
|
|
"float" : "left | right | none | inherit",
|
|
"float-offset" : 1,
|
|
"font" : 1,
|
|
"font-family" : 1,
|
|
"font-size" : "<absolute-size> | <relative-size> | <length> | <percentage> | inherit",
|
|
"font-size-adjust" : "<number> | none | inherit",
|
|
"font-stretch" : "normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit",
|
|
"font-style" : "normal | italic | oblique | inherit",
|
|
"font-variant" : "normal | small-caps | inherit",
|
|
"font-weight" : "normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit",
|
|
"grid-cell-stacking" : "columns | rows | layer",
|
|
"grid-column" : 1,
|
|
"grid-columns" : 1,
|
|
"grid-column-align" : "start | end | center | stretch",
|
|
"grid-column-sizing" : 1,
|
|
"grid-column-span" : "<integer>",
|
|
"grid-flow" : "none | rows | columns",
|
|
"grid-layer" : "<integer>",
|
|
"grid-row" : 1,
|
|
"grid-rows" : 1,
|
|
"grid-row-align" : "start | end | center | stretch",
|
|
"grid-row-span" : "<integer>",
|
|
"grid-row-sizing" : 1,
|
|
"hanging-punctuation" : 1,
|
|
"height" : "<margin-width> | <content-sizing> | inherit",
|
|
"hyphenate-after" : "<integer> | auto",
|
|
"hyphenate-before" : "<integer> | auto",
|
|
"hyphenate-character" : "<string> | auto",
|
|
"hyphenate-lines" : "no-limit | <integer>",
|
|
"hyphenate-resource" : 1,
|
|
"hyphens" : "none | manual | auto",
|
|
"icon" : 1,
|
|
"image-orientation" : "angle | auto",
|
|
"image-rendering" : 1,
|
|
"image-resolution" : 1,
|
|
"inline-box-align" : "initial | last | <integer>",
|
|
"justify-content" : "flex-start | flex-end | center | space-between | space-around",
|
|
"-webkit-justify-content" : "flex-start | flex-end | center | space-between | space-around",
|
|
"left" : "<margin-width> | inherit",
|
|
"letter-spacing" : "<length> | normal | inherit",
|
|
"line-height" : "<number> | <length> | <percentage> | normal | inherit",
|
|
"line-break" : "auto | loose | normal | strict",
|
|
"line-stacking" : 1,
|
|
"line-stacking-ruby" : "exclude-ruby | include-ruby",
|
|
"line-stacking-shift" : "consider-shifts | disregard-shifts",
|
|
"line-stacking-strategy" : "inline-line-height | block-line-height | max-height | grid-height",
|
|
"list-style" : 1,
|
|
"list-style-image" : "<uri> | none | inherit",
|
|
"list-style-position" : "inside | outside | inherit",
|
|
"list-style-type" : "disc | circle | square | decimal | decimal-leading-zero | lower-roman | upper-roman | lower-greek | lower-latin | upper-latin | armenian | georgian | lower-alpha | upper-alpha | none | inherit",
|
|
"margin" : { multi: "<margin-width> | inherit", max: 4 },
|
|
"margin-bottom" : "<margin-width> | inherit",
|
|
"margin-left" : "<margin-width> | inherit",
|
|
"margin-right" : "<margin-width> | inherit",
|
|
"margin-top" : "<margin-width> | inherit",
|
|
"mark" : 1,
|
|
"mark-after" : 1,
|
|
"mark-before" : 1,
|
|
"marks" : 1,
|
|
"marquee-direction" : 1,
|
|
"marquee-play-count" : 1,
|
|
"marquee-speed" : 1,
|
|
"marquee-style" : 1,
|
|
"max-height" : "<length> | <percentage> | <content-sizing> | none | inherit",
|
|
"max-width" : "<length> | <percentage> | <content-sizing> | none | inherit",
|
|
"min-height" : "<length> | <percentage> | <content-sizing> | contain-floats | -moz-contain-floats | -webkit-contain-floats | inherit",
|
|
"min-width" : "<length> | <percentage> | <content-sizing> | contain-floats | -moz-contain-floats | -webkit-contain-floats | inherit",
|
|
"move-to" : 1,
|
|
"nav-down" : 1,
|
|
"nav-index" : 1,
|
|
"nav-left" : 1,
|
|
"nav-right" : 1,
|
|
"nav-up" : 1,
|
|
"opacity" : "<number> | inherit",
|
|
"order" : "<integer>",
|
|
"-webkit-order" : "<integer>",
|
|
"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-wrap" : "normal | break-word",
|
|
"overflow-x" : 1,
|
|
"overflow-y" : 1,
|
|
"padding" : { multi: "<padding-width> | inherit", max: 4 },
|
|
"padding-bottom" : "<padding-width> | inherit",
|
|
"padding-left" : "<padding-width> | inherit",
|
|
"padding-right" : "<padding-width> | inherit",
|
|
"padding-top" : "<padding-width> | inherit",
|
|
"page" : 1,
|
|
"page-break-after" : "auto | always | avoid | left | right | inherit",
|
|
"page-break-before" : "auto | always | avoid | left | right | inherit",
|
|
"page-break-inside" : "auto | avoid | inherit",
|
|
"page-policy" : 1,
|
|
"pause" : 1,
|
|
"pause-after" : 1,
|
|
"pause-before" : 1,
|
|
"perspective" : 1,
|
|
"perspective-origin" : 1,
|
|
"phonemes" : 1,
|
|
"pitch" : 1,
|
|
"pitch-range" : 1,
|
|
"play-during" : 1,
|
|
"pointer-events" : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit",
|
|
"position" : "static | relative | absolute | fixed | inherit",
|
|
"presentation-level" : 1,
|
|
"punctuation-trim" : 1,
|
|
"quotes" : 1,
|
|
"rendering-intent" : 1,
|
|
"resize" : 1,
|
|
"rest" : 1,
|
|
"rest-after" : 1,
|
|
"rest-before" : 1,
|
|
"richness" : 1,
|
|
"right" : "<margin-width> | inherit",
|
|
"rotation" : 1,
|
|
"rotation-point" : 1,
|
|
"ruby-align" : 1,
|
|
"ruby-overhang" : 1,
|
|
"ruby-position" : 1,
|
|
"ruby-span" : 1,
|
|
"size" : 1,
|
|
"speak" : "normal | none | spell-out | inherit",
|
|
"speak-header" : "once | always | inherit",
|
|
"speak-numeral" : "digits | continuous | inherit",
|
|
"speak-punctuation" : "code | none | inherit",
|
|
"speech-rate" : 1,
|
|
"src" : 1,
|
|
"stress" : 1,
|
|
"string-set" : 1,
|
|
|
|
"table-layout" : "auto | fixed | inherit",
|
|
"tab-size" : "<integer> | <length>",
|
|
"target" : 1,
|
|
"target-name" : 1,
|
|
"target-new" : 1,
|
|
"target-position" : 1,
|
|
"text-align" : "left | right | center | justify | inherit" ,
|
|
"text-align-last" : 1,
|
|
"text-decoration" : 1,
|
|
"text-emphasis" : 1,
|
|
"text-height" : 1,
|
|
"text-indent" : "<length> | <percentage> | inherit",
|
|
"text-justify" : "auto | none | inter-word | inter-ideograph | inter-cluster | distribute | kashida",
|
|
"text-outline" : 1,
|
|
"text-overflow" : 1,
|
|
"text-rendering" : "auto | optimizeSpeed | optimizeLegibility | geometricPrecision | inherit",
|
|
"text-shadow" : 1,
|
|
"text-transform" : "capitalize | uppercase | lowercase | none | inherit",
|
|
"text-wrap" : "normal | none | avoid",
|
|
"top" : "<margin-width> | inherit",
|
|
"-ms-touch-action" : "auto | none | pan-x | pan-y",
|
|
"touch-action" : "auto | none | pan-x | pan-y",
|
|
"transform" : 1,
|
|
"transform-origin" : 1,
|
|
"transform-style" : 1,
|
|
"transition" : 1,
|
|
"transition-delay" : 1,
|
|
"transition-duration" : 1,
|
|
"transition-property" : 1,
|
|
"transition-timing-function" : 1,
|
|
"unicode-bidi" : "normal | embed | isolate | bidi-override | isolate-override | plaintext | inherit",
|
|
"user-modify" : "read-only | read-write | write-only | inherit",
|
|
"user-select" : "none | text | toggle | element | elements | all | inherit",
|
|
"vertical-align" : "auto | use-script | baseline | sub | super | top | text-top | central | middle | bottom | text-bottom | <percentage> | <length>",
|
|
"visibility" : "visible | hidden | collapse | inherit",
|
|
"voice-balance" : 1,
|
|
"voice-duration" : 1,
|
|
"voice-family" : 1,
|
|
"voice-pitch" : 1,
|
|
"voice-pitch-range" : 1,
|
|
"voice-rate" : 1,
|
|
"voice-stress" : 1,
|
|
"voice-volume" : 1,
|
|
"volume" : 1,
|
|
"white-space" : "normal | pre | nowrap | pre-wrap | pre-line | inherit | -pre-wrap | -o-pre-wrap | -moz-pre-wrap | -hp-pre-wrap", //http://perishablepress.com/wrapping-content/
|
|
"white-space-collapse" : 1,
|
|
"widows" : "<integer> | inherit",
|
|
"width" : "<length> | <percentage> | <content-sizing> | auto | inherit",
|
|
"word-break" : "normal | keep-all | break-all",
|
|
"word-spacing" : "<length> | normal | inherit",
|
|
"word-wrap" : "normal | break-word",
|
|
"writing-mode" : "horizontal-tb | vertical-rl | vertical-lr | lr-tb | rl-tb | tb-rl | bt-rl | tb-lr | bt-lr | lr-bt | rl-bt | lr | rl | tb | inherit",
|
|
"z-index" : "<integer> | auto | inherit",
|
|
"zoom" : "<number> | <percentage> | normal"
|
|
};
|
|
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;
|
|
};
|
|
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;
|
|
function PropertyValueIterator(value){
|
|
this._i = 0;
|
|
this._parts = value.parts;
|
|
this._marks = [];
|
|
this.value = value;
|
|
|
|
}
|
|
PropertyValueIterator.prototype.count = function(){
|
|
return this._parts.length;
|
|
};
|
|
PropertyValueIterator.prototype.isFirst = function(){
|
|
return this._i === 0;
|
|
};
|
|
PropertyValueIterator.prototype.hasNext = function(){
|
|
return (this._i < this._parts.length);
|
|
};
|
|
PropertyValueIterator.prototype.mark = function(){
|
|
this._marks.push(this._i);
|
|
};
|
|
PropertyValueIterator.prototype.peek = function(count){
|
|
return this.hasNext() ? this._parts[this._i + (count || 0)] : null;
|
|
};
|
|
PropertyValueIterator.prototype.next = function(){
|
|
return this.hasNext() ? this._parts[this._i++] : null;
|
|
};
|
|
PropertyValueIterator.prototype.previous = function(){
|
|
return this._i > 0 ? this._parts[--this._i] : null;
|
|
};
|
|
PropertyValueIterator.prototype.restore = function(){
|
|
if (this._marks.length){
|
|
this._i = this._marks.pop();
|
|
}
|
|
};
|
|
function PropertyValuePart(text, line, col){
|
|
|
|
SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_VALUE_PART_TYPE);
|
|
this.type = "unknown";
|
|
|
|
var temp;
|
|
if (/^([+\-]?[\d\.]+)([a-z]+)$/i.test(text)){ //dimension
|
|
this.type = "dimension";
|
|
this.value = +RegExp.$1;
|
|
this.units = RegExp.$2;
|
|
switch(this.units.toLowerCase()){
|
|
|
|
case "em":
|
|
case "rem":
|
|
case "ex":
|
|
case "px":
|
|
case "cm":
|
|
case "mm":
|
|
case "in":
|
|
case "pt":
|
|
case "pc":
|
|
case "ch":
|
|
case "vh":
|
|
case "vw":
|
|
case "vmax":
|
|
case "vmin":
|
|
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;
|
|
|
|
}
|
|
|
|
} 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;
|
|
};
|
|
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;
|
|
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;
|
|
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;
|
|
function Specificity(a, b, c, d){
|
|
this.a = a;
|
|
this.b = b;
|
|
this.c = c;
|
|
this.d = d;
|
|
}
|
|
|
|
Specificity.prototype = {
|
|
constructor: Specificity,
|
|
compare: function(other){
|
|
var comps = ["a", "b", "c", "d"],
|
|
i, len;
|
|
|
|
for (i=0, len=comps.length; i < len; i++){
|
|
if (this[comps[i]] < other[comps[i]]){
|
|
return -1;
|
|
} else if (this[comps[i]] > other[comps[i]]){
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
},
|
|
valueOf: function(){
|
|
return (this.a * 1000) + (this.b * 100) + (this.c * 10) + this.d;
|
|
},
|
|
toString: function(){
|
|
return this.a + "," + this.b + "," + this.c + "," + this.d;
|
|
}
|
|
|
|
};
|
|
Specificity.calculate = function(selector){
|
|
|
|
var i, len,
|
|
part,
|
|
b=0, c=0, d=0;
|
|
|
|
function updateValues(part){
|
|
|
|
var i, j, len, num,
|
|
elementName = part.elementName ? part.elementName.text : "",
|
|
modifier;
|
|
|
|
if (elementName && elementName.charAt(elementName.length-1) != "*") {
|
|
d++;
|
|
}
|
|
|
|
for (i=0, len=part.modifiers.length; i < len; i++){
|
|
modifier = part.modifiers[i];
|
|
switch(modifier.type){
|
|
case "class":
|
|
case "attribute":
|
|
c++;
|
|
break;
|
|
|
|
case "id":
|
|
b++;
|
|
break;
|
|
|
|
case "pseudo":
|
|
if (Pseudos.isElement(modifier.text)){
|
|
d++;
|
|
} else {
|
|
c++;
|
|
}
|
|
break;
|
|
|
|
case "not":
|
|
for (j=0, num=modifier.args.length; j < num; j++){
|
|
updateValues(modifier.args[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i=0, len=selector.parts.length; i < len; i++){
|
|
part = selector.parts[i];
|
|
|
|
if (part instanceof SelectorPart){
|
|
updateValues(part);
|
|
}
|
|
}
|
|
|
|
return new Specificity(0, b, c, d);
|
|
};
|
|
|
|
var h = /^[0-9a-fA-F]$/,
|
|
nonascii = /^[\u0080-\uFFFF]$/,
|
|
nl = /\n|\r\n|\r|\f/;
|
|
|
|
|
|
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;
|
|
}
|
|
function TokenStream(input){
|
|
TokenStreamBase.call(this, input, Tokens);
|
|
}
|
|
|
|
TokenStream.prototype = mix(new TokenStreamBase(), {
|
|
_getToken: function(channel){
|
|
|
|
var c,
|
|
reader = this._reader,
|
|
token = null,
|
|
startLine = reader.getLine(),
|
|
startCol = reader.getCol();
|
|
|
|
c = reader.read();
|
|
|
|
|
|
while(c){
|
|
switch(c){
|
|
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;
|
|
}
|
|
default:
|
|
if (isDigit(c)){
|
|
token = this.numberToken(c, startLine, startCol);
|
|
} else
|
|
if (isWhitespace(c)){
|
|
token = this.whitespaceToken(c, startLine, startCol);
|
|
} else
|
|
if (isIdentStart(c)){
|
|
token = this.identOrFunctionToken(c, startLine, startCol);
|
|
} else
|
|
{
|
|
token = this.charToken(c, startLine, startCol);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!token && c === null){
|
|
token = this.createToken(Tokens.EOF,null,startLine,startCol);
|
|
}
|
|
|
|
return token;
|
|
},
|
|
createToken: function(tt, value, startLine, startCol, options){
|
|
var reader = this._reader;
|
|
options = options || {};
|
|
|
|
return {
|
|
value: value,
|
|
type: tt,
|
|
channel: options.channel,
|
|
endChar: options.endChar,
|
|
hide: options.hide || false,
|
|
startLine: startLine,
|
|
startCol: startCol,
|
|
endLine: reader.getLine(),
|
|
endCol: reader.getCol()
|
|
};
|
|
},
|
|
atRuleToken: function(first, startLine, startCol){
|
|
var rule = first,
|
|
reader = this._reader,
|
|
tt = Tokens.CHAR,
|
|
valid = false,
|
|
ident,
|
|
c;
|
|
reader.mark();
|
|
ident = this.readName();
|
|
rule = first + ident;
|
|
tt = Tokens.type(rule.toLowerCase());
|
|
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);
|
|
},
|
|
charToken: function(c, startLine, startCol){
|
|
var tt = Tokens.type(c);
|
|
var opts = {};
|
|
|
|
if (tt == -1){
|
|
tt = Tokens.CHAR;
|
|
} else {
|
|
opts.endChar = Tokens[tt].endChar;
|
|
}
|
|
|
|
return this.createToken(tt, c, startLine, startCol, opts);
|
|
},
|
|
commentToken: function(first, startLine, startCol){
|
|
var reader = this._reader,
|
|
comment = this.readComment(first);
|
|
|
|
return this.createToken(Tokens.COMMENT, comment, startLine, startCol);
|
|
},
|
|
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);
|
|
},
|
|
hashToken: function(first, startLine, startCol){
|
|
var reader = this._reader,
|
|
name = this.readName(first);
|
|
|
|
return this.createToken(Tokens.HASH, name, startLine, startCol);
|
|
},
|
|
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);
|
|
}
|
|
},
|
|
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);
|
|
}
|
|
},
|
|
identOrFunctionToken: function(first, startLine, startCol){
|
|
var reader = this._reader,
|
|
ident = this.readName(first),
|
|
tt = Tokens.IDENT;
|
|
if (reader.peek() == "("){
|
|
ident += reader.read();
|
|
if (ident.toLowerCase() == "url("){
|
|
tt = Tokens.URI;
|
|
ident = this.readURI(ident);
|
|
if (ident.toLowerCase() == "url("){
|
|
tt = Tokens.FUNCTION;
|
|
}
|
|
} else {
|
|
tt = Tokens.FUNCTION;
|
|
}
|
|
} else if (reader.peek() == ":"){ //might be an IE function
|
|
if (ident.toLowerCase() == "progid"){
|
|
ident += reader.readTo("(");
|
|
tt = Tokens.IE_FUNCTION;
|
|
}
|
|
}
|
|
|
|
return this.createToken(tt, ident, startLine, startCol);
|
|
},
|
|
importantToken: function(first, startLine, startCol){
|
|
var reader = this._reader,
|
|
important = first,
|
|
tt = Tokens.CHAR,
|
|
temp,
|
|
c;
|
|
|
|
reader.mark();
|
|
c = reader.read();
|
|
|
|
while(c){
|
|
if (c == "/"){
|
|
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);
|
|
}
|
|
|
|
|
|
},
|
|
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);
|
|
}
|
|
},
|
|
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$|^vmax$|^vmin$|^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);
|
|
},
|
|
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 (c == delim && prev != "\\"){
|
|
break;
|
|
}
|
|
if (isNewLine(reader.peek()) && c != "\\"){
|
|
tt = Tokens.INVALID;
|
|
break;
|
|
}
|
|
prev = c;
|
|
c = reader.read();
|
|
}
|
|
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;
|
|
if (reader.peek() == "+"){
|
|
reader.mark();
|
|
value += reader.read();
|
|
value += this.readUnicodeRangePart(true);
|
|
if (value.length == 2){
|
|
reader.reset();
|
|
} else {
|
|
|
|
tt = Tokens.UNICODE_RANGE;
|
|
if (value.indexOf("?") == -1){
|
|
|
|
if (reader.peek() == "-"){
|
|
reader.mark();
|
|
temp = reader.read();
|
|
temp += this.readUnicodeRangePart(false);
|
|
if (temp.length == 1){
|
|
reader.reset();
|
|
} else {
|
|
value += temp;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.createToken(tt, value, startLine, startCol);
|
|
},
|
|
whitespaceToken: function(first, startLine, startCol){
|
|
var reader = this._reader,
|
|
value = first + this.readWhitespace();
|
|
return this.createToken(Tokens.S, value, startLine, startCol);
|
|
},
|
|
|
|
readUnicodeRangePart: function(allowQuestionMark){
|
|
var reader = this._reader,
|
|
part = "",
|
|
c = reader.peek();
|
|
while(isHexDigit(c) && part.length < 6){
|
|
reader.read();
|
|
part += c;
|
|
c = reader.peek();
|
|
}
|
|
if (allowQuestionMark){
|
|
while(c == "?" && part.length < 6){
|
|
reader.read();
|
|
part += c;
|
|
c = reader.peek();
|
|
}
|
|
}
|
|
|
|
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 (c == delim && prev != "\\"){
|
|
break;
|
|
}
|
|
if (isNewLine(reader.peek()) && c != "\\"){
|
|
string = "";
|
|
break;
|
|
}
|
|
prev = c;
|
|
c = reader.peek();
|
|
}
|
|
if (c === null){
|
|
string = "";
|
|
}
|
|
|
|
return string;
|
|
},
|
|
readURI: function(first){
|
|
var reader = this._reader,
|
|
uri = first,
|
|
inner = "",
|
|
c = reader.peek();
|
|
|
|
reader.mark();
|
|
while(c && isWhitespace(c)){
|
|
reader.read();
|
|
c = reader.peek();
|
|
}
|
|
if (c == "'" || c == "\""){
|
|
inner = this.readString();
|
|
} else {
|
|
inner = this.readURL();
|
|
}
|
|
|
|
c = reader.peek();
|
|
while(c && isWhitespace(c)){
|
|
reader.read();
|
|
c = reader.peek();
|
|
}
|
|
if (inner === "" || c != ")"){
|
|
uri = first;
|
|
reader.reset();
|
|
} else {
|
|
uri += inner + reader.read();
|
|
}
|
|
|
|
return uri;
|
|
},
|
|
readURL: function(){
|
|
var reader = this._reader,
|
|
url = "",
|
|
c = reader.peek();
|
|
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;
|
|
if (comment.length > 2 && c == "*" && reader.peek() == "/"){
|
|
comment += reader.read();
|
|
break;
|
|
}
|
|
|
|
c = reader.read();
|
|
}
|
|
|
|
return comment;
|
|
} else {
|
|
return "";
|
|
}
|
|
|
|
}
|
|
});
|
|
|
|
var Tokens = [
|
|
{ name: "CDO"},
|
|
{ name: "CDC"},
|
|
{ name: "S", whitespace: true/*, channel: "ws"*/},
|
|
{ name: "COMMENT", comment: true, hide: true, channel: "comment" },
|
|
{ name: "INCLUDES", text: "~="},
|
|
{ name: "DASHMATCH", text: "|="},
|
|
{ name: "PREFIXMATCH", text: "^="},
|
|
{ name: "SUFFIXMATCH", text: "$="},
|
|
{ name: "SUBSTRINGMATCH", text: "*="},
|
|
{ name: "STRING"},
|
|
{ name: "IDENT"},
|
|
{ name: "HASH"},
|
|
{ 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: "VIEWPORT_SYM", text: ["@viewport", "@-ms-viewport"]},
|
|
{ name: "UNKNOWN_SYM" },
|
|
{ name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-o-keyframes" ] },
|
|
{ name: "IMPORTANT_SYM"},
|
|
{ name: "LENGTH"},
|
|
{ name: "ANGLE"},
|
|
{ name: "TIME"},
|
|
{ name: "FREQ"},
|
|
{ name: "DIMENSION"},
|
|
{ name: "PERCENTAGE"},
|
|
{ name: "NUMBER"},
|
|
{ name: "URI"},
|
|
{ name: "FUNCTION"},
|
|
{ name: "UNICODE_RANGE"},
|
|
{ name: "INVALID"},
|
|
{ name: "PLUS", text: "+" },
|
|
{ name: "GREATER", text: ">"},
|
|
{ name: "COMMA", text: ","},
|
|
{ name: "TILDE", text: "~"},
|
|
{ name: "NOT"},
|
|
{ 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"},
|
|
{ name: "RESOLUTION", state: "media"},
|
|
{ name: "IE_FUNCTION" },
|
|
{ name: "CHAR" },
|
|
{
|
|
name: "PIPE",
|
|
text: "|"
|
|
},
|
|
{
|
|
name: "SLASH",
|
|
text: "/"
|
|
},
|
|
{
|
|
name: "MINUS",
|
|
text: "-"
|
|
},
|
|
{
|
|
name: "STAR",
|
|
text: "*"
|
|
},
|
|
|
|
{
|
|
name: "LBRACE",
|
|
endChar: "}",
|
|
text: "{"
|
|
},
|
|
{
|
|
name: "RBRACE",
|
|
text: "}"
|
|
},
|
|
{
|
|
name: "LBRACKET",
|
|
endChar: "]",
|
|
text: "["
|
|
},
|
|
{
|
|
name: "RBRACKET",
|
|
text: "]"
|
|
},
|
|
{
|
|
name: "EQUALS",
|
|
text: "="
|
|
},
|
|
{
|
|
name: "COLON",
|
|
text: ":"
|
|
},
|
|
{
|
|
name: "SEMICOLON",
|
|
text: ";"
|
|
},
|
|
|
|
{
|
|
name: "LPAREN",
|
|
endChar: ")",
|
|
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;
|
|
};
|
|
|
|
})();
|
|
var Validation = {
|
|
|
|
validate: function(property, value){
|
|
var name = property.toString().toLowerCase(),
|
|
parts = value.parts,
|
|
expression = new PropertyValueIterator(value),
|
|
spec = Properties[name],
|
|
part,
|
|
valid,
|
|
j, count,
|
|
msg,
|
|
types,
|
|
last,
|
|
literals,
|
|
max, multi, group;
|
|
|
|
if (!spec) {
|
|
if (name.indexOf("-") !== 0){ //vendor prefixed are ok
|
|
throw new ValidationError("Unknown property '" + property + "'.", property.line, property.col);
|
|
}
|
|
} else if (typeof spec != "number"){
|
|
if (typeof spec == "string"){
|
|
if (spec.indexOf("||") > -1) {
|
|
this.groupProperty(spec, expression);
|
|
} else {
|
|
this.singleProperty(spec, expression, 1);
|
|
}
|
|
|
|
} else if (spec.multi) {
|
|
this.multiProperty(spec.multi, expression, spec.comma, spec.max || Infinity);
|
|
} else if (typeof spec == "function") {
|
|
spec(expression);
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
singleProperty: function(types, expression, max, partial) {
|
|
|
|
var result = false,
|
|
value = expression.value,
|
|
count = 0,
|
|
part;
|
|
|
|
while (expression.hasNext() && count < max) {
|
|
result = ValidationTypes.isAny(expression, types);
|
|
if (!result) {
|
|
break;
|
|
}
|
|
count++;
|
|
}
|
|
|
|
if (!result) {
|
|
if (expression.hasNext() && !expression.isFirst()) {
|
|
part = expression.peek();
|
|
throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
|
|
} else {
|
|
throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col);
|
|
}
|
|
} else if (expression.hasNext()) {
|
|
part = expression.next();
|
|
throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
|
|
}
|
|
|
|
},
|
|
|
|
multiProperty: function (types, expression, comma, max) {
|
|
|
|
var result = false,
|
|
value = expression.value,
|
|
count = 0,
|
|
sep = false,
|
|
part;
|
|
|
|
while(expression.hasNext() && !result && count < max) {
|
|
if (ValidationTypes.isAny(expression, types)) {
|
|
count++;
|
|
if (!expression.hasNext()) {
|
|
result = true;
|
|
|
|
} else if (comma) {
|
|
if (expression.peek() == ",") {
|
|
part = expression.next();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
if (!result) {
|
|
if (expression.hasNext() && !expression.isFirst()) {
|
|
part = expression.peek();
|
|
throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
|
|
} else {
|
|
part = expression.previous();
|
|
if (comma && part == ",") {
|
|
throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
|
|
} else {
|
|
throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col);
|
|
}
|
|
}
|
|
|
|
} else if (expression.hasNext()) {
|
|
part = expression.next();
|
|
throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
|
|
}
|
|
|
|
},
|
|
|
|
groupProperty: function (types, expression, comma) {
|
|
|
|
var result = false,
|
|
value = expression.value,
|
|
typeCount = types.split("||").length,
|
|
groups = { count: 0 },
|
|
partial = false,
|
|
name,
|
|
part;
|
|
|
|
while(expression.hasNext() && !result) {
|
|
name = ValidationTypes.isAnyOfGroup(expression, types);
|
|
if (name) {
|
|
if (groups[name]) {
|
|
break;
|
|
} else {
|
|
groups[name] = 1;
|
|
groups.count++;
|
|
partial = true;
|
|
|
|
if (groups.count == typeCount || !expression.hasNext()) {
|
|
result = true;
|
|
}
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!result) {
|
|
if (partial && expression.hasNext()) {
|
|
part = expression.peek();
|
|
throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
|
|
} else {
|
|
throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col);
|
|
}
|
|
} else if (expression.hasNext()) {
|
|
part = expression.next();
|
|
throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
};
|
|
function ValidationError(message, line, col){
|
|
this.col = col;
|
|
this.line = line;
|
|
this.message = message;
|
|
|
|
}
|
|
ValidationError.prototype = new Error();
|
|
var ValidationTypes = {
|
|
|
|
isLiteral: function (part, literals) {
|
|
var text = part.text.toString().toLowerCase(),
|
|
args = literals.split(" | "),
|
|
i, len, found = false;
|
|
|
|
for (i=0,len=args.length; i < len && !found; i++){
|
|
if (text == args[i].toLowerCase()){
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
},
|
|
|
|
isSimple: function(type) {
|
|
return !!this.simple[type];
|
|
},
|
|
|
|
isComplex: function(type) {
|
|
return !!this.complex[type];
|
|
},
|
|
isAny: function (expression, types) {
|
|
var args = types.split(" | "),
|
|
i, len, found = false;
|
|
|
|
for (i=0,len=args.length; i < len && !found && expression.hasNext(); i++){
|
|
found = this.isType(expression, args[i]);
|
|
}
|
|
|
|
return found;
|
|
},
|
|
isAnyOfGroup: function(expression, types) {
|
|
var args = types.split(" || "),
|
|
i, len, found = false;
|
|
|
|
for (i=0,len=args.length; i < len && !found; i++){
|
|
found = this.isType(expression, args[i]);
|
|
}
|
|
|
|
return found ? args[i-1] : false;
|
|
},
|
|
isType: function (expression, type) {
|
|
var part = expression.peek(),
|
|
result = false;
|
|
|
|
if (type.charAt(0) != "<") {
|
|
result = this.isLiteral(part, type);
|
|
if (result) {
|
|
expression.next();
|
|
}
|
|
} else if (this.simple[type]) {
|
|
result = this.simple[type](part);
|
|
if (result) {
|
|
expression.next();
|
|
}
|
|
} else {
|
|
result = this.complex[type](expression);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
|
|
|
|
simple: {
|
|
|
|
"<absolute-size>": function(part){
|
|
return ValidationTypes.isLiteral(part, "xx-small | x-small | small | medium | large | x-large | xx-large");
|
|
},
|
|
|
|
"<attachment>": function(part){
|
|
return ValidationTypes.isLiteral(part, "scroll | fixed | local");
|
|
},
|
|
|
|
"<attr>": function(part){
|
|
return part.type == "function" && part.name == "attr";
|
|
},
|
|
|
|
"<bg-image>": function(part){
|
|
return this["<image>"](part) || this["<gradient>"](part) || part == "none";
|
|
},
|
|
|
|
"<gradient>": function(part) {
|
|
return part.type == "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?(?:repeating\-)?(?:radial\-|linear\-)?gradient/i.test(part);
|
|
},
|
|
|
|
"<box>": function(part){
|
|
return ValidationTypes.isLiteral(part, "padding-box | border-box | content-box");
|
|
},
|
|
|
|
"<content>": function(part){
|
|
return part.type == "function" && part.name == "content";
|
|
},
|
|
|
|
"<relative-size>": function(part){
|
|
return ValidationTypes.isLiteral(part, "smaller | larger");
|
|
},
|
|
"<ident>": function(part){
|
|
return part.type == "identifier";
|
|
},
|
|
|
|
"<length>": function(part){
|
|
if (part.type == "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?calc/i.test(part)){
|
|
return true;
|
|
}else{
|
|
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");
|
|
},
|
|
|
|
"<content-sizing>": function(part){ // http://www.w3.org/TR/css3-sizing/#width-height-keywords
|
|
return ValidationTypes.isLiteral(part, "fill-available | -moz-available | -webkit-fill-available | max-content | -moz-max-content | -webkit-max-content | min-content | -moz-min-content | -webkit-min-content | fit-content | -moz-fit-content | -webkit-fit-content");
|
|
},
|
|
|
|
"<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";
|
|
},
|
|
|
|
"<flex-grow>": function(part){
|
|
return this["<number>"](part);
|
|
},
|
|
|
|
"<flex-shrink>": function(part){
|
|
return this["<number>"](part);
|
|
},
|
|
|
|
"<width>": function(part){
|
|
return this["<margin-width>"](part);
|
|
},
|
|
|
|
"<flex-basis>": function(part){
|
|
return this["<width>"](part);
|
|
},
|
|
|
|
"<flex-direction>": function(part){
|
|
return ValidationTypes.isLiteral(part, "row | row-reverse | column | column-reverse");
|
|
},
|
|
|
|
"<flex-wrap>": function(part){
|
|
return ValidationTypes.isLiteral(part, "nowrap | wrap | wrap-reverse");
|
|
}
|
|
},
|
|
|
|
complex: {
|
|
|
|
"<bg-position>": function(expression){
|
|
var types = this,
|
|
result = false,
|
|
numeric = "<percentage> | <length>",
|
|
xDir = "left | right",
|
|
yDir = "top | bottom",
|
|
count = 0,
|
|
hasNext = function() {
|
|
return expression.hasNext() && expression.peek() != ",";
|
|
};
|
|
|
|
while (expression.peek(count) && expression.peek(count) != ",") {
|
|
count++;
|
|
}
|
|
|
|
if (count < 3) {
|
|
if (ValidationTypes.isAny(expression, xDir + " | center | " + numeric)) {
|
|
result = true;
|
|
ValidationTypes.isAny(expression, yDir + " | center | " + numeric);
|
|
} else if (ValidationTypes.isAny(expression, yDir)) {
|
|
result = true;
|
|
ValidationTypes.isAny(expression, xDir + " | center");
|
|
}
|
|
} else {
|
|
if (ValidationTypes.isAny(expression, xDir)) {
|
|
if (ValidationTypes.isAny(expression, yDir)) {
|
|
result = true;
|
|
ValidationTypes.isAny(expression, numeric);
|
|
} else if (ValidationTypes.isAny(expression, numeric)) {
|
|
if (ValidationTypes.isAny(expression, yDir)) {
|
|
result = true;
|
|
ValidationTypes.isAny(expression, numeric);
|
|
} else if (ValidationTypes.isAny(expression, "center")) {
|
|
result = true;
|
|
}
|
|
}
|
|
} else if (ValidationTypes.isAny(expression, yDir)) {
|
|
if (ValidationTypes.isAny(expression, xDir)) {
|
|
result = true;
|
|
ValidationTypes.isAny(expression, numeric);
|
|
} else if (ValidationTypes.isAny(expression, numeric)) {
|
|
if (ValidationTypes.isAny(expression, xDir)) {
|
|
result = true;
|
|
ValidationTypes.isAny(expression, numeric);
|
|
} else if (ValidationTypes.isAny(expression, "center")) {
|
|
result = true;
|
|
}
|
|
}
|
|
} else if (ValidationTypes.isAny(expression, "center")) {
|
|
if (ValidationTypes.isAny(expression, xDir + " | " + yDir)) {
|
|
result = true;
|
|
ValidationTypes.isAny(expression, numeric);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
"<bg-size>": function(expression){
|
|
var types = this,
|
|
result = false,
|
|
numeric = "<percentage> | <length> | auto",
|
|
part,
|
|
i, len;
|
|
|
|
if (ValidationTypes.isAny(expression, "cover | contain")) {
|
|
result = true;
|
|
} else if (ValidationTypes.isAny(expression, numeric)) {
|
|
result = true;
|
|
ValidationTypes.isAny(expression, numeric);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
"<repeat-style>": function(expression){
|
|
var result = false,
|
|
values = "repeat | space | round | no-repeat",
|
|
part;
|
|
|
|
if (expression.hasNext()){
|
|
part = expression.next();
|
|
|
|
if (ValidationTypes.isLiteral(part, "repeat-x | repeat-y")) {
|
|
result = true;
|
|
} else if (ValidationTypes.isLiteral(part, values)) {
|
|
result = true;
|
|
|
|
if (expression.hasNext() && ValidationTypes.isLiteral(expression.peek(), values)) {
|
|
expression.next();
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
"<shadow>": function(expression) {
|
|
var result = false,
|
|
count = 0,
|
|
inset = false,
|
|
color = false,
|
|
part;
|
|
|
|
if (expression.hasNext()) {
|
|
|
|
if (ValidationTypes.isAny(expression, "inset")){
|
|
inset = true;
|
|
}
|
|
|
|
if (ValidationTypes.isAny(expression, "<color>")) {
|
|
color = true;
|
|
}
|
|
|
|
while (ValidationTypes.isAny(expression, "<length>") && count < 4) {
|
|
count++;
|
|
}
|
|
|
|
|
|
if (expression.hasNext()) {
|
|
if (!color) {
|
|
ValidationTypes.isAny(expression, "<color>");
|
|
}
|
|
|
|
if (!inset) {
|
|
ValidationTypes.isAny(expression, "inset");
|
|
}
|
|
|
|
}
|
|
|
|
result = (count >= 2 && count <= 4);
|
|
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
"<x-one-radius>": function(expression) {
|
|
var result = false,
|
|
simple = "<length> | <percentage> | inherit";
|
|
|
|
if (ValidationTypes.isAny(expression, simple)){
|
|
result = true;
|
|
ValidationTypes.isAny(expression, simple);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
"<flex>": function(expression) {
|
|
var part,
|
|
result = false;
|
|
if (ValidationTypes.isAny(expression, "none | inherit")) {
|
|
result = true;
|
|
} else {
|
|
if (ValidationTypes.isType(expression, "<flex-grow>")) {
|
|
if (expression.peek()) {
|
|
if (ValidationTypes.isType(expression, "<flex-shrink>")) {
|
|
if (expression.peek()) {
|
|
result = ValidationTypes.isType(expression, "<flex-basis>");
|
|
} else {
|
|
result = true;
|
|
}
|
|
} else if (ValidationTypes.isType(expression, "<flex-basis>")) {
|
|
result = expression.peek() === null;
|
|
}
|
|
} else {
|
|
result = true;
|
|
}
|
|
} else if (ValidationTypes.isType(expression, "<flex-basis>")) {
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
if (!result) {
|
|
part = expression.peek();
|
|
throw new ValidationError("Expected (none | [ <flex-grow> <flex-shrink>? || <flex-basis> ]) but found '" + expression.value.text + "'.", part.line, part.col);
|
|
}
|
|
|
|
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
|
|
};
|
|
})();
|
|
|
|
(function(){
|
|
for(var prop in parserlib){
|
|
exports[prop] = parserlib[prop];
|
|
}
|
|
})();
|
|
|
|
|
|
function objectToString(o) {
|
|
return Object.prototype.toString.call(o);
|
|
}
|
|
var util = {
|
|
isArray: function (ar) {
|
|
return Array.isArray(ar) || (typeof ar === 'object' && objectToString(ar) === '[object Array]');
|
|
},
|
|
isDate: function (d) {
|
|
return typeof d === 'object' && objectToString(d) === '[object Date]';
|
|
},
|
|
isRegExp: function (re) {
|
|
return typeof re === 'object' && objectToString(re) === '[object RegExp]';
|
|
},
|
|
getRegExpFlags: function (re) {
|
|
var flags = '';
|
|
re.global && (flags += 'g');
|
|
re.ignoreCase && (flags += 'i');
|
|
re.multiline && (flags += 'm');
|
|
return flags;
|
|
}
|
|
};
|
|
|
|
|
|
if (typeof module === 'object')
|
|
module.exports = clone;
|
|
|
|
function clone(parent, circular, depth, prototype) {
|
|
var allParents = [];
|
|
var allChildren = [];
|
|
|
|
var useBuffer = typeof Buffer != 'undefined';
|
|
|
|
if (typeof circular == 'undefined')
|
|
circular = true;
|
|
|
|
if (typeof depth == 'undefined')
|
|
depth = Infinity;
|
|
function _clone(parent, depth) {
|
|
if (parent === null)
|
|
return null;
|
|
|
|
if (depth == 0)
|
|
return parent;
|
|
|
|
var child;
|
|
if (typeof parent != 'object') {
|
|
return parent;
|
|
}
|
|
|
|
if (util.isArray(parent)) {
|
|
child = [];
|
|
} else if (util.isRegExp(parent)) {
|
|
child = new RegExp(parent.source, util.getRegExpFlags(parent));
|
|
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
|
|
} else if (util.isDate(parent)) {
|
|
child = new Date(parent.getTime());
|
|
} else if (useBuffer && Buffer.isBuffer(parent)) {
|
|
child = new Buffer(parent.length);
|
|
parent.copy(child);
|
|
return child;
|
|
} else {
|
|
if (typeof prototype == 'undefined') child = Object.create(Object.getPrototypeOf(parent));
|
|
else child = Object.create(prototype);
|
|
}
|
|
|
|
if (circular) {
|
|
var index = allParents.indexOf(parent);
|
|
|
|
if (index != -1) {
|
|
return allChildren[index];
|
|
}
|
|
allParents.push(parent);
|
|
allChildren.push(child);
|
|
}
|
|
|
|
for (var i in parent) {
|
|
child[i] = _clone(parent[i], depth - 1);
|
|
}
|
|
|
|
return child;
|
|
}
|
|
|
|
return _clone(parent, depth);
|
|
}
|
|
clone.clonePrototype = function(parent) {
|
|
if (parent === null)
|
|
return null;
|
|
|
|
var c = function () {};
|
|
c.prototype = parent;
|
|
return new c();
|
|
};
|
|
|
|
var CSSLint = (function(){
|
|
|
|
var rules = [],
|
|
formatters = [],
|
|
embeddedRuleset = /\/\*csslint([^\*]*)\*\//,
|
|
api = new parserlib.util.EventTarget();
|
|
|
|
api.version = "@VERSION@";
|
|
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;
|
|
});
|
|
};
|
|
api.getRuleset = function() {
|
|
var ruleset = {},
|
|
i = 0,
|
|
len = rules.length;
|
|
|
|
while (i < len){
|
|
ruleset[rules[i++].id] = 1; //by default, everything is a warning
|
|
}
|
|
|
|
return ruleset;
|
|
};
|
|
function applyEmbeddedRuleset(text, ruleset){
|
|
var valueMap,
|
|
embedded = text && text.match(embeddedRuleset),
|
|
rules = embedded && embedded[1];
|
|
|
|
if (rules) {
|
|
valueMap = {
|
|
"true": 2, // true is error
|
|
"": 1, // blank is warning
|
|
"false": 0, // false is ignore
|
|
|
|
"2": 2, // explicit error
|
|
"1": 1, // explicit warning
|
|
"0": 0 // explicit ignore
|
|
};
|
|
|
|
rules.toLowerCase().split(",").forEach(function(rule){
|
|
var pair = rule.split(":"),
|
|
property = pair[0] || "",
|
|
value = pair[1] || "";
|
|
|
|
ruleset[property.trim()] = valueMap[value.trim()];
|
|
});
|
|
}
|
|
|
|
return ruleset;
|
|
}
|
|
api.addFormatter = function(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);
|
|
};
|
|
api.verify = function(text, ruleset){
|
|
|
|
var i = 0,
|
|
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 = this.getRuleset();
|
|
}
|
|
|
|
if (embeddedRuleset.test(text)){
|
|
ruleset = clone(ruleset);
|
|
ruleset = applyEmbeddedRuleset(text, ruleset);
|
|
}
|
|
|
|
reporter = new Reporter(lines, ruleset);
|
|
|
|
ruleset.errors = 2; //always report parsing errors as errors
|
|
for (i in ruleset){
|
|
if(ruleset.hasOwnProperty(i) && ruleset[i]){
|
|
if (rules[i]){
|
|
rules[i].init(parser, reporter);
|
|
}
|
|
}
|
|
}
|
|
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,
|
|
ruleset : reporter.ruleset
|
|
};
|
|
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;
|
|
};
|
|
|
|
return api;
|
|
|
|
})();
|
|
function Reporter(lines, ruleset){
|
|
this.messages = [];
|
|
this.stats = [];
|
|
this.lines = lines;
|
|
this.ruleset = ruleset;
|
|
}
|
|
|
|
Reporter.prototype = {
|
|
constructor: Reporter,
|
|
error: function(message, line, col, rule){
|
|
this.messages.push({
|
|
type : "error",
|
|
line : line,
|
|
col : col,
|
|
message : message,
|
|
evidence: this.lines[line-1],
|
|
rule : rule || {}
|
|
});
|
|
},
|
|
warn: function(message, line, col, rule){
|
|
this.report(message, line, col, rule);
|
|
},
|
|
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
|
|
});
|
|
},
|
|
info: function(message, line, col, rule){
|
|
this.messages.push({
|
|
type : "info",
|
|
line : line,
|
|
col : col,
|
|
message : message,
|
|
evidence: this.lines[line-1],
|
|
rule : rule
|
|
});
|
|
},
|
|
rollupError: function(message, rule){
|
|
this.messages.push({
|
|
type : "error",
|
|
rollup : true,
|
|
message : message,
|
|
rule : rule
|
|
});
|
|
},
|
|
rollupWarn: function(message, rule){
|
|
this.messages.push({
|
|
type : "warning",
|
|
rollup : true,
|
|
message : message,
|
|
rule : rule
|
|
});
|
|
},
|
|
stat: function(name, value){
|
|
this.stats[name] = value;
|
|
}
|
|
};
|
|
CSSLint._Reporter = Reporter;
|
|
CSSLint.Util = {
|
|
mix: function(receiver, supplier){
|
|
var prop;
|
|
|
|
for (prop in supplier){
|
|
if (supplier.hasOwnProperty(prop)){
|
|
receiver[prop] = supplier[prop];
|
|
}
|
|
}
|
|
|
|
return prop;
|
|
},
|
|
indexOf: function(values, value){
|
|
if (values.indexOf){
|
|
return values.indexOf(value);
|
|
} else {
|
|
for (var i=0, len=values.length; i < len; i++){
|
|
if (values[i] === value){
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
},
|
|
forEach: function(values, func) {
|
|
if (values.forEach){
|
|
return values.forEach(func);
|
|
} else {
|
|
for (var i=0, len=values.length; i < len; i++){
|
|
func(values[i], i, values);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
CSSLint.addRule({
|
|
id: "adjoining-classes",
|
|
name: "Disallow adjoining classes",
|
|
desc: "Don't use adjoining classes.",
|
|
browsers: "IE6",
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
CSSLint.addRule({
|
|
id: "box-model",
|
|
name: "Beware of broken box size",
|
|
desc: "Don't use width or height when using padding or border.",
|
|
browsers: "All",
|
|
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,
|
|
boxSizing = false;
|
|
|
|
function startRule(){
|
|
properties = {};
|
|
boxSizing = false;
|
|
}
|
|
|
|
function endRule(){
|
|
var prop, value;
|
|
|
|
if (!boxSizing) {
|
|
if (properties.height){
|
|
for (prop in heightProperties){
|
|
if (heightProperties.hasOwnProperty(prop) && properties[prop]){
|
|
value = properties[prop].value;
|
|
if (!(prop === "padding" && value.parts.length === 2 && value.parts[0].value === 0)){
|
|
reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (properties.width){
|
|
for (prop in widthProperties){
|
|
if (widthProperties.hasOwnProperty(prop) && properties[prop]){
|
|
value = properties[prop].value;
|
|
|
|
if (!(prop === "padding" && value.parts.length === 2 && value.parts[1].value === 0)){
|
|
reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
parser.addListener("startrule", startRule);
|
|
parser.addListener("startfontface", startRule);
|
|
parser.addListener("startpage", startRule);
|
|
parser.addListener("startpagemargin", startRule);
|
|
parser.addListener("startkeyframerule", startRule);
|
|
|
|
parser.addListener("property", function(event){
|
|
var name = event.property.text.toLowerCase();
|
|
|
|
if (heightProperties[name] || widthProperties[name]){
|
|
if (!/^0\S*$/.test(event.value) && !(name === "border" && event.value.toString() === "none")){
|
|
properties[name] = { line: event.property.line, col: event.property.col, value: event.value };
|
|
}
|
|
} else {
|
|
if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)){
|
|
properties[name] = 1;
|
|
} else if (name === "box-sizing") {
|
|
boxSizing = true;
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
parser.addListener("endrule", endRule);
|
|
parser.addListener("endfontface", endRule);
|
|
parser.addListener("endpage", endRule);
|
|
parser.addListener("endpagemargin", endRule);
|
|
parser.addListener("endkeyframerule", endRule);
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "box-sizing",
|
|
name: "Disallow use of box-sizing",
|
|
desc: "The box-sizing properties isn't supported in IE6 and IE7.",
|
|
browsers: "IE6, IE7",
|
|
tags: ["Compatibility"],
|
|
init: function(parser, reporter){
|
|
var rule = this;
|
|
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "bulletproof-font-face",
|
|
name: "Use the bulletproof @font-face syntax",
|
|
desc: "Use the bulletproof @font-face syntax to avoid 404's in old IE (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax).",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this,
|
|
fontFaceRule = false,
|
|
firstSrc = true,
|
|
ruleFailed = false,
|
|
line, col;
|
|
parser.addListener("startfontface", function(){
|
|
fontFaceRule = true;
|
|
});
|
|
|
|
parser.addListener("property", function(event){
|
|
if (!fontFaceRule) {
|
|
return;
|
|
}
|
|
|
|
var propertyName = event.property.toString().toLowerCase(),
|
|
value = event.value.toString();
|
|
line = event.line;
|
|
col = event.col;
|
|
if (propertyName === "src") {
|
|
var regex = /^\s?url\(['"].+\.eot\?.*['"]\)\s*format\(['"]embedded-opentype['"]\).*$/i;
|
|
if (!value.match(regex) && firstSrc) {
|
|
ruleFailed = true;
|
|
firstSrc = false;
|
|
} else if (value.match(regex) && !firstSrc) {
|
|
ruleFailed = false;
|
|
}
|
|
}
|
|
|
|
|
|
});
|
|
parser.addListener("endfontface", function(){
|
|
fontFaceRule = false;
|
|
|
|
if (ruleFailed) {
|
|
reporter.report("@font-face declaration doesn't follow the fontspring bulletproof syntax.", line, col, rule);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "compatible-vendor-prefixes",
|
|
name: "Require compatible vendor prefixes",
|
|
desc: "Include all compatible vendor prefixes to reach a wider range of users.",
|
|
browsers: "All",
|
|
init: function (parser, reporter) {
|
|
var rule = this,
|
|
compatiblePrefixes,
|
|
properties,
|
|
prop,
|
|
variations,
|
|
prefixed,
|
|
i,
|
|
len,
|
|
inKeyFrame = false,
|
|
arrayPush = Array.prototype.push,
|
|
applyTo = [];
|
|
compatiblePrefixes = {
|
|
"animation" : "webkit moz",
|
|
"animation-delay" : "webkit moz",
|
|
"animation-direction" : "webkit moz",
|
|
"animation-duration" : "webkit moz",
|
|
"animation-fill-mode" : "webkit moz",
|
|
"animation-iteration-count" : "webkit moz",
|
|
"animation-name" : "webkit moz",
|
|
"animation-play-state" : "webkit moz",
|
|
"animation-timing-function" : "webkit moz",
|
|
"appearance" : "webkit moz",
|
|
"border-end" : "webkit moz",
|
|
"border-end-color" : "webkit moz",
|
|
"border-end-style" : "webkit moz",
|
|
"border-end-width" : "webkit moz",
|
|
"border-image" : "webkit moz o",
|
|
"border-radius" : "webkit",
|
|
"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",
|
|
"transition-delay" : "webkit moz o",
|
|
"transition-duration" : "webkit moz o",
|
|
"transition-property" : "webkit moz o",
|
|
"transition-timing-function" : "webkit moz o",
|
|
"user-modify" : "webkit moz",
|
|
"user-select" : "webkit moz 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("startkeyframes", function (event) {
|
|
inKeyFrame = event.prefix || true;
|
|
});
|
|
|
|
parser.addListener("endkeyframes", function () {
|
|
inKeyFrame = false;
|
|
});
|
|
|
|
parser.addListener("property", function (event) {
|
|
var name = event.property;
|
|
if (CSSLint.Util.indexOf(applyTo, name.text) > -1) {
|
|
if (!inKeyFrame || typeof inKeyFrame !== "string" ||
|
|
name.text.indexOf("-" + inKeyFrame + "-") !== 0) {
|
|
properties.push(name);
|
|
}
|
|
}
|
|
});
|
|
|
|
parser.addListener("endrule", function () {
|
|
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);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "display-property-grouping",
|
|
name: "Require properties appropriate for display",
|
|
desc: "Certain properties shouldn't be used with certain display property values.",
|
|
browsers: "All",
|
|
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":
|
|
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":
|
|
reportProperty("vertical-align", display);
|
|
break;
|
|
|
|
case "inline-block":
|
|
reportProperty("float", display);
|
|
break;
|
|
|
|
default:
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "duplicate-background-images",
|
|
name: "Disallow duplicate background images",
|
|
desc: "Every background-image should be unique. Use a common class for e.g. sprites.",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this,
|
|
stack = {};
|
|
|
|
parser.addListener("property", function(event){
|
|
var name = event.property.text,
|
|
value = event.value,
|
|
i, len;
|
|
|
|
if (name.match(/background/i)) {
|
|
for (i=0, len=value.parts.length; i < len; i++) {
|
|
if (value.parts[i].type === "uri") {
|
|
if (typeof stack[value.parts[i].uri] === "undefined") {
|
|
stack[value.parts[i].uri] = event;
|
|
}
|
|
else {
|
|
reporter.report("Background image '" + value.parts[i].uri + "' was used multiple times, first declared at line " + stack[value.parts[i].uri].line + ", col " + stack[value.parts[i].uri].col + ".", event.line, event.col, rule);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "duplicate-properties",
|
|
name: "Disallow duplicate properties",
|
|
desc: "Duplicate properties must appear one after the other.",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this,
|
|
properties,
|
|
lastProperty;
|
|
|
|
function startRule(){
|
|
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;
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "empty-rules",
|
|
name: "Disallow empty rules",
|
|
desc: "Rules without any properties specified should be removed.",
|
|
browsers: "All",
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "errors",
|
|
name: "Parsing Errors",
|
|
desc: "This rule looks for recoverable syntax errors.",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this;
|
|
|
|
parser.addListener("error", function(event){
|
|
reporter.error(event.message, event.line, event.col, rule);
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "fallback-colors",
|
|
name: "Require fallback colors",
|
|
desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.",
|
|
browsers: "IE6,IE7,IE8",
|
|
init: function(parser, reporter){
|
|
var rule = this,
|
|
lastProperty,
|
|
propertiesToCheck = {
|
|
color: 1,
|
|
background: 1,
|
|
"border-color": 1,
|
|
"border-top-color": 1,
|
|
"border-right-color": 1,
|
|
"border-bottom-color": 1,
|
|
"border-left-color": 1,
|
|
border: 1,
|
|
"border-top": 1,
|
|
"border-right": 1,
|
|
"border-bottom": 1,
|
|
"border-left": 1,
|
|
"background-color": 1
|
|
},
|
|
properties;
|
|
|
|
function startRule(){
|
|
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;
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "floats",
|
|
name: "Disallow too many floats",
|
|
desc: "This rule tests if the float property is used too many times",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this;
|
|
var count = 0;
|
|
parser.addListener("property", function(event){
|
|
if (event.property.text.toLowerCase() === "float" &&
|
|
event.value.text.toLowerCase() !== "none"){
|
|
count++;
|
|
}
|
|
});
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "font-faces",
|
|
name: "Don't use too many web fonts",
|
|
desc: "Too many different web fonts in the same stylesheet.",
|
|
browsers: "All",
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "font-sizes",
|
|
name: "Disallow too many font sizes",
|
|
desc: "Checks the number of font-size declarations.",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this,
|
|
count = 0;
|
|
parser.addListener("property", function(event){
|
|
if (event.property.toString() === "font-size"){
|
|
count++;
|
|
}
|
|
});
|
|
parser.addListener("endstylesheet", function(){
|
|
reporter.stat("font-sizes", count);
|
|
if (count >= 10){
|
|
reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule);
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "gradients",
|
|
name: "Require all gradient definitions",
|
|
desc: "When using a vendor-prefixed gradient, make sure to use them all.",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this,
|
|
gradients;
|
|
|
|
parser.addListener("startrule", function(){
|
|
gradients = {
|
|
moz: 0,
|
|
webkit: 0,
|
|
oldWebkit: 0,
|
|
o: 0
|
|
};
|
|
});
|
|
|
|
parser.addListener("property", function(event){
|
|
|
|
if (/\-(moz|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.o){
|
|
missing.push("Opera 11.1+");
|
|
}
|
|
|
|
if (missing.length && missing.length < 4){
|
|
reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule);
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "ids",
|
|
name: "Disallow IDs in selectors",
|
|
desc: "Selectors should not contain IDs.",
|
|
browsers: "All",
|
|
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);
|
|
}
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "import",
|
|
name: "Disallow @import",
|
|
desc: "Don't use @import, use <link> instead.",
|
|
browsers: "All",
|
|
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);
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "important",
|
|
name: "Disallow !important",
|
|
desc: "Be careful when using !important declaration",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this,
|
|
count = 0;
|
|
parser.addListener("property", function(event){
|
|
if (event.important === true){
|
|
count++;
|
|
reporter.report("Use of !important", event.line, event.col, rule);
|
|
}
|
|
});
|
|
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 specificity issues.", rule);
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "known-properties",
|
|
name: "Require use of known properties",
|
|
desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this;
|
|
|
|
parser.addListener("property", function(event){
|
|
if (event.invalid) {
|
|
reporter.report(event.invalid.message, event.line, event.col, rule);
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
});
|
|
CSSLint.addRule({
|
|
id: "order-alphabetical",
|
|
name: "Alphabetical order",
|
|
desc: "Assure properties are in alphabetical order",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this,
|
|
properties;
|
|
|
|
var startRule = function () {
|
|
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 name = event.property.text,
|
|
lowerCasePrefixLessName = name.toLowerCase().replace(/^-.*?-/, "");
|
|
|
|
properties.push(lowerCasePrefixLessName);
|
|
});
|
|
|
|
parser.addListener("endrule", function(event){
|
|
var currentProperties = properties.join(","),
|
|
expectedProperties = properties.sort().join(",");
|
|
|
|
if (currentProperties !== expectedProperties){
|
|
reporter.report("Rule doesn't have all its properties in alphabetical ordered.", event.line, event.col, rule);
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "outline-none",
|
|
name: "Disallow outline: none",
|
|
desc: "Use of outline: none or outline: 0 should be limited to :focus rules.",
|
|
browsers: "All",
|
|
tags: ["Accessibility"],
|
|
init: function(parser, reporter){
|
|
var rule = this,
|
|
lastRule;
|
|
|
|
function startRule(event){
|
|
if (event.selectors){
|
|
lastRule = {
|
|
line: event.line,
|
|
col: event.col,
|
|
selectors: event.selectors,
|
|
propCount: 0,
|
|
outline: false
|
|
};
|
|
} else {
|
|
lastRule = null;
|
|
}
|
|
}
|
|
|
|
function endRule(){
|
|
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.toString() === "none" || value.toString() === "0")){
|
|
lastRule.outline = true;
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
parser.addListener("endrule", endRule);
|
|
parser.addListener("endfontface", endRule);
|
|
parser.addListener("endpage", endRule);
|
|
parser.addListener("endpagemargin", endRule);
|
|
parser.addListener("endkeyframerule", endRule);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "overqualified-elements",
|
|
name: "Disallow overqualified elements",
|
|
desc: "Don't use classes or IDs with elements (a.foo or a#foo).",
|
|
browsers: "All",
|
|
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)){
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "qualified-headings",
|
|
name: "Disallow qualified headings",
|
|
desc: "Headings should not be qualified (namespaced).",
|
|
browsers: "All",
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
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",
|
|
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);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "rules-count",
|
|
name: "Rules Count",
|
|
desc: "Track how many rules there are.",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var count = 0;
|
|
parser.addListener("startrule", function(){
|
|
count++;
|
|
});
|
|
|
|
parser.addListener("endstylesheet", function(){
|
|
reporter.stat("rule-count", count);
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "selector-max-approaching",
|
|
name: "Warn when approaching the 4095 selector limit for IE",
|
|
desc: "Will warn when selector count is >= 3800 selectors.",
|
|
browsers: "IE",
|
|
init: function(parser, reporter) {
|
|
var rule = this, count = 0;
|
|
|
|
parser.addListener("startrule", function(event) {
|
|
count += event.selectors.length;
|
|
});
|
|
|
|
parser.addListener("endstylesheet", function() {
|
|
if (count >= 3800) {
|
|
reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.",0,0,rule);
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "selector-max",
|
|
name: "Error when past the 4095 selector limit for IE",
|
|
desc: "Will error when selector count is > 4095.",
|
|
browsers: "IE",
|
|
init: function(parser, reporter){
|
|
var rule = this, count = 0;
|
|
|
|
parser.addListener("startrule", function(event) {
|
|
count += event.selectors.length;
|
|
});
|
|
|
|
parser.addListener("endstylesheet", function() {
|
|
if (count > 4095) {
|
|
reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.",0,0,rule);
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "selector-newline",
|
|
name: "Disallow new-line characters in selectors",
|
|
desc: "New-line characters in selectors are usually a forgotten comma and not a descendant combinator.",
|
|
browsers: "All",
|
|
init: function(parser, reporter) {
|
|
var rule = this;
|
|
|
|
function startRule(event) {
|
|
var i, len, selector, p, n, pLen, part, part2, type, currentLine, nextLine,
|
|
selectors = event.selectors;
|
|
|
|
for (i = 0, len = selectors.length; i < len; i++) {
|
|
selector = selectors[i];
|
|
for (p = 0, pLen = selector.parts.length; p < pLen; p++) {
|
|
for (n = p + 1; n < pLen; n++) {
|
|
part = selector.parts[p];
|
|
part2 = selector.parts[n];
|
|
type = part.type;
|
|
currentLine = part.line;
|
|
nextLine = part2.line;
|
|
|
|
if (type === "descendant" && nextLine > currentLine) {
|
|
reporter.report("newline character found in selector (forgot a comma?)", currentLine, selectors[i].parts[0].col, rule);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
parser.addListener("startrule", startRule);
|
|
|
|
}
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "shorthand",
|
|
name: "Require shorthand properties",
|
|
desc: "Use shorthand properties where possible.",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this,
|
|
prop, i, len,
|
|
propertiesToCheck = {},
|
|
properties,
|
|
mapping = {
|
|
"margin": [
|
|
"margin-top",
|
|
"margin-bottom",
|
|
"margin-left",
|
|
"margin-right"
|
|
],
|
|
"padding": [
|
|
"padding-top",
|
|
"padding-bottom",
|
|
"padding-left",
|
|
"padding-right"
|
|
]
|
|
};
|
|
for (prop in mapping){
|
|
if (mapping.hasOwnProperty(prop)){
|
|
for (i=0, len=mapping[prop].length; i < len; i++){
|
|
propertiesToCheck[mapping[prop][i]] = prop;
|
|
}
|
|
}
|
|
}
|
|
|
|
function startRule(){
|
|
properties = {};
|
|
}
|
|
function endRule(event){
|
|
|
|
var prop, i, len, total;
|
|
for (prop in mapping){
|
|
if (mapping.hasOwnProperty(prop)){
|
|
total=0;
|
|
|
|
for (i=0, len=mapping[prop].length; i < len; i++){
|
|
total += properties[mapping[prop][i]] ? 1 : 0;
|
|
}
|
|
|
|
if (total === mapping[prop].length){
|
|
reporter.report("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
parser.addListener("startrule", startRule);
|
|
parser.addListener("startfontface", startRule);
|
|
parser.addListener("property", function(event){
|
|
var name = event.property.toString().toLowerCase();
|
|
|
|
if (propertiesToCheck[name]){
|
|
properties[name] = 1;
|
|
}
|
|
});
|
|
|
|
parser.addListener("endrule", endRule);
|
|
parser.addListener("endfontface", endRule);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "star-property-hack",
|
|
name: "Disallow properties with a star prefix",
|
|
desc: "Checks for the star property hack (targets IE6/7)",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this;
|
|
parser.addListener("property", function(event){
|
|
var property = event.property;
|
|
|
|
if (property.hack === "*") {
|
|
reporter.report("Property with star prefix found.", event.property.line, event.property.col, rule);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "text-indent",
|
|
name: "Disallow negative text-indent",
|
|
desc: "Checks for text indent less than -99px",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this,
|
|
textIndent,
|
|
direction;
|
|
|
|
|
|
function startRule(){
|
|
textIndent = false;
|
|
direction = "inherit";
|
|
}
|
|
function endRule(){
|
|
if (textIndent && direction !== "ltr"){
|
|
reporter.report("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set direction for that item to ltr.", textIndent.line, textIndent.col, rule);
|
|
}
|
|
}
|
|
|
|
parser.addListener("startrule", startRule);
|
|
parser.addListener("startfontface", startRule);
|
|
parser.addListener("property", function(event){
|
|
var name = event.property.toString().toLowerCase(),
|
|
value = event.value;
|
|
|
|
if (name === "text-indent" && value.parts[0].value < -99){
|
|
textIndent = event.property;
|
|
} else if (name === "direction" && value.toString() === "ltr"){
|
|
direction = "ltr";
|
|
}
|
|
});
|
|
|
|
parser.addListener("endrule", endRule);
|
|
parser.addListener("endfontface", endRule);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "underscore-property-hack",
|
|
name: "Disallow properties with an underscore prefix",
|
|
desc: "Checks for the underscore property hack (targets IE6)",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this;
|
|
parser.addListener("property", function(event){
|
|
var property = event.property;
|
|
|
|
if (property.hack === "_") {
|
|
reporter.report("Property with underscore prefix found.", event.property.line, event.property.col, rule);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "unique-headings",
|
|
name: "Headings should only be defined once",
|
|
desc: "Headings should be defined only once.",
|
|
browsers: "All",
|
|
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(){
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "universal-selector",
|
|
name: "Disallow universal selector",
|
|
desc: "The universal selector (*) is known to be slow.",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this;
|
|
|
|
parser.addListener("startrule", function(event){
|
|
var selectors = event.selectors,
|
|
selector,
|
|
part,
|
|
i;
|
|
|
|
for (i=0; i < selectors.length; i++){
|
|
selector = selectors[i];
|
|
|
|
part = selector.parts[selector.parts.length-1];
|
|
if (part.elementName === "*"){
|
|
reporter.report(rule.desc, part.line, part.col, rule);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "unqualified-attributes",
|
|
name: "Disallow unqualified attribute selectors",
|
|
desc: "Unqualified attribute selectors are known to be slow.",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this;
|
|
|
|
parser.addListener("startrule", function(event){
|
|
|
|
var selectors = event.selectors,
|
|
selector,
|
|
part,
|
|
modifier,
|
|
i, 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
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",
|
|
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"
|
|
};
|
|
function startRule(){
|
|
properties = {};
|
|
num = 1;
|
|
}
|
|
function endRule(){
|
|
var prop,
|
|
i,
|
|
len,
|
|
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 {
|
|
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);
|
|
}
|
|
|
|
});
|
|
|
|
CSSLint.addRule({
|
|
id: "zero-units",
|
|
name: "Disallow units for 0 values",
|
|
desc: "You don't need to specify units when a value is 0.",
|
|
browsers: "All",
|
|
init: function(parser, reporter){
|
|
var rule = this;
|
|
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++;
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
(function() {
|
|
var xmlEscape = function(str) {
|
|
if (!str || str.constructor !== String) {
|
|
return "";
|
|
}
|
|
|
|
return str.replace(/[\"&><]/g, function(match) {
|
|
switch (match) {
|
|
case "\"":
|
|
return """;
|
|
case "&":
|
|
return "&";
|
|
case "<":
|
|
return "<";
|
|
case ">":
|
|
return ">";
|
|
}
|
|
});
|
|
};
|
|
|
|
CSSLint.addFormatter({
|
|
id: "checkstyle-xml",
|
|
name: "Checkstyle XML format",
|
|
startFormat: function(){
|
|
return "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle>";
|
|
},
|
|
endFormat: function(){
|
|
return "</checkstyle>";
|
|
},
|
|
readError: function(filename, message) {
|
|
return "<file name=\"" + xmlEscape(filename) + "\"><error line=\"0\" column=\"0\" severty=\"error\" message=\"" + xmlEscape(message) + "\"></error></file>";
|
|
},
|
|
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,"");
|
|
};
|
|
|
|
|
|
|
|
if (messages.length > 0) {
|
|
output.push("<file name=\""+filename+"\">");
|
|
CSSLint.Util.forEach(messages, function (message) {
|
|
if (!message.rollup) {
|
|
output.push("<error line=\"" + message.line + "\" column=\"" + message.col + "\" severity=\"" + message.type + "\"" +
|
|
" message=\"" + xmlEscape(message.message) + "\" source=\"" + generateSource(message.rule) +"\"/>");
|
|
}
|
|
});
|
|
output.push("</file>");
|
|
}
|
|
|
|
return output.join("");
|
|
}
|
|
});
|
|
|
|
}());
|
|
|
|
CSSLint.addFormatter({
|
|
id: "compact",
|
|
name: "Compact, 'porcelain' format",
|
|
startFormat: function() {
|
|
return "";
|
|
},
|
|
endFormat: function() {
|
|
return "";
|
|
},
|
|
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) {
|
|
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 + " (" + message.rule.id + ")\n";
|
|
}
|
|
});
|
|
|
|
return output;
|
|
}
|
|
});
|
|
|
|
CSSLint.addFormatter({
|
|
id: "csslint-xml",
|
|
name: "CSSLint XML format",
|
|
startFormat: function(){
|
|
return "<?xml version=\"1.0\" encoding=\"utf-8\"?><csslint>";
|
|
},
|
|
endFormat: function(){
|
|
return "</csslint>";
|
|
},
|
|
formatResults: function(results, filename/*, options*/) {
|
|
var messages = results.messages,
|
|
output = [];
|
|
var escapeSpecialCharacters = function(str) {
|
|
if (!str || str.constructor !== String) {
|
|
return "";
|
|
}
|
|
return str.replace(/\"/g, "'").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
};
|
|
|
|
if (messages.length > 0) {
|
|
output.push("<file name=\""+filename+"\">");
|
|
CSSLint.Util.forEach(messages, function (message) {
|
|
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({
|
|
id: "junit-xml",
|
|
name: "JUNIT XML format",
|
|
startFormat: function(){
|
|
return "<?xml version=\"1.0\" encoding=\"utf-8\"?><testsuites>";
|
|
},
|
|
endFormat: function() {
|
|
return "</testsuites>";
|
|
},
|
|
formatResults: function(results, filename/*, options*/) {
|
|
|
|
var messages = results.messages,
|
|
output = [],
|
|
tests = {
|
|
"error": 0,
|
|
"failure": 0
|
|
};
|
|
var generateSource = function(rule) {
|
|
if (!rule || !("name" in rule)) {
|
|
return "";
|
|
}
|
|
return "net.csslint." + rule.name.replace(/\s/g,"");
|
|
};
|
|
var escapeSpecialCharacters = function(str) {
|
|
|
|
if (!str || str.constructor !== String) {
|
|
return "";
|
|
}
|
|
|
|
return str.replace(/\"/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
|
|
};
|
|
|
|
if (messages.length > 0) {
|
|
|
|
messages.forEach(function (message) {
|
|
var type = message.type === "warning" ? "error" : message.type;
|
|
if (!message.rollup) {
|
|
output.push("<testcase time=\"0\" name=\"" + generateSource(message.rule) + "\">");
|
|
output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\"><![CDATA[" + message.line + ":" + message.col + ":" + escapeSpecialCharacters(message.evidence) + "]]></" + type + ">");
|
|
output.push("</testcase>");
|
|
|
|
tests[type] += 1;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
output.unshift("<testsuite time=\"0\" tests=\"" + messages.length + "\" skipped=\"0\" errors=\"" + tests.error + "\" failures=\"" + tests.failure + "\" package=\"net.csslint\" name=\"" + filename + "\">");
|
|
output.push("</testsuite>");
|
|
|
|
}
|
|
|
|
return output.join("");
|
|
|
|
}
|
|
});
|
|
|
|
CSSLint.addFormatter({
|
|
id: "lint-xml",
|
|
name: "Lint XML format",
|
|
startFormat: function(){
|
|
return "<?xml version=\"1.0\" encoding=\"utf-8\"?><lint>";
|
|
},
|
|
endFormat: function(){
|
|
return "</lint>";
|
|
},
|
|
formatResults: function(results, filename/*, options*/) {
|
|
var messages = results.messages,
|
|
output = [];
|
|
var escapeSpecialCharacters = function(str) {
|
|
if (!str || str.constructor !== String) {
|
|
return "";
|
|
}
|
|
return str.replace(/\"/g, "'").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
};
|
|
|
|
if (messages.length > 0) {
|
|
|
|
output.push("<file name=\""+filename+"\">");
|
|
CSSLint.Util.forEach(messages, function (message) {
|
|
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({
|
|
id: "text",
|
|
name: "Plain Text",
|
|
startFormat: function() {
|
|
return "";
|
|
},
|
|
endFormat: function() {
|
|
return "";
|
|
},
|
|
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 ";
|
|
if (messages.length === 1) {
|
|
output += "is 1 problem";
|
|
} else {
|
|
output += "are " + messages.length + " problems";
|
|
}
|
|
output += " 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;
|
|
}
|
|
});
|
|
|
|
module.exports.CSSLint = CSSLint;
|
|
|
|
});
|
|
|
|
define("ace/mode/css_worker",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/worker/mirror","ace/mode/css/csslint"], function(require, exports, module) {
|
|
"use strict";
|
|
|
|
var oop = require("../lib/oop");
|
|
var lang = require("../lib/lang");
|
|
var Mirror = require("../worker/mirror").Mirror;
|
|
var CSSLint = require("./css/csslint").CSSLint;
|
|
|
|
var Worker = exports.Worker = function(sender) {
|
|
Mirror.call(this, sender);
|
|
this.setTimeout(400);
|
|
this.ruleset = null;
|
|
this.setDisabledRules("ids|order-alphabetical");
|
|
this.setInfoRules(
|
|
"adjoining-classes|qualified-headings|zero-units|gradients|" +
|
|
"import|outline-none|vendor-prefix"
|
|
);
|
|
};
|
|
|
|
oop.inherits(Worker, Mirror);
|
|
|
|
(function() {
|
|
this.setInfoRules = function(ruleNames) {
|
|
if (typeof ruleNames == "string")
|
|
ruleNames = ruleNames.split("|");
|
|
this.infoRules = lang.arrayToMap(ruleNames);
|
|
this.doc.getValue() && this.deferredUpdate.schedule(100);
|
|
};
|
|
|
|
this.setDisabledRules = function(ruleNames) {
|
|
if (!ruleNames) {
|
|
this.ruleset = null;
|
|
} else {
|
|
if (typeof ruleNames == "string")
|
|
ruleNames = ruleNames.split("|");
|
|
var all = {};
|
|
|
|
CSSLint.getRules().forEach(function(x){
|
|
all[x.id] = true;
|
|
});
|
|
ruleNames.forEach(function(x) {
|
|
delete all[x];
|
|
});
|
|
|
|
this.ruleset = all;
|
|
}
|
|
this.doc.getValue() && this.deferredUpdate.schedule(100);
|
|
};
|
|
|
|
this.onUpdate = function() {
|
|
var value = this.doc.getValue();
|
|
if (!value)
|
|
return this.sender.emit("annotate", []);
|
|
var infoRules = this.infoRules;
|
|
|
|
var result = CSSLint.verify(value, this.ruleset);
|
|
this.sender.emit("annotate", result.messages.map(function(msg) {
|
|
return {
|
|
row: msg.line - 1,
|
|
column: msg.col - 1,
|
|
text: msg.message,
|
|
type: infoRules[msg.rule.id] ? "info" : msg.type,
|
|
rule: msg.rule.name
|
|
};
|
|
}));
|
|
};
|
|
|
|
}).call(Worker.prototype);
|
|
|
|
});
|
|
|
|
define("ace/lib/es5-shim",["require","exports","module"], function(require, exports, module) {
|
|
|
|
function Empty() {}
|
|
|
|
if (!Function.prototype.bind) {
|
|
Function.prototype.bind = function bind(that) { // .length is 1
|
|
var target = this;
|
|
if (typeof target != "function") {
|
|
throw new TypeError("Function.prototype.bind called on incompatible " + target);
|
|
}
|
|
var args = slice.call(arguments, 1); // for normal call
|
|
var bound = function () {
|
|
|
|
if (this instanceof bound) {
|
|
|
|
var result = target.apply(
|
|
this,
|
|
args.concat(slice.call(arguments))
|
|
);
|
|
if (Object(result) === result) {
|
|
return result;
|
|
}
|
|
return this;
|
|
|
|
} else {
|
|
return target.apply(
|
|
that,
|
|
args.concat(slice.call(arguments))
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
if(target.prototype) {
|
|
Empty.prototype = target.prototype;
|
|
bound.prototype = new Empty();
|
|
Empty.prototype = null;
|
|
}
|
|
return bound;
|
|
};
|
|
}
|
|
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);
|
|
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__);
|
|
}
|
|
if ([1,2].splice(0).length != 2) {
|
|
if(function() { // test IE < 9 to splice bug - see issue #138
|
|
function makeArray(l) {
|
|
var a = new Array(l+2);
|
|
a[0] = a[1] = 0;
|
|
return a;
|
|
}
|
|
var array = [], lengthBefore;
|
|
|
|
array.splice.apply(array, makeArray(20));
|
|
array.splice.apply(array, makeArray(26));
|
|
|
|
lengthBefore = array.length; //46
|
|
array.splice(5, 0, "XXX"); // add one element
|
|
|
|
lengthBefore + 1 == array.length
|
|
|
|
if (lengthBefore + 1 == array.length) {
|
|
return true;// has right splice implementation without bugs
|
|
}
|
|
}()) {//IE 6/7
|
|
var array_splice = Array.prototype.splice;
|
|
Array.prototype.splice = function(start, deleteCount) {
|
|
if (!arguments.length) {
|
|
return [];
|
|
} else {
|
|
return array_splice.apply(this, [
|
|
start === void 0 ? 0 : start,
|
|
deleteCount === void 0 ? (this.length - start) : deleteCount
|
|
].concat(slice.call(arguments, 2)))
|
|
}
|
|
};
|
|
} else {//IE8
|
|
Array.prototype.splice = function(pos, removeCount){
|
|
var length = this.length;
|
|
if (pos > 0) {
|
|
if (pos > length)
|
|
pos = length;
|
|
} else if (pos == void 0) {
|
|
pos = 0;
|
|
} else if (pos < 0) {
|
|
pos = Math.max(length + pos, 0);
|
|
}
|
|
|
|
if (!(pos+removeCount < length))
|
|
removeCount = length - pos;
|
|
|
|
var removed = this.slice(pos, pos+removeCount);
|
|
var insert = slice.call(arguments, 2);
|
|
var add = insert.length;
|
|
if (pos === length) {
|
|
if (add) {
|
|
this.push.apply(this, insert);
|
|
}
|
|
} else {
|
|
var remove = Math.min(removeCount, length - pos);
|
|
var tailOldPos = pos + remove;
|
|
var tailNewPos = tailOldPos + add - remove;
|
|
var tailCount = length - tailOldPos;
|
|
var lengthAfterRemove = length - remove;
|
|
|
|
if (tailNewPos < tailOldPos) { // case A
|
|
for (var i = 0; i < tailCount; ++i) {
|
|
this[tailNewPos+i] = this[tailOldPos+i];
|
|
}
|
|
} else if (tailNewPos > tailOldPos) { // case B
|
|
for (i = tailCount; i--; ) {
|
|
this[tailNewPos+i] = this[tailOldPos+i];
|
|
}
|
|
} // else, add == remove (nothing to do)
|
|
|
|
if (add && pos === lengthAfterRemove) {
|
|
this.length = lengthAfterRemove; // truncate array
|
|
this.push.apply(this, insert);
|
|
} else {
|
|
this.length = lengthAfterRemove + add; // reserves space
|
|
for (i = 0; i < add; ++i) {
|
|
this[pos+i] = insert[i];
|
|
}
|
|
}
|
|
}
|
|
return removed;
|
|
};
|
|
}
|
|
}
|
|
if (!Array.isArray) {
|
|
Array.isArray = function isArray(obj) {
|
|
return _toString(obj) == "[object Array]";
|
|
};
|
|
}
|
|
var boxedString = Object("a"),
|
|
splitString = boxedString[0] != "a" || !(0 in boxedString);
|
|
|
|
if (!Array.prototype.forEach) {
|
|
Array.prototype.forEach = function forEach(fun /*, thisp*/) {
|
|
var object = toObject(this),
|
|
self = splitString && _toString(this) == "[object String]" ?
|
|
this.split("") :
|
|
object,
|
|
thisp = arguments[1],
|
|
i = -1,
|
|
length = self.length >>> 0;
|
|
if (_toString(fun) != "[object Function]") {
|
|
throw new TypeError(); // TODO message
|
|
}
|
|
|
|
while (++i < length) {
|
|
if (i in self) {
|
|
fun.call(thisp, self[i], i, object);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
if (!Array.prototype.map) {
|
|
Array.prototype.map = function map(fun /*, thisp*/) {
|
|
var object = toObject(this),
|
|
self = splitString && _toString(this) == "[object String]" ?
|
|
this.split("") :
|
|
object,
|
|
length = self.length >>> 0,
|
|
result = Array(length),
|
|
thisp = arguments[1];
|
|
if (_toString(fun) != "[object Function]") {
|
|
throw new TypeError(fun + " is not a function");
|
|
}
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
if (i in self)
|
|
result[i] = fun.call(thisp, self[i], i, object);
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
if (!Array.prototype.filter) {
|
|
Array.prototype.filter = function filter(fun /*, thisp */) {
|
|
var object = toObject(this),
|
|
self = splitString && _toString(this) == "[object String]" ?
|
|
this.split("") :
|
|
object,
|
|
length = self.length >>> 0,
|
|
result = [],
|
|
value,
|
|
thisp = arguments[1];
|
|
if (_toString(fun) != "[object Function]") {
|
|
throw new TypeError(fun + " is not a function");
|
|
}
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
if (i in self) {
|
|
value = self[i];
|
|
if (fun.call(thisp, value, i, object)) {
|
|
result.push(value);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
if (!Array.prototype.every) {
|
|
Array.prototype.every = function every(fun /*, thisp */) {
|
|
var object = toObject(this),
|
|
self = splitString && _toString(this) == "[object String]" ?
|
|
this.split("") :
|
|
object,
|
|
length = self.length >>> 0,
|
|
thisp = arguments[1];
|
|
if (_toString(fun) != "[object Function]") {
|
|
throw new TypeError(fun + " is not a function");
|
|
}
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
if (i in self && !fun.call(thisp, self[i], i, object)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
}
|
|
if (!Array.prototype.some) {
|
|
Array.prototype.some = function some(fun /*, thisp */) {
|
|
var object = toObject(this),
|
|
self = splitString && _toString(this) == "[object String]" ?
|
|
this.split("") :
|
|
object,
|
|
length = self.length >>> 0,
|
|
thisp = arguments[1];
|
|
if (_toString(fun) != "[object Function]") {
|
|
throw new TypeError(fun + " is not a function");
|
|
}
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
if (i in self && fun.call(thisp, self[i], i, object)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
}
|
|
if (!Array.prototype.reduce) {
|
|
Array.prototype.reduce = function reduce(fun /*, initial*/) {
|
|
var object = toObject(this),
|
|
self = splitString && _toString(this) == "[object String]" ?
|
|
this.split("") :
|
|
object,
|
|
length = self.length >>> 0;
|
|
if (_toString(fun) != "[object Function]") {
|
|
throw new TypeError(fun + " is not a function");
|
|
}
|
|
if (!length && arguments.length == 1) {
|
|
throw new TypeError("reduce of empty array with no initial value");
|
|
}
|
|
|
|
var i = 0;
|
|
var result;
|
|
if (arguments.length >= 2) {
|
|
result = arguments[1];
|
|
} else {
|
|
do {
|
|
if (i in self) {
|
|
result = self[i++];
|
|
break;
|
|
}
|
|
if (++i >= length) {
|
|
throw new TypeError("reduce of empty array with no initial value");
|
|
}
|
|
} while (true);
|
|
}
|
|
|
|
for (; i < length; i++) {
|
|
if (i in self) {
|
|
result = fun.call(void 0, result, self[i], i, object);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
}
|
|
if (!Array.prototype.reduceRight) {
|
|
Array.prototype.reduceRight = function reduceRight(fun /*, initial*/) {
|
|
var object = toObject(this),
|
|
self = splitString && _toString(this) == "[object String]" ?
|
|
this.split("") :
|
|
object,
|
|
length = self.length >>> 0;
|
|
if (_toString(fun) != "[object Function]") {
|
|
throw new TypeError(fun + " is not a function");
|
|
}
|
|
if (!length && arguments.length == 1) {
|
|
throw new TypeError("reduceRight of empty array with no initial value");
|
|
}
|
|
|
|
var result, i = length - 1;
|
|
if (arguments.length >= 2) {
|
|
result = arguments[1];
|
|
} else {
|
|
do {
|
|
if (i in self) {
|
|
result = self[i--];
|
|
break;
|
|
}
|
|
if (--i < 0) {
|
|
throw new TypeError("reduceRight of empty array with no initial value");
|
|
}
|
|
} while (true);
|
|
}
|
|
|
|
do {
|
|
if (i in this) {
|
|
result = fun.call(void 0, result, self[i], i, object);
|
|
}
|
|
} while (i--);
|
|
|
|
return result;
|
|
};
|
|
}
|
|
if (!Array.prototype.indexOf || ([0, 1].indexOf(1, 2) != -1)) {
|
|
Array.prototype.indexOf = function indexOf(sought /*, fromIndex */ ) {
|
|
var self = splitString && _toString(this) == "[object String]" ?
|
|
this.split("") :
|
|
toObject(this),
|
|
length = self.length >>> 0;
|
|
|
|
if (!length) {
|
|
return -1;
|
|
}
|
|
|
|
var i = 0;
|
|
if (arguments.length > 1) {
|
|
i = toInteger(arguments[1]);
|
|
}
|
|
i = i >= 0 ? i : Math.max(0, length + i);
|
|
for (; i < length; i++) {
|
|
if (i in self && self[i] === sought) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
}
|
|
if (!Array.prototype.lastIndexOf || ([0, 1].lastIndexOf(0, -3) != -1)) {
|
|
Array.prototype.lastIndexOf = function lastIndexOf(sought /*, fromIndex */) {
|
|
var self = splitString && _toString(this) == "[object String]" ?
|
|
this.split("") :
|
|
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]));
|
|
}
|
|
i = i >= 0 ? i : length - Math.abs(i);
|
|
for (; i >= 0; i--) {
|
|
if (i in self && sought === self[i]) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
}
|
|
if (!Object.getPrototypeOf) {
|
|
Object.getPrototypeOf = function getPrototypeOf(object) {
|
|
return object.__proto__ || (
|
|
object.constructor ?
|
|
object.constructor.prototype :
|
|
prototypeOfObject
|
|
);
|
|
};
|
|
}
|
|
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 (!owns(object, property))
|
|
return;
|
|
|
|
var descriptor, getter, setter;
|
|
descriptor = { enumerable: true, configurable: true };
|
|
if (supportsAccessors) {
|
|
var prototype = object.__proto__;
|
|
object.__proto__ = prototypeOfObject;
|
|
|
|
var getter = lookupGetter(object, property);
|
|
var setter = lookupSetter(object, property);
|
|
object.__proto__ = prototype;
|
|
|
|
if (getter || setter) {
|
|
if (getter) descriptor.get = getter;
|
|
if (setter) descriptor.set = setter;
|
|
return descriptor;
|
|
}
|
|
}
|
|
descriptor.value = object[property];
|
|
return descriptor;
|
|
};
|
|
}
|
|
if (!Object.getOwnPropertyNames) {
|
|
Object.getOwnPropertyNames = function getOwnPropertyNames(object) {
|
|
return Object.keys(object);
|
|
};
|
|
}
|
|
if (!Object.create) {
|
|
var createEmpty;
|
|
if (Object.prototype.__proto__ === null) {
|
|
createEmpty = function () {
|
|
return { "__proto__": null };
|
|
};
|
|
} else {
|
|
createEmpty = function () {
|
|
var empty = {};
|
|
for (var i in empty)
|
|
empty[i] = null;
|
|
empty.constructor =
|
|
empty.hasOwnProperty =
|
|
empty.propertyIsEnumerable =
|
|
empty.isPrototypeOf =
|
|
empty.toLocaleString =
|
|
empty.toString =
|
|
empty.valueOf =
|
|
empty.__proto__ = null;
|
|
return empty;
|
|
}
|
|
}
|
|
|
|
Object.create = function create(prototype, properties) {
|
|
var object;
|
|
if (prototype === null) {
|
|
object = createEmpty();
|
|
} else {
|
|
if (typeof prototype != "object")
|
|
throw new TypeError("typeof prototype["+(typeof prototype)+"] != 'object'");
|
|
var Type = function () {};
|
|
Type.prototype = prototype;
|
|
object = new Type();
|
|
object.__proto__ = prototype;
|
|
}
|
|
if (properties !== void 0)
|
|
Object.defineProperties(object, properties);
|
|
return object;
|
|
};
|
|
}
|
|
|
|
function doesDefinePropertyWork(object) {
|
|
try {
|
|
Object.defineProperty(object, "sentinel", {});
|
|
return "sentinel" in object;
|
|
} catch (exception) {
|
|
}
|
|
}
|
|
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);
|
|
if (definePropertyFallback) {
|
|
try {
|
|
return definePropertyFallback.call(Object, object, property, descriptor);
|
|
} catch (exception) {
|
|
}
|
|
}
|
|
if (owns(descriptor, "value")) {
|
|
|
|
if (supportsAccessors && (lookupGetter(object, property) ||
|
|
lookupSetter(object, property)))
|
|
{
|
|
var prototype = object.__proto__;
|
|
object.__proto__ = prototypeOfObject;
|
|
delete object[property];
|
|
object[property] = descriptor.value;
|
|
object.__proto__ = prototype;
|
|
} else {
|
|
object[property] = descriptor.value;
|
|
}
|
|
} else {
|
|
if (!supportsAccessors)
|
|
throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
|
|
if (owns(descriptor, "get"))
|
|
defineGetter(object, property, descriptor.get);
|
|
if (owns(descriptor, "set"))
|
|
defineSetter(object, property, descriptor.set);
|
|
}
|
|
|
|
return object;
|
|
};
|
|
}
|
|
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;
|
|
};
|
|
}
|
|
if (!Object.seal) {
|
|
Object.seal = function seal(object) {
|
|
return object;
|
|
};
|
|
}
|
|
if (!Object.freeze) {
|
|
Object.freeze = function freeze(object) {
|
|
return object;
|
|
};
|
|
}
|
|
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);
|
|
}
|
|
if (!Object.preventExtensions) {
|
|
Object.preventExtensions = function preventExtensions(object) {
|
|
return object;
|
|
};
|
|
}
|
|
if (!Object.isSealed) {
|
|
Object.isSealed = function isSealed(object) {
|
|
return false;
|
|
};
|
|
}
|
|
if (!Object.isFrozen) {
|
|
Object.isFrozen = function isFrozen(object) {
|
|
return false;
|
|
};
|
|
}
|
|
if (!Object.isExtensible) {
|
|
Object.isExtensible = function isExtensible(object) {
|
|
if (Object(object) === object) {
|
|
throw new TypeError(); // TODO message
|
|
}
|
|
var name = '';
|
|
while (owns(object, name)) {
|
|
name += '?';
|
|
}
|
|
object[name] = true;
|
|
var returnValue = owns(object, name);
|
|
delete object[name];
|
|
return returnValue;
|
|
};
|
|
}
|
|
if (!Object.keys) {
|
|
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;
|
|
};
|
|
|
|
}
|
|
if (!Date.now) {
|
|
Date.now = function now() {
|
|
return new Date().getTime();
|
|
};
|
|
}
|
|
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()) {
|
|
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, "");
|
|
};
|
|
}
|
|
|
|
function toInteger(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;
|
|
}
|
|
|
|
function isPrimitive(input) {
|
|
var type = typeof input;
|
|
return (
|
|
input === null ||
|
|
type === "undefined" ||
|
|
type === "boolean" ||
|
|
type === "number" ||
|
|
type === "string"
|
|
);
|
|
}
|
|
|
|
function toPrimitive(input) {
|
|
var val, valueOf, toString;
|
|
if (isPrimitive(input)) {
|
|
return input;
|
|
}
|
|
valueOf = input.valueOf;
|
|
if (typeof valueOf === "function") {
|
|
val = valueOf.call(input);
|
|
if (isPrimitive(val)) {
|
|
return val;
|
|
}
|
|
}
|
|
toString = input.toString;
|
|
if (typeof toString === "function") {
|
|
val = toString.call(input);
|
|
if (isPrimitive(val)) {
|
|
return val;
|
|
}
|
|
}
|
|
throw new TypeError();
|
|
}
|
|
var toObject = function (o) {
|
|
if (o == null) { // this matches both null and undefined
|
|
throw new TypeError("can't convert "+o+" to object");
|
|
}
|
|
return Object(o);
|
|
};
|
|
|
|
});
|