substance
Version:
Substance is a JavaScript library for web-based content editing. It provides building blocks for realizing custom text editors and web-based publishing systems.
1,078 lines (838 loc) • 25 kB
JavaScript
import domutils from 'domutils';
import BaseFuncs from 'boolbase';
import cssWhat from 'css-what';
import nthCheck from 'nth-check';
var universal = 50;
var tag = 30;
var attribute = 1;
var pseudo = 0;
var descendant = -1;
var child = -1;
var parent = -1;
var sibling = -1;
var adjacent = -1;
var procedure = {
universal: universal,
tag: tag,
attribute: attribute,
pseudo: pseudo,
descendant: descendant,
child: child,
parent: parent,
sibling: sibling,
adjacent: adjacent
};
var procedure$1 = Object.freeze({
universal: universal,
tag: tag,
attribute: attribute,
pseudo: pseudo,
descendant: descendant,
child: child,
parent: parent,
sibling: sibling,
adjacent: adjacent,
default: procedure
});
var procedure$2 = ( procedure$1 && procedure ) || procedure$1;
var sort = sortByProcedure;
var attributes = {
__proto__: null,
exists: 10,
equals: 8,
not: 7,
start: 6,
end: 6,
any: 5,
hyphen: 4,
element: 4
};
function sortByProcedure(arr){
var procs = arr.map(getProcedure);
for(var i = 1; i < arr.length; i++){
var procNew = procs[i];
if(procNew < 0) continue;
for(var j = i - 1; j >= 0 && procNew < procs[j]; j--){
var token = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = token;
procs[j + 1] = procs[j];
procs[j] = procNew;
}
}
}
function getProcedure(token){
var proc = procedure$2[token.type];
if(proc === procedure$2.attribute){
proc = attributes[token.action];
if(proc === attributes.equals && token.name === "id"){
proc = 9;
}
if(token.ignoreCase){
proc >>= 1;
}
} else if(proc === procedure$2.pseudo){
if(!token.data){
proc = 3;
} else if(token.name === "has" || token.name === "contains"){
proc = 0;
} else if(token.name === "matches" || token.name === "not"){
proc = 0;
for(var i = 0; i < token.data.length; i++){
if(token.data[i].length !== 1) continue;
var cur = getProcedure(token.data[i][0]);
if(cur === 0){
proc = 0;
break;
}
if(cur > proc) proc = cur;
}
if(token.data.length > 1 && proc > 0) proc -= 1;
} else {
proc = 1;
}
}
return proc;
}
var falseFunc$2 = BaseFuncs.falseFunc;
var reChars = /[-[\]{}()*+?.,\\^$|#\s]/g;
function factory(adapter){
var attributeRules = {
__proto__: null,
equals: function(next, data){
var name = data.name,
value = data.value;
if(data.ignoreCase){
value = value.toLowerCase();
return function equalsIC(elem){
var attr = adapter.getAttributeValue(elem, name);
return attr != null && attr.toLowerCase() === value && next(elem);
};
}
return function equals(elem){
return adapter.getAttributeValue(elem, name) === value && next(elem);
};
},
hyphen: function(next, data){
var name = data.name,
value = data.value,
len = value.length;
if(data.ignoreCase){
value = value.toLowerCase();
return function hyphenIC(elem){
var attr = adapter.getAttributeValue(elem, name);
return attr != null &&
(attr.length === len || attr.charAt(len) === "-") &&
attr.substr(0, len).toLowerCase() === value &&
next(elem);
};
}
return function hyphen(elem){
var attr = adapter.getAttributeValue(elem, name);
return attr != null &&
attr.substr(0, len) === value &&
(attr.length === len || attr.charAt(len) === "-") &&
next(elem);
};
},
element: function(next, data){
var name = data.name,
value = data.value;
if (data.name === 'class') {
let value = data.value;
if (/\s/.test(value)) return function() { return false }
return function clazz(elem) {
let classes = elem.classes;
return classes && classes.has(value) && next(elem)
}
} else {
if(/\s/.test(value)){
return falseFunc$2;
}
value = value.replace(reChars, "\\$&");
var pattern = "(?:^|\\s)" + value + "(?:$|\\s)",
flags = data.ignoreCase ? "i" : "",
regex = new RegExp(pattern, flags);
return function element(elem){
var attr = adapter.getAttributeValue(elem, name);
return attr != null && regex.test(attr) && next(elem);
};
}
},
exists: function(next, data){
var name = data.name;
return function exists(elem){
return adapter.hasAttrib(elem, name) && next(elem);
};
},
start: function(next, data){
var name = data.name,
value = data.value,
len = value.length;
if(len === 0){
return falseFunc$2;
}
if(data.ignoreCase){
value = value.toLowerCase();
return function startIC(elem){
var attr = adapter.getAttributeValue(elem, name);
return attr != null && attr.substr(0, len).toLowerCase() === value && next(elem);
};
}
return function start(elem){
var attr = adapter.getAttributeValue(elem, name);
return attr != null && attr.substr(0, len) === value && next(elem);
};
},
end: function(next, data){
var name = data.name,
value = data.value,
len = -value.length;
if(len === 0){
return falseFunc$2;
}
if(data.ignoreCase){
value = value.toLowerCase();
return function endIC(elem){
var attr = adapter.getAttributeValue(elem, name);
return attr != null && attr.substr(len).toLowerCase() === value && next(elem);
};
}
return function end(elem){
var attr = adapter.getAttributeValue(elem, name);
return attr != null && attr.substr(len) === value && next(elem);
};
},
any: function(next, data){
var name = data.name,
value = data.value;
if(value === ""){
return falseFunc$2;
}
if(data.ignoreCase){
var regex = new RegExp(value.replace(reChars, "\\$&"), "i");
return function anyIC(elem){
var attr = adapter.getAttributeValue(elem, name);
return attr != null && regex.test(attr) && next(elem);
};
}
return function any(elem){
var attr = adapter.getAttributeValue(elem, name);
return attr != null && attr.indexOf(value) >= 0 && next(elem);
};
},
not: function(next, data){
var name = data.name,
value = data.value;
if(value === ""){
return function notEmpty(elem){
return !!adapter.getAttributeValue(elem, name) && next(elem);
};
} else if(data.ignoreCase){
value = value.toLowerCase();
return function notIC(elem){
var attr = adapter.getAttributeValue(elem, name);
return attr != null && attr.toLowerCase() !== value && next(elem);
};
}
return function not(elem){
return adapter.getAttributeValue(elem, name) !== value && next(elem);
};
}
};
return {
compile: function(next, data, options){
if(options && options.strict && (
data.ignoreCase || data.action === "not"
)) throw new Error("Unsupported attribute selector");
return attributeRules[data.action](next, data);
},
rules: attributeRules
};
}
var attributes$1 = factory;
function generalFactory(adapter, Pseudos){
return {
__proto__: null,
attribute: attributes$1(adapter).compile,
pseudo: Pseudos.compile,
tag: function(next, data){
var name = data.name;
return function tag(elem){
return adapter.getNameWithoutNS(elem) === name && next(elem);
}
},
descendant: function(next){
return function descendant(elem){
var found = false;
while(!found && (elem = adapter.getParent(elem))){
found = next(elem);
}
return found;
};
},
_flexibleDescendant: function(next){
return function descendant(elem){
var found = next(elem);
while(!found && (elem = adapter.getParent(elem))){
found = next(elem);
}
return found;
};
},
parent: function(next, data, options){
if(options && options.strict) throw new Error("Parent selector isn't part of CSS3");
return function parent(elem){
return adapter.getChildren(elem).some(test);
};
function test(elem){
return adapter.isTag(elem) && next(elem);
}
},
child: function(next){
return function child(elem){
var parent = adapter.getParent(elem);
return !!parent && next(parent);
};
},
sibling: function(next){
return function sibling(elem){
var siblings = adapter.getSiblings(elem);
for(var i = 0; i < siblings.length; i++){
if(adapter.isTag(siblings[i])){
if(siblings[i] === elem) break;
if(next(siblings[i])) return true;
}
}
return false;
};
},
adjacent: function(next){
return function adjacent(elem){
var siblings = adapter.getSiblings(elem),
lastElement;
for(var i = 0; i < siblings.length; i++){
if(adapter.isTag(siblings[i])){
if(siblings[i] === elem) break;
lastElement = siblings[i];
}
}
return !!lastElement && next(lastElement);
};
},
universal: function(next){
return next;
}
};
}
var general = generalFactory;
var trueFunc$1 = BaseFuncs.trueFunc;
var falseFunc$3 = BaseFuncs.falseFunc;
function filtersFactory(adapter){
var attributes = attributes$1(adapter),
checkAttrib = attributes.rules.equals;
function equals(a, b){
if(typeof adapter.equals === "function") return adapter.equals(a, b);
return a === b;
}
function getAttribFunc(name, value){
var data = {name: name, value: value};
return function attribFunc(next){
return checkAttrib(next, data);
};
}
function getChildFunc(next){
return function(elem){
return !!adapter.getParent(elem) && next(elem);
};
}
var filters = {
contains: function(next, text){
return function contains(elem){
return next(elem) && adapter.getText(elem).indexOf(text) >= 0;
};
},
icontains: function(next, text){
var itext = text.toLowerCase();
return function icontains(elem){
return next(elem) &&
adapter.getText(elem).toLowerCase().indexOf(itext) >= 0;
};
},
"nth-child": function(next, rule){
var func = nthCheck(rule);
if(func === falseFunc$3) return func;
if(func === trueFunc$1) return getChildFunc(next);
return function nthChild(elem){
var siblings = adapter.getSiblings(elem);
for(var i = 0, pos = 0; i < siblings.length; i++){
if(adapter.isTag(siblings[i])){
if(siblings[i] === elem) break;
else pos++;
}
}
return func(pos) && next(elem);
};
},
"nth-last-child": function(next, rule){
var func = nthCheck(rule);
if(func === falseFunc$3) return func;
if(func === trueFunc$1) return getChildFunc(next);
return function nthLastChild(elem){
var siblings = adapter.getSiblings(elem);
for(var pos = 0, i = siblings.length - 1; i >= 0; i--){
if(adapter.isTag(siblings[i])){
if(siblings[i] === elem) break;
else pos++;
}
}
return func(pos) && next(elem);
};
},
"nth-of-type": function(next, rule){
var func = nthCheck(rule);
if(func === falseFunc$3) return func;
if(func === trueFunc$1) return getChildFunc(next);
return function nthOfType(elem){
var siblings = adapter.getSiblings(elem);
for(var pos = 0, i = 0; i < siblings.length; i++){
if(adapter.isTag(siblings[i])){
if(siblings[i] === elem) break;
if(adapter.getName(siblings[i]) === adapter.getName(elem)) pos++;
}
}
return func(pos) && next(elem);
};
},
"nth-last-of-type": function(next, rule){
var func = nthCheck(rule);
if(func === falseFunc$3) return func;
if(func === trueFunc$1) return getChildFunc(next);
return function nthLastOfType(elem){
var siblings = adapter.getSiblings(elem);
for(var pos = 0, i = siblings.length - 1; i >= 0; i--){
if(adapter.isTag(siblings[i])){
if(siblings[i] === elem) break;
if(adapter.getName(siblings[i]) === adapter.getName(elem)) pos++;
}
}
return func(pos) && next(elem);
};
},
root: function(next){
return function(elem){
return !adapter.getParent(elem) && next(elem);
};
},
scope: function(next, rule, options, context){
if(!context || context.length === 0){
return filters.root(next);
}
if(context.length === 1){
return function(elem){
return equals(context[0], elem) && next(elem);
};
}
return function(elem){
return context.indexOf(elem) >= 0 && next(elem);
};
},
checkbox: getAttribFunc("type", "checkbox"),
file: getAttribFunc("type", "file"),
password: getAttribFunc("type", "password"),
radio: getAttribFunc("type", "radio"),
reset: getAttribFunc("type", "reset"),
image: getAttribFunc("type", "image"),
submit: getAttribFunc("type", "submit")
};
return filters;
}
function pseudosFactory(adapter){
function getFirstElement(elems){
for(var i = 0; elems && i < elems.length; i++){
if(adapter.isTag(elems[i])) return elems[i];
}
}
var pseudos = {
empty: function(elem){
return !adapter.getChildren(elem).some(function(elem){
return adapter.isTag(elem) || elem.type === "text";
});
},
"first-child": function(elem){
return getFirstElement(adapter.getSiblings(elem)) === elem;
},
"last-child": function(elem){
var siblings = adapter.getSiblings(elem);
for(var i = siblings.length - 1; i >= 0; i--){
if(siblings[i] === elem) return true;
if(adapter.isTag(siblings[i])) break;
}
return false;
},
"first-of-type": function(elem){
var siblings = adapter.getSiblings(elem);
for(var i = 0; i < siblings.length; i++){
if(adapter.isTag(siblings[i])){
if(siblings[i] === elem) return true;
if(adapter.getName(siblings[i]) === adapter.getName(elem)) break;
}
}
return false;
},
"last-of-type": function(elem){
var siblings = adapter.getSiblings(elem);
for(var i = siblings.length - 1; i >= 0; i--){
if(adapter.isTag(siblings[i])){
if(siblings[i] === elem) return true;
if(adapter.getName(siblings[i]) === adapter.getName(elem)) break;
}
}
return false;
},
"only-of-type": function(elem){
var siblings = adapter.getSiblings(elem);
for(var i = 0, j = siblings.length; i < j; i++){
if(adapter.isTag(siblings[i])){
if(siblings[i] === elem) continue;
if(adapter.getName(siblings[i]) === adapter.getName(elem)) return false;
}
}
return true;
},
"only-child": function(elem){
var siblings = adapter.getSiblings(elem);
for(var i = 0; i < siblings.length; i++){
if(adapter.isTag(siblings[i]) && siblings[i] !== elem) return false;
}
return true;
},
link: function(elem){
return adapter.hasAttrib(elem, "href");
},
visited: falseFunc$3,
selected: function(elem){
if(adapter.hasAttrib(elem, "selected")) return true;
else if(adapter.getName(elem) !== "option") return false;
var parent = adapter.getParent(elem);
if(
!parent ||
adapter.getName(parent) !== "select" ||
adapter.hasAttrib(parent, "multiple")
) return false;
var siblings = adapter.getChildren(parent),
sawElem = false;
for(var i = 0; i < siblings.length; i++){
if(adapter.isTag(siblings[i])){
if(siblings[i] === elem){
sawElem = true;
} else if(!sawElem){
return false;
} else if(adapter.hasAttrib(siblings[i], "selected")){
return false;
}
}
}
return sawElem;
},
disabled: function(elem){
return adapter.hasAttrib(elem, "disabled");
},
enabled: function(elem){
return !adapter.hasAttrib(elem, "disabled");
},
checked: function(elem){
return adapter.hasAttrib(elem, "checked") || pseudos.selected(elem);
},
required: function(elem){
return adapter.hasAttrib(elem, "required");
},
optional: function(elem){
return !adapter.hasAttrib(elem, "required");
},
parent: function(elem){
return !pseudos.empty(elem);
},
header: function(elem){
var name = adapter.getName(elem);
return name === "h1" ||
name === "h2" ||
name === "h3" ||
name === "h4" ||
name === "h5" ||
name === "h6";
},
button: function(elem){
var name = adapter.getName(elem);
return name === "button" ||
name === "input" &&
adapter.getAttributeValue(elem, "type") === "button";
},
input: function(elem){
var name = adapter.getName(elem);
return name === "input" ||
name === "textarea" ||
name === "select" ||
name === "button";
},
text: function(elem){
var attr;
return adapter.getName(elem) === "input" && (
!(attr = adapter.getAttributeValue(elem, "type")) ||
attr.toLowerCase() === "text"
);
}
};
return pseudos;
}
function verifyArgs(func, name, subselect){
if(subselect === null){
if(func.length > 1 && name !== "scope"){
throw new Error("pseudo-selector :" + name + " requires an argument");
}
} else {
if(func.length === 1){
throw new Error("pseudo-selector :" + name + " doesn't have any arguments");
}
}
}
var re_CSS3 = /^(?:(?:nth|last|first|only)-(?:child|of-type)|root|empty|(?:en|dis)abled|checked|not)$/;
function factory$1(adapter){
var pseudos = pseudosFactory(adapter);
var filters = filtersFactory(adapter);
return {
compile: function(next, data, options, context){
var name = data.name,
subselect = data.data;
if(options && options.strict && !re_CSS3.test(name)){
throw new Error(":" + name + " isn't part of CSS3");
}
if(typeof filters[name] === "function"){
verifyArgs(filters[name], name, subselect);
return filters[name](next, subselect, options, context);
} else if(typeof pseudos[name] === "function"){
var func = pseudos[name];
verifyArgs(func, name, subselect);
if(next === trueFunc$1) return func;
return function pseudoArgs(elem){
return func(elem, subselect) && next(elem);
};
} else {
throw new Error("unmatched pseudo-class :" + name);
}
},
filters: filters,
pseudos: pseudos
};
}
var pseudos = factory$1;
var compile = compileFactory;
var trueFunc = BaseFuncs.trueFunc;
var falseFunc$1 = BaseFuncs.falseFunc;
function compileFactory(adapter){
var Pseudos = pseudos(adapter),
filters = Pseudos.filters,
Rules = general(adapter, Pseudos);
function compile(selector, options, context){
var next = compileUnsafe(selector, options, context);
return wrap(next);
}
function wrap(next){
return function base(elem){
return adapter.isTag(elem) && next(elem);
};
}
function compileUnsafe(selector, options, context){
var token = cssWhat(selector, options);
return compileToken(token, options, context);
}
function includesScopePseudo(t){
return t.type === "pseudo" && (
t.name === "scope" || (
Array.isArray(t.data) &&
t.data.some(function(data){
return data.some(includesScopePseudo);
})
)
);
}
var DESCENDANT_TOKEN = {type: "descendant"},
FLEXIBLE_DESCENDANT_TOKEN = {type: "_flexibleDescendant"},
SCOPE_TOKEN = {type: "pseudo", name: "scope"},
PLACEHOLDER_ELEMENT = {};
function absolutize(token, context){
var hasContext = !!context && !!context.length && context.every(function(e){
return e === PLACEHOLDER_ELEMENT || !!adapter.getParent(e);
});
token.forEach(function(t){
if(t.length > 0 && isTraversal(t[0]) && t[0].type !== "descendant"){
} else if(hasContext && !includesScopePseudo(t)){
t.unshift(DESCENDANT_TOKEN);
} else {
return;
}
t.unshift(SCOPE_TOKEN);
});
}
function compileToken(token, options, context){
token = token.filter(function(t){ return t.length > 0; });
token.forEach(sort);
var isArrayContext = Array.isArray(context);
context = (options && options.context) || context;
if(context && !isArrayContext) context = [context];
absolutize(token, context);
var shouldTestNextSiblings = false;
var query = token
.map(function(rules){
if(rules[0] && rules[1] && rules[0].name === "scope"){
var ruleType = rules[1].type;
if(isArrayContext && ruleType === "descendant") rules[1] = FLEXIBLE_DESCENDANT_TOKEN;
else if(ruleType === "adjacent" || ruleType === "sibling") shouldTestNextSiblings = true;
}
return compileRules(rules, options, context);
})
.reduce(reduceRules, falseFunc$1);
query.shouldTestNextSiblings = shouldTestNextSiblings;
return query;
}
function isTraversal(t){
return procedure$2[t.type] < 0;
}
function compileRules(rules, options, context){
return rules.reduce(function(func, rule){
if(func === falseFunc$1) return func;
return Rules[rule.type](func, rule, options, context);
}, options && options.rootFunc || trueFunc);
}
function reduceRules(a, b){
if(b === falseFunc$1 || a === trueFunc){
return a;
}
if(a === falseFunc$1 || b === trueFunc){
return b;
}
return function combine(elem){
return a(elem) || b(elem);
};
}
function containsTraversal(t){
return t.some(isTraversal);
}
filters.not = function(next, token, options, context){
var opts = {
xmlMode: !!(options && options.xmlMode),
strict: !!(options && options.strict)
};
if(opts.strict){
if(token.length > 1 || token.some(containsTraversal)){
throw new Error("complex selectors in :not aren't allowed in strict mode");
}
}
var func = compileToken(token, opts, context);
if(func === falseFunc$1) return next;
if(func === trueFunc) return falseFunc$1;
return function(elem){
return !func(elem) && next(elem);
};
};
filters.has = function(next, token, options){
var opts = {
xmlMode: !!(options && options.xmlMode),
strict: !!(options && options.strict)
};
var context = token.some(containsTraversal) ? [PLACEHOLDER_ELEMENT] : null;
var func = compileToken(token, opts, context);
if(func === falseFunc$1) return falseFunc$1;
if(func === trueFunc){
return function(elem){
return adapter.getChildren(elem).some(adapter.isTag) && next(elem);
};
}
func = wrap(func);
if(context){
return function has(elem){
return next(elem) && (
(context[0] = elem), adapter.existsOne(func, adapter.getChildren(elem))
);
};
}
return function has(elem){
return next(elem) && adapter.existsOne(func, adapter.getChildren(elem));
};
};
filters.matches = function(next, token, options, context){
var opts = {
xmlMode: !!(options && options.xmlMode),
strict: !!(options && options.strict),
rootFunc: next
};
return compileToken(token, opts, context);
};
compile.compileToken = compileToken;
compile.compileUnsafe = compileUnsafe;
compile.Pseudos = Pseudos;
return compile;
}
var index = CSSselect$1;
var falseFunc = BaseFuncs.falseFunc;
var defaultCompile = compile(domutils);
function adapterCompile(adapter){
if(!adapter.__compile__){
adapter.__compile__ = compile(adapter);
}
return adapter.__compile__
}
function getSelectorFunc(searchFunc){
return function select(query, elems, options){
options = options || {};
options.adapter = options.adapter || domutils;
var compile$$1 = adapterCompile(options.adapter);
if(typeof query !== "function") query = compile$$1.compileUnsafe(query, options, elems);
if(query.shouldTestNextSiblings) elems = appendNextSiblings((options && options.context) || elems, options.adapter);
if(!Array.isArray(elems)) elems = options.adapter.getChildren(elems);
else elems = options.adapter.removeSubsets(elems);
return searchFunc(query, elems, options);
};
}
function getNextSiblings(elem, adapter){
var siblings = adapter.getSiblings(elem);
if(!Array.isArray(siblings)) return [];
siblings = siblings.slice(0);
while(siblings.shift() !== elem);
return siblings;
}
function appendNextSiblings(elems, adapter){
if(!Array.isArray(elems)) elems = [elems];
var newElems = elems.slice(0);
for(var i = 0, len = elems.length; i < len; i++){
var nextSiblings = getNextSiblings(newElems[i], adapter);
newElems.push.apply(newElems, nextSiblings);
}
return newElems;
}
var selectAll = getSelectorFunc(function selectAll(query, elems, options){
return (query === falseFunc || !elems || elems.length === 0) ? [] : options.adapter.findAll(query, elems);
});
var selectOne = getSelectorFunc(function selectOne(query, elems, options){
return (query === falseFunc || !elems || elems.length === 0) ? null : options.adapter.findOne(query, elems);
});
function is(elem, query, options){
options = options || {};
options.adapter = options.adapter || domutils;
var compile$$1 = adapterCompile(options.adapter);
return (typeof query === "function" ? query : compile$$1(query, options))(elem);
}
function CSSselect$1(query, elems, options){
return selectAll(query, elems, options);
}
CSSselect$1.compile = defaultCompile;
CSSselect$1.filters = defaultCompile.Pseudos.filters;
CSSselect$1.pseudos = defaultCompile.Pseudos.pseudos;
CSSselect$1.selectAll = selectAll;
CSSselect$1.selectOne = selectOne;
CSSselect$1.is = is;
CSSselect$1.parse = defaultCompile;
CSSselect$1.iterate = selectAll;
CSSselect$1._compileUnsafe = defaultCompile.compileUnsafe;
CSSselect$1._compileToken = defaultCompile.compileToken;
export default index;