diff options
Diffstat (limited to 'node_modules/css-select/lib')
-rw-r--r-- | node_modules/css-select/lib/attributes.js | 181 | ||||
-rw-r--r-- | node_modules/css-select/lib/compile.js | 192 | ||||
-rw-r--r-- | node_modules/css-select/lib/general.js | 89 | ||||
-rw-r--r-- | node_modules/css-select/lib/procedure.json | 11 | ||||
-rw-r--r-- | node_modules/css-select/lib/pseudos.js | 393 | ||||
-rw-r--r-- | node_modules/css-select/lib/sort.js | 80 |
6 files changed, 946 insertions, 0 deletions
diff --git a/node_modules/css-select/lib/attributes.js b/node_modules/css-select/lib/attributes.js new file mode 100644 index 000000000..a8689c01c --- /dev/null +++ b/node_modules/css-select/lib/attributes.js @@ -0,0 +1,181 @@ +var DomUtils = require("domutils"), + hasAttrib = DomUtils.hasAttrib, + getAttributeValue = DomUtils.getAttributeValue, + falseFunc = require("boolbase").falseFunc; + +//https://github.com/slevithan/XRegExp/blob/master/src/xregexp.js#L469 +var reChars = /[-[\]{}()*+?.,\\^$|#\s]/g; + +/* + attribute selectors +*/ + +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 = getAttributeValue(elem, name); + return attr != null && attr.toLowerCase() === value && next(elem); + }; + } + + return function equals(elem){ + return 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 = 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 = 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(/\s/.test(value)){ + return falseFunc; + } + + value = value.replace(reChars, "\\$&"); + + var pattern = "(?:^|\\s)" + value + "(?:$|\\s)", + flags = data.ignoreCase ? "i" : "", + regex = new RegExp(pattern, flags); + + return function element(elem){ + var attr = getAttributeValue(elem, name); + return attr != null && regex.test(attr) && next(elem); + }; + }, + exists: function(next, data){ + var name = data.name; + return function exists(elem){ + return hasAttrib(elem, name) && next(elem); + }; + }, + start: function(next, data){ + var name = data.name, + value = data.value, + len = value.length; + + if(len === 0){ + return falseFunc; + } + + if(data.ignoreCase){ + value = value.toLowerCase(); + + return function startIC(elem){ + var attr = getAttributeValue(elem, name); + return attr != null && attr.substr(0, len).toLowerCase() === value && next(elem); + }; + } + + return function start(elem){ + var attr = 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; + } + + if(data.ignoreCase){ + value = value.toLowerCase(); + + return function endIC(elem){ + var attr = getAttributeValue(elem, name); + return attr != null && attr.substr(len).toLowerCase() === value && next(elem); + }; + } + + return function end(elem){ + var attr = 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; + } + + if(data.ignoreCase){ + var regex = new RegExp(value.replace(reChars, "\\$&"), "i"); + + return function anyIC(elem){ + var attr = getAttributeValue(elem, name); + return attr != null && regex.test(attr) && next(elem); + }; + } + + return function any(elem){ + var attr = 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 !!getAttributeValue(elem, name) && next(elem); + }; + } else if(data.ignoreCase){ + value = value.toLowerCase(); + + return function notIC(elem){ + var attr = getAttributeValue(elem, name); + return attr != null && attr.toLowerCase() !== value && next(elem); + }; + } + + return function not(elem){ + return getAttributeValue(elem, name) !== value && next(elem); + }; + } +}; + +module.exports = { + compile: function(next, data, options){ + if(options && options.strict && ( + data.ignoreCase || data.action === "not" + )) throw SyntaxError("Unsupported attribute selector"); + return attributeRules[data.action](next, data); + }, + rules: attributeRules +}; diff --git a/node_modules/css-select/lib/compile.js b/node_modules/css-select/lib/compile.js new file mode 100644 index 000000000..91ac5925e --- /dev/null +++ b/node_modules/css-select/lib/compile.js @@ -0,0 +1,192 @@ +/* + compiles a selector to an executable function +*/ + +module.exports = compile; +module.exports.compileUnsafe = compileUnsafe; +module.exports.compileToken = compileToken; + +var parse = require("css-what"), + DomUtils = require("domutils"), + isTag = DomUtils.isTag, + Rules = require("./general.js"), + sortRules = require("./sort.js"), + BaseFuncs = require("boolbase"), + trueFunc = BaseFuncs.trueFunc, + falseFunc = BaseFuncs.falseFunc, + procedure = require("./procedure.json"); + +function compile(selector, options, context){ + var next = compileUnsafe(selector, options, context); + return wrap(next); +} + +function wrap(next){ + return function base(elem){ + return isTag(elem) && next(elem); + }; +} + +function compileUnsafe(selector, options, context){ + var token = parse(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"}, + SCOPE_TOKEN = {type: "pseudo", name: "scope"}, + PLACEHOLDER_ELEMENT = {}, + getParent = DomUtils.getParent; + +//CSS 4 Spec (Draft): 3.3.1. Absolutizing a Scope-relative Selector +//http://www.w3.org/TR/selectors4/#absolutizing +function absolutize(token, context){ + //TODO better check if context is document + var hasContext = !!context && !!context.length && context.every(function(e){ + return e === PLACEHOLDER_ELEMENT || !!getParent(e); + }); + + + token.forEach(function(t){ + if(t.length > 0 && isTraversal(t[0]) && t[0].type !== "descendant"){ + //don't return in else branch + } 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(sortRules); + + var isArrayContext = Array.isArray(context); + + context = (options && options.context) || context; + + if(context && !isArrayContext) context = [context]; + + absolutize(token, context); + + return token + .map(function(rules){ return compileRules(rules, options, context, isArrayContext); }) + .reduce(reduceRules, falseFunc); +} + +function isTraversal(t){ + return procedure[t.type] < 0; +} + +function compileRules(rules, options, context, isArrayContext){ + var acceptSelf = (isArrayContext && rules[0].name === "scope" && rules[1].type === "descendant"); + return rules.reduce(function(func, rule, index){ + if(func === falseFunc) return func; + return Rules[rule.type](func, rule, options, context, acceptSelf && index === 1); + }, options && options.rootFunc || trueFunc); +} + +function reduceRules(a, b){ + if(b === falseFunc || a === trueFunc){ + return a; + } + if(a === falseFunc || b === trueFunc){ + return b; + } + + return function combine(elem){ + return a(elem) || b(elem); + }; +} + +//:not, :has and :matches have to compile selectors +//doing this in lib/pseudos.js would lead to circular dependencies, +//so we add them here + +var Pseudos = require("./pseudos.js"), + filters = Pseudos.filters, + existsOne = DomUtils.existsOne, + isTag = DomUtils.isTag, + getChildren = DomUtils.getChildren; + + +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 SyntaxError("complex selectors in :not aren't allowed in strict mode"); + } + } + + var func = compileToken(token, opts, context); + + if(func === falseFunc) return next; + if(func === trueFunc) return falseFunc; + + return function(elem){ + return !func(elem) && next(elem); + }; +}; + +filters.has = function(next, token, options){ + var opts = { + xmlMode: !!(options && options.xmlMode), + strict: !!(options && options.strict) + }; + + //FIXME: Uses an array as a pointer to the current element (side effects) + var context = token.some(containsTraversal) ? [PLACEHOLDER_ELEMENT] : null; + + var func = compileToken(token, opts, context); + + if(func === falseFunc) return falseFunc; + if(func === trueFunc) return function(elem){ + return getChildren(elem).some(isTag) && next(elem); + }; + + func = wrap(func); + + if(context){ + return function has(elem){ + return next(elem) && ( + (context[0] = elem), existsOne(func, getChildren(elem)) + ); + }; + } + + return function has(elem){ + return next(elem) && existsOne(func, 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); +}; diff --git a/node_modules/css-select/lib/general.js b/node_modules/css-select/lib/general.js new file mode 100644 index 000000000..fbc960fe9 --- /dev/null +++ b/node_modules/css-select/lib/general.js @@ -0,0 +1,89 @@ +var DomUtils = require("domutils"), + isTag = DomUtils.isTag, + getParent = DomUtils.getParent, + getChildren = DomUtils.getChildren, + getSiblings = DomUtils.getSiblings, + getName = DomUtils.getName; + +/* + all available rules +*/ +module.exports = { + __proto__: null, + + attribute: require("./attributes.js").compile, + pseudo: require("./pseudos.js").compile, + + //tags + tag: function(next, data){ + var name = data.name; + return function tag(elem){ + return getName(elem) === name && next(elem); + }; + }, + + //traversal + descendant: function(next, rule, options, context, acceptSelf){ + return function descendant(elem){ + + if (acceptSelf && next(elem)) return true; + + var found = false; + + while(!found && (elem = getParent(elem))){ + found = next(elem); + } + + return found; + }; + }, + parent: function(next, data, options){ + if(options && options.strict) throw SyntaxError("Parent selector isn't part of CSS3"); + + return function parent(elem){ + return getChildren(elem).some(test); + }; + + function test(elem){ + return isTag(elem) && next(elem); + } + }, + child: function(next){ + return function child(elem){ + var parent = getParent(elem); + return !!parent && next(parent); + }; + }, + sibling: function(next){ + return function sibling(elem){ + var siblings = getSiblings(elem); + + for(var i = 0; i < siblings.length; i++){ + if(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 = getSiblings(elem), + lastElement; + + for(var i = 0; i < siblings.length; i++){ + if(isTag(siblings[i])){ + if(siblings[i] === elem) break; + lastElement = siblings[i]; + } + } + + return !!lastElement && next(lastElement); + }; + }, + universal: function(next){ + return next; + } +};
\ No newline at end of file diff --git a/node_modules/css-select/lib/procedure.json b/node_modules/css-select/lib/procedure.json new file mode 100644 index 000000000..e836de117 --- /dev/null +++ b/node_modules/css-select/lib/procedure.json @@ -0,0 +1,11 @@ +{ + "universal": 50, + "tag": 30, + "attribute": 1, + "pseudo": 0, + "descendant": -1, + "child": -1, + "parent": -1, + "sibling": -1, + "adjacent": -1 +} diff --git a/node_modules/css-select/lib/pseudos.js b/node_modules/css-select/lib/pseudos.js new file mode 100644 index 000000000..f6774ecfc --- /dev/null +++ b/node_modules/css-select/lib/pseudos.js @@ -0,0 +1,393 @@ +/* + pseudo selectors + + --- + + they are available in two forms: + * filters called when the selector + is compiled and return a function + that needs to return next() + * pseudos get called on execution + they need to return a boolean +*/ + +var DomUtils = require("domutils"), + isTag = DomUtils.isTag, + getText = DomUtils.getText, + getParent = DomUtils.getParent, + getChildren = DomUtils.getChildren, + getSiblings = DomUtils.getSiblings, + hasAttrib = DomUtils.hasAttrib, + getName = DomUtils.getName, + getAttribute= DomUtils.getAttributeValue, + getNCheck = require("nth-check"), + checkAttrib = require("./attributes.js").rules.equals, + BaseFuncs = require("boolbase"), + trueFunc = BaseFuncs.trueFunc, + falseFunc = BaseFuncs.falseFunc; + +//helper methods +function getFirstElement(elems){ + for(var i = 0; elems && i < elems.length; i++){ + if(isTag(elems[i])) return elems[i]; + } +} + +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 !!getParent(elem) && next(elem); + }; +} + +var filters = { + contains: function(next, text){ + return function contains(elem){ + return next(elem) && getText(elem).indexOf(text) >= 0; + }; + }, + icontains: function(next, text){ + var itext = text.toLowerCase(); + return function icontains(elem){ + return next(elem) && + getText(elem).toLowerCase().indexOf(itext) >= 0; + }; + }, + + //location specific methods + "nth-child": function(next, rule){ + var func = getNCheck(rule); + + if(func === falseFunc) return func; + if(func === trueFunc) return getChildFunc(next); + + return function nthChild(elem){ + var siblings = getSiblings(elem); + + for(var i = 0, pos = 0; i < siblings.length; i++){ + if(isTag(siblings[i])){ + if(siblings[i] === elem) break; + else pos++; + } + } + + return func(pos) && next(elem); + }; + }, + "nth-last-child": function(next, rule){ + var func = getNCheck(rule); + + if(func === falseFunc) return func; + if(func === trueFunc) return getChildFunc(next); + + return function nthLastChild(elem){ + var siblings = getSiblings(elem); + + for(var pos = 0, i = siblings.length - 1; i >= 0; i--){ + if(isTag(siblings[i])){ + if(siblings[i] === elem) break; + else pos++; + } + } + + return func(pos) && next(elem); + }; + }, + "nth-of-type": function(next, rule){ + var func = getNCheck(rule); + + if(func === falseFunc) return func; + if(func === trueFunc) return getChildFunc(next); + + return function nthOfType(elem){ + var siblings = getSiblings(elem); + + for(var pos = 0, i = 0; i < siblings.length; i++){ + if(isTag(siblings[i])){ + if(siblings[i] === elem) break; + if(getName(siblings[i]) === getName(elem)) pos++; + } + } + + return func(pos) && next(elem); + }; + }, + "nth-last-of-type": function(next, rule){ + var func = getNCheck(rule); + + if(func === falseFunc) return func; + if(func === trueFunc) return getChildFunc(next); + + return function nthLastOfType(elem){ + var siblings = getSiblings(elem); + + for(var pos = 0, i = siblings.length - 1; i >= 0; i--){ + if(isTag(siblings[i])){ + if(siblings[i] === elem) break; + if(getName(siblings[i]) === getName(elem)) pos++; + } + } + + return func(pos) && next(elem); + }; + }, + + //TODO determine the actual root element + root: function(next){ + return function(elem){ + return !getParent(elem) && next(elem); + }; + }, + + scope: function(next, rule, options, context){ + if(!context || context.length === 0){ + //equivalent to :root + return filters.root(next); + } + + if(context.length === 1){ + //NOTE: can't be unpacked, as :has uses this for side-effects + return function(elem){ + return context[0] === elem && next(elem); + }; + } + + return function(elem){ + return context.indexOf(elem) >= 0 && next(elem); + }; + }, + + //jQuery extensions (others follow as pseudos) + 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") +}; + +//while filters are precompiled, pseudos get called when they are needed +var pseudos = { + empty: function(elem){ + return !getChildren(elem).some(function(elem){ + return isTag(elem) || elem.type === "text"; + }); + }, + + "first-child": function(elem){ + return getFirstElement(getSiblings(elem)) === elem; + }, + "last-child": function(elem){ + var siblings = getSiblings(elem); + + for(var i = siblings.length - 1; i >= 0; i--){ + if(siblings[i] === elem) return true; + if(isTag(siblings[i])) break; + } + + return false; + }, + "first-of-type": function(elem){ + var siblings = getSiblings(elem); + + for(var i = 0; i < siblings.length; i++){ + if(isTag(siblings[i])){ + if(siblings[i] === elem) return true; + if(getName(siblings[i]) === getName(elem)) break; + } + } + + return false; + }, + "last-of-type": function(elem){ + var siblings = getSiblings(elem); + + for(var i = siblings.length-1; i >= 0; i--){ + if(isTag(siblings[i])){ + if(siblings[i] === elem) return true; + if(getName(siblings[i]) === getName(elem)) break; + } + } + + return false; + }, + "only-of-type": function(elem){ + var siblings = getSiblings(elem); + + for(var i = 0, j = siblings.length; i < j; i++){ + if(isTag(siblings[i])){ + if(siblings[i] === elem) continue; + if(getName(siblings[i]) === getName(elem)) return false; + } + } + + return true; + }, + "only-child": function(elem){ + var siblings = getSiblings(elem); + + for(var i = 0; i < siblings.length; i++){ + if(isTag(siblings[i]) && siblings[i] !== elem) return false; + } + + return true; + }, + + //:matches(a, area, link)[href] + link: function(elem){ + return hasAttrib(elem, "href"); + }, + visited: falseFunc, //seems to be a valid implementation + //TODO: :any-link once the name is finalized (as an alias of :link) + + //forms + //to consider: :target + + //:matches([selected], select:not([multiple]):not(> option[selected]) > option:first-of-type) + selected: function(elem){ + if(hasAttrib(elem, "selected")) return true; + else if(getName(elem) !== "option") return false; + + //the first <option> in a <select> is also selected + var parent = getParent(elem); + + if( + !parent || + getName(parent) !== "select" || + hasAttrib(parent, "multiple") + ) return false; + + var siblings = getChildren(parent), + sawElem = false; + + for(var i = 0; i < siblings.length; i++){ + if(isTag(siblings[i])){ + if(siblings[i] === elem){ + sawElem = true; + } else if(!sawElem){ + return false; + } else if(hasAttrib(siblings[i], "selected")){ + return false; + } + } + } + + return sawElem; + }, + //https://html.spec.whatwg.org/multipage/scripting.html#disabled-elements + //:matches( + // :matches(button, input, select, textarea, menuitem, optgroup, option)[disabled], + // optgroup[disabled] > option), + // fieldset[disabled] * //TODO not child of first <legend> + //) + disabled: function(elem){ + return hasAttrib(elem, "disabled"); + }, + enabled: function(elem){ + return !hasAttrib(elem, "disabled"); + }, + //:matches(:matches(:radio, :checkbox)[checked], :selected) (TODO menuitem) + checked: function(elem){ + return hasAttrib(elem, "checked") || pseudos.selected(elem); + }, + //:matches(input, select, textarea)[required] + required: function(elem){ + return hasAttrib(elem, "required"); + }, + //:matches(input, select, textarea):not([required]) + optional: function(elem){ + return !hasAttrib(elem, "required"); + }, + + //jQuery extensions + + //:not(:empty) + parent: function(elem){ + return !pseudos.empty(elem); + }, + //:matches(h1, h2, h3, h4, h5, h6) + header: function(elem){ + var name = getName(elem); + return name === "h1" || + name === "h2" || + name === "h3" || + name === "h4" || + name === "h5" || + name === "h6"; + }, + + //:matches(button, input[type=button]) + button: function(elem){ + var name = getName(elem); + return name === "button" || + name === "input" && + getAttribute(elem, "type") === "button"; + }, + //:matches(input, textarea, select, button) + input: function(elem){ + var name = getName(elem); + return name === "input" || + name === "textarea" || + name === "select" || + name === "button"; + }, + //input:matches(:not([type!='']), [type='text' i]) + text: function(elem){ + var attr; + return getName(elem) === "input" && ( + !(attr = getAttribute(elem, "type")) || + attr.toLowerCase() === "text" + ); + } +}; + +function verifyArgs(func, name, subselect){ + if(subselect === null){ + if(func.length > 1 && name !== "scope"){ + throw new SyntaxError("pseudo-selector :" + name + " requires an argument"); + } + } else { + if(func.length === 1){ + throw new SyntaxError("pseudo-selector :" + name + " doesn't have any arguments"); + } + } +} + +//FIXME this feels hacky +var re_CSS3 = /^(?:(?:nth|last|first|only)-(?:child|of-type)|root|empty|(?:en|dis)abled|checked|not)$/; + +module.exports = { + compile: function(next, data, options, context){ + var name = data.name, + subselect = data.data; + + if(options && options.strict && !re_CSS3.test(name)){ + throw SyntaxError(":" + 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) return func; + + return function pseudoArgs(elem){ + return func(elem, subselect) && next(elem); + }; + } else { + throw new SyntaxError("unmatched pseudo-class :" + name); + } + }, + filters: filters, + pseudos: pseudos +}; diff --git a/node_modules/css-select/lib/sort.js b/node_modules/css-select/lib/sort.js new file mode 100644 index 000000000..835332459 --- /dev/null +++ b/node_modules/css-select/lib/sort.js @@ -0,0 +1,80 @@ +module.exports = sortByProcedure; + +/* + sort the parts of the passed selector, + as there is potential for optimization + (some types of selectors are faster than others) +*/ + +var procedure = require("./procedure.json"); + +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[token.type]; + + if(proc === procedure.attribute){ + proc = attributes[token.action]; + + if(proc === attributes.equals && token.name === "id"){ + //prefer ID selectors (eg. #ID) + proc = 9; + } + + if(token.ignoreCase){ + //ignoreCase adds some overhead, prefer "normal" token + //this is a binary operation, to ensure it's still an int + proc >>= 1; + } + } else if(proc === procedure.pseudo){ + if(!token.data){ + proc = 3; + } else if(token.name === "has" || token.name === "contains"){ + proc = 0; //expensive in any case + } else if(token.name === "matches" || token.name === "not"){ + proc = 0; + for(var i = 0; i < token.data.length; i++){ + //TODO better handling of complex selectors + if(token.data[i].length !== 1) continue; + var cur = getProcedure(token.data[i][0]); + //avoid executing :has or :contains + 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; +} |