Skip to content
Snippets Groups Projects
Commit 1b5fa85f authored by Yadd's avatar Yadd
Browse files

New upstream version 2.0.2

parent 4edd5970
No related branches found
No related tags found
No related merge requests found
......@@ -3,16 +3,9 @@
"env": {
"node": true
},
"globals": {
"beforeEach": true,
"describe": true,
"it": true
},
"rules": {
"no-console": 0,
"new-cap": 0,
"space-before-blocks": [2, "never"],
"space-in-parens": [2, "never"],
"eqeqeq": [2, "allow-null"],
"no-extend-native": 2,
"no-use-before-define": [
......@@ -23,70 +16,16 @@
}
],
"no-caller": 2,
"no-irregular-whitespace": 2,
"quotes": [
2,
"double"
],
"no-undef": 2,
"no-unused-vars": 2,
"no-proto": 2,
"curly": [
2,
"multi-line"
],
"no-mixed-spaces-and-tabs": [
2,
"smart-tabs"
],
"space-infix-ops": 2,
"keyword-spacing": [
2,
{
"overrides": {
"if": {
"after": false
},
"catch": {
"after": false
},
"for": {
"after": false
},
"while": {
"after": false
}
}
}
],
"comma-style": [
2,
"last"
],
"dot-notation": 2,
"wrap-iife": 2,
"no-empty": 2,
"space-unary-ops": [
2,
{
"words": false,
"nonwords": false
}
],
"no-with": 2,
"no-multi-str": 2,
"no-trailing-spaces": 2,
"indent": [
2,
"tab",
{
"SwitchCase": 1
}
],
"linebreak-style": [
2,
"unix"
],
"consistent-this": [
2,
"_this"
......
language: node_js
node_js:
- stable
- 6
- 4
script: npm run coveralls
......@@ -57,9 +57,11 @@ _//TODO: More in-depth description. Implementation details. Build a spaceship._
## API
```js
var CSSselect = require("css-select");
const CSSselect = require("css-select");
```
__Note:__ css-select throws errors when invalid selectors are passed to it, contrary to the behavior in browsers, which swallow them. This is done to aid with writing css selectors, but can be unexpected when processing arbitrary strings.
#### `CSSselect(query, elems, options)`
Queries `elems`, returns an array containing all matches.
......@@ -204,4 +206,4 @@ __*__: Not part of CSS3
---
License: BSD-like
License: BSD-2-Clause
theme: jekyll-theme-cayman
\ No newline at end of file
export = CSSselect;
/**
* Alias for CSSselect.selectAll(query, elems, options).
* @see [CSSselect.compile] for supported selector queries.
*/
declare function CSSselect<Node, ElementNode extends Node>(
query: CSSselect.Query,
elems: Array<ElementNode> | ElementNode,
options?: CSSselect.Options<Node, ElementNode>
): Array<ElementNode>;
declare namespace CSSselect {
type Predicate<Value> = (v: Value) => boolean;
interface Adapter<Node, ElementNode extends Node> {
/**
* is the node a tag?
*/
isTag(node: Node): node is ElementNode;
/**
* Does at least one of passed element nodes pass the test predicate?
*/
existsOne(test: Predicate<ElementNode>, elems: Array<ElementNode>): boolean;
/**
* get the attribute value.
*/
getAttributeValue(elem: ElementNode, name: string): string;
/**
* get the node's children
*/
getChildren(node: Node): Array<Node>;
/**
* get the name of the tag
*/
getName(elem: ElementNode): string;
/**
* get the parent of the node
*/
getParent(node: Node): Node;
/*
Get the siblings of the node. Note that unlike jQuery's `siblings` method,
this is expected to include the current node as well
*/
getSiblings(node: Node): Array<Node>;
/*
* Get the text content of the node, and its children if it has any.
*/
getText(node: Node): string;
/**
* Does the element have the named attribute?
*/
hasAttrib(elem: ElementNode, name: string): boolean;
/**
* takes an array of nodes, and removes any duplicates, as well as any
* nodes whose ancestors are also in the array.
*/
removeSubsets(nodes: Array<Node>): Array<Node>;
/**
* finds all of the element nodes in the array that match the test predicate,
* as well as any of their children that match it.
*/
findAll(test: Predicate<ElementNode>, nodes: Array<Node>): Array<ElementNode>;
/**
* finds the first node in the array that matches the test predicate, or one
* of its children.
*/
findOne(test: Predicate<ElementNode>, elems: Array<ElementNode>): ElementNode | undefined,
/**
The adapter can also optionally include an equals method, if your DOM
structure needs a custom equality test to compare two objects which refer
to the same underlying node. If not provided, `css-select` will fall back to
`a === b`.
*/
equals?: (a: Node, b: Node) => boolean;
}
// TODO default types to the domutil/httpparser2 types
interface Options<Node, ElementNode extends Node> {
/**
* When enabled, tag names will be case-sensitive. Default: false.
*/
xmlMode?: boolean;
/**
* Limits the module to only use CSS3 selectors. Default: false.
*/
strict?: boolean;
/**
* The last function in the stack, will be called with the last element
* that's looked at. Should return true.
*/
rootFunc?: (element: ElementNode) => true;
/**
* The adapter to use when interacting with the backing DOM structure. By
* default it uses domutils.
*/
adapter?: Adapter<Node, ElementNode>;
}
type CompiledQuery = (node: any) => boolean;
type Query = string | CompiledQuery;
/**
* Compiles the query, returns a function.
*
* Supported simple selectors:
* * Universal (*)
* * Tag (<tagname>)
* * Attribute ([attr=foo]), with supported comparisons:
* * [attr] (existential)
* * =
* * ~=
* * |=
* * *=
* * ^=
* * $=
* * !=
* * Can be case insensitive (E.g. [attr=foo i])
* * Pseudos:
* * :not
* * :root
* * :empty
* * :[first|last]-child[-of-type]
* * :only-of-type, :only-child
* * :nth-[last-]child[-of-type]
* * :link, :visited (the latter doesn't match any elements)
* * :checked
* * :enabled, :disabled
* * :required, :optional
* * Nonstandard Pseudos (available when strict mode is not enabled):
* * `:contains`
* * `:icontains` (case-insensitive version of :contains)
* * `:has`
* * `:parent`
* * `:selected`
* * `:header, :button, :input, :text, :checkbox, :file, :password, :reset, :radio etc.
* * :matches
*
* Supported Combinators:
*
* * Descendant (` `)
* * Child (`>`)
* * Parent (`<`) (when strict mode is not enabled)
* * Sibling (`~`)
* * Adjacent (`+`)
*/
function compile(query: string): CompiledQuery;
/**
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elems Elements to query. If it is an element, its children will be queried..
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see CSSselect.compile for supported selector queries.
* @returns All matching elements.
*/
function selectAll<Node, ElementNode extends Node>(
query: Query,
elems: Array<ElementNode> | ElementNode,
options?: Options<Node, ElementNode>
): Array<ElementNode>;
/**
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elems Elements to query. If it is an element, its children will be queried..
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see CSSselect.compile for supported selector queries.
* @returns the first match, or null if there was no match.
*/
function selectOne<Node, ElementNode extends Node>(
query: Query,
elems: Array<ElementNode> | ElementNode,
options?: Options<Node, ElementNode>
): ElementNode | null;
/**
* Tests whether or not an element is matched by query.
*
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elem The element to test if it matches the query.
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see CSSselect.compile for supported selector queries.
* @returns
*/
function is<Node, ElementNode extends Node>(
elem: ElementNode,
query: Query,
options?: Options<Node, ElementNode>
): boolean;
}
\ No newline at end of file
......@@ -2,74 +2,83 @@
module.exports = CSSselect;
var DomUtils = require("domutils"),
falseFunc = require("boolbase").falseFunc,
compileFactory = require("./lib/compile.js"),
defaultCompile = compileFactory(DomUtils);
var DomUtils = require("domutils");
var falseFunc = require("boolbase").falseFunc;
var compileRaw = require("./lib/compile.js");
function adapterCompile(adapter){
return adapter === DomUtils ? defaultCompile : compileFactory(adapter);
function wrapCompile(func) {
return function addAdapter(selector, options, context) {
options = options || {};
options.adapter = options.adapter || DomUtils;
return func(selector, options, context);
};
}
function getSelectorFunc(searchFunc){
return function select(query, elems, options){
options = options || {}
options.adapter = options.adapter || DomUtils;
var compile = adapterCompile(options.adapter);
if(typeof query !== "function") query = compile.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);
};
var compile = wrapCompile(compileRaw);
var compileUnsafe = wrapCompile(compileRaw.compileUnsafe);
function getSelectorFunc(searchFunc) {
return function select(query, elems, options) {
options = options || {};
options.adapter = options.adapter || DomUtils;
if (typeof query !== "function") {
query = 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 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){
// Order matters because jQuery seems to check the children before the siblings
if(!Array.isArray(elems)) elems = [elems];
var newElems = elems.slice(0);
function appendNextSiblings(elems, adapter) {
// Order matters because jQuery seems to check the children before the siblings
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;
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 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);
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 = adapterCompile(options.adapter);
return (typeof query === "function" ? query : compile(query, options))(elem);
function is(elem, query, options) {
options = options || {};
options.adapter = options.adapter || DomUtils;
return (typeof query === "function" ? query : compile(query, options))(elem);
}
/*
the exported interface
*/
function CSSselect(query, elems, options){
return selectAll(query, elems, options);
function CSSselect(query, elems, options) {
return selectAll(query, elems, options);
}
CSSselect.compile = defaultCompile;
CSSselect.filters = defaultCompile.Pseudos.filters;
CSSselect.pseudos = defaultCompile.Pseudos.pseudos;
CSSselect.compile = compile;
CSSselect.filters = compileRaw.Pseudos.filters;
CSSselect.pseudos = compileRaw.Pseudos.pseudos;
CSSselect.selectAll = selectAll;
CSSselect.selectOne = selectOne;
......@@ -77,9 +86,9 @@ CSSselect.selectOne = selectOne;
CSSselect.is = is;
//legacy methods (might be removed)
CSSselect.parse = defaultCompile;
CSSselect.parse = compile;
CSSselect.iterate = selectAll;
//hooks
CSSselect._compileUnsafe = defaultCompile.compileUnsafe;
CSSselect._compileToken = defaultCompile.compileToken;
CSSselect._compileUnsafe = compileUnsafe;
CSSselect._compileToken = compileRaw.compileToken;
......@@ -3,179 +3,188 @@ var falseFunc = require("boolbase").falseFunc;
//https://github.com/slevithan/XRegExp/blob/master/src/xregexp.js#L469
var reChars = /[-[\]{}()*+?.,\\^$|#\s]/g;
function factory(adapter){
/*
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 = 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(/\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 = 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;
}
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;
}
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;
}
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
};
}
module.exports = factory;
/*
attribute selectors
*/
var attributeRules = {
__proto__: null,
equals: function(next, data, options) {
var name = data.name;
var value = data.value;
var adapter = options.adapter;
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, options) {
var name = data.name;
var value = data.value;
var len = value.length;
var adapter = options.adapter;
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, options) {
var name = data.name;
var value = data.value;
var adapter = options.adapter;
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 = adapter.getAttributeValue(elem, name);
return attr != null && regex.test(attr) && next(elem);
};
},
exists: function(next, data, options) {
var name = data.name;
var adapter = options.adapter;
return function exists(elem) {
return adapter.hasAttrib(elem, name) && next(elem);
};
},
start: function(next, data, options) {
var name = data.name;
var value = data.value;
var len = value.length;
var adapter = options.adapter;
if (len === 0) {
return falseFunc;
}
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, options) {
var name = data.name;
var value = data.value;
var len = -value.length;
var adapter = options.adapter;
if (len === 0) {
return falseFunc;
}
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, options) {
var name = data.name;
var value = data.value;
var adapter = options.adapter;
if (value === "") {
return falseFunc;
}
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, options) {
var name = data.name;
var value = data.value;
var adapter = options.adapter;
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);
};
}
};
module.exports = {
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, options);
},
rules: attributeRules
};
......@@ -2,206 +2,218 @@
compiles a selector to an executable function
*/
module.exports = compileFactory;
var parse = require("css-what"),
BaseFuncs = require("boolbase"),
sortRules = require("./sort.js"),
procedure = require("./procedure.json"),
rulesFactory = require("./general.js"),
pseudosFactory = require("./pseudos.js"),
trueFunc = BaseFuncs.trueFunc,
falseFunc = BaseFuncs.falseFunc;
function compileFactory(adapter){
var Pseudos = pseudosFactory(adapter),
filters = Pseudos.filters,
Rules = rulesFactory(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 = 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"},
FLEXIBLE_DESCENDANT_TOKEN = {type: "_flexibleDescendant"},
SCOPE_TOKEN = {type: "pseudo", name: "scope"},
PLACEHOLDER_ELEMENT = {};
//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 || !!adapter.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);
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);
query.shouldTestNextSiblings = shouldTestNextSiblings;
return query;
}
function isTraversal(t){
return procedure[t.type] < 0;
}
function compileRules(rules, options, context){
return rules.reduce(function(func, rule){
if(func === falseFunc) return func;
return Rules[rule.type](func, rule, options, context);
}, 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);
};
}
function containsTraversal(t){
return t.some(isTraversal);
}
//:not, :has and :matches have to compile selectors
//doing this in lib/pseudos.js would lead to circular dependencies,
//so we add them here
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) 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 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;
module.exports = compile;
var parse = require("css-what");
var BaseFuncs = require("boolbase");
var sortRules = require("./sort.js");
var procedure = require("./procedure.json");
var Rules = require("./general.js");
var Pseudos = require("./pseudos.js");
var trueFunc = BaseFuncs.trueFunc;
var falseFunc = BaseFuncs.falseFunc;
var filters = Pseudos.filters;
function compile(selector, options, context) {
var next = compileUnsafe(selector, options, context);
return wrap(next, options);
}
function wrap(next, options) {
var adapter = options.adapter;
return function base(elem) {
return adapter.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" };
var FLEXIBLE_DESCENDANT_TOKEN = { type: "_flexibleDescendant" };
var SCOPE_TOKEN = { type: "pseudo", name: "scope" };
var PLACEHOLDER_ELEMENT = {};
//CSS 4 Spec (Draft): 3.3.1. Absolutizing a Scope-relative Selector
//http://www.w3.org/TR/selectors4/#absolutizing
function absolutize(token, options, context) {
var adapter = options.adapter;
//TODO better check if context is document
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") {
//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, options, 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);
query.shouldTestNextSiblings = shouldTestNextSiblings;
return query;
}
function isTraversal(t) {
return procedure[t.type] < 0;
}
function compileRules(rules, options, context) {
return rules.reduce(function(func, rule) {
if (func === falseFunc) return func;
if (!(rule.type in Rules)) {
throw new Error("Rule type " + rule.type + " is not supported by css-select");
}
return Rules[rule.type](func, rule, options, context);
}, (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);
};
}
function containsTraversal(t) {
return t.some(isTraversal);
}
//:not, :has and :matches have to compile selectors
//doing this in lib/pseudos.js would lead to circular dependencies,
//so we add them here
filters.not = function(next, token, options, context) {
var opts = {
xmlMode: !!(options && options.xmlMode),
strict: !!(options && options.strict),
adapter: options.adapter
};
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) return next;
if (func === trueFunc) return falseFunc;
return function not(elem) {
return !func(elem) && next(elem);
};
};
filters.has = function(next, token, options) {
var adapter = options.adapter;
var opts = {
xmlMode: !!(options && options.xmlMode),
strict: !!(options && options.strict),
adapter: adapter
};
//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 hasChild(elem) {
return adapter.getChildren(elem).some(adapter.isTag) && next(elem);
};
}
func = wrap(func, options);
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,
adapter: options.adapter
};
return compileToken(token, opts, context);
};
compile.compileToken = compileToken;
compile.compileUnsafe = compileUnsafe;
compile.Pseudos = Pseudos;
var attributeFactory = require("./attributes.js");
function generalFactory(adapter, Pseudos){
/*
all available rules
*/
return {
__proto__: null,
attribute: attributeFactory(adapter).compile,
pseudo: Pseudos.compile,
//tags
tag: function(next, data){
var name = data.name;
return function tag(elem){
return adapter.getName(elem) === name && next(elem);
};
},
//traversal
descendant: function(next){
return function descendant(elem){
var found = false;
while(!found && (elem = adapter.getParent(elem))){
found = next(elem);
}
return found;
};
},
_flexibleDescendant: function(next){
// Include element itself, only used while querying an array
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;
}
};
}
module.exports = generalFactory;
var attributes = require("./attributes.js");
var Pseudos = require("./pseudos");
/*
all available rules
*/
module.exports = {
__proto__: null,
attribute: attributes.compile,
pseudo: Pseudos.compile,
//tags
tag: function(next, data, options) {
var name = data.name;
var adapter = options.adapter;
return function tag(elem) {
return adapter.getName(elem) === name && next(elem);
};
},
//traversal
descendant: function(next, data, options) {
// eslint-disable-next-line no-undef
var isFalseCache = typeof WeakSet !== "undefined" ? new WeakSet() : null;
var adapter = options.adapter;
return function descendant(elem) {
var found = false;
while (!found && (elem = adapter.getParent(elem))) {
if (!isFalseCache || !isFalseCache.has(elem)) {
found = next(elem);
if (!found && isFalseCache) {
isFalseCache.add(elem);
}
}
}
return found;
};
},
_flexibleDescendant: function(next, data, options) {
var adapter = options.adapter;
// Include element itself, only used while querying an array
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");
}
var adapter = options.adapter;
return function parent(elem) {
return adapter.getChildren(elem).some(test);
};
function test(elem) {
return adapter.isTag(elem) && next(elem);
}
},
child: function(next, data, options) {
var adapter = options.adapter;
return function child(elem) {
var parent = adapter.getParent(elem);
return !!parent && next(parent);
};
},
sibling: function(next, data, options) {
var adapter = options.adapter;
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, data, options) {
var adapter = options.adapter;
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;
}
};
{
"universal": 50,
"tag": 30,
"attribute": 1,
"pseudo": 0,
"descendant": -1,
"child": -1,
"parent": -1,
"sibling": -1,
"adjacent": -1
"universal": 50,
"tag": 30,
"attribute": 1,
"pseudo": 0,
"descendant": -1,
"child": -1,
"parent": -1,
"sibling": -1,
"adjacent": -1
}
This diff is collapsed.
......@@ -9,72 +9,72 @@ module.exports = sortByProcedure;
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
__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];
function sortByProcedure(arr) {
var procs = arr.map(getProcedure);
for (var i = 1; i < arr.length; i++) {
var procNew = procs[i];
if(procNew < 0) continue;
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;
}
}
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];
function getProcedure(token) {
var proc = procedure[token.type];
if(proc === procedure.attribute){
proc = attributes[token.action];
if (proc === procedure.attribute) {
proc = attributes[token.action];
if(proc === attributes.equals && token.name === "id"){
//prefer ID selectors (eg. #ID)
proc = 9;
}
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;
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;
}
This diff is collapsed.
{
"name": "css-select",
"version": "1.3.0-rc0",
"version": "2.0.2",
"description": "a CSS selector compiler/engine",
"author": "Felix Boehm <me@feedic.com>",
"keywords": [
......@@ -14,23 +14,24 @@
},
"files": [
"index.js",
"index.d.ts",
"lib"
],
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "2.1",
"domutils": "1.5.1",
"nth-check": "^1.0.1"
"css-what": "^2.1.2",
"domutils": "^1.7.0",
"nth-check": "^1.0.2"
},
"devDependencies": {
"cheerio-soupselect": "*",
"coveralls": "*",
"eslint": "^3.0.0",
"expect.js": "*",
"htmlparser2": "*",
"istanbul": "*",
"mocha": "*",
"mocha-lcov-reporter": "*"
"cheerio-soupselect": "^0.1.1",
"coveralls": "^3.0.2",
"eslint": "^5.7.0",
"expect.js": "^0.3.1",
"htmlparser2": "^3.10.0",
"istanbul": "^0.4.5",
"mocha": "^5.2.0",
"mocha-lcov-reporter": "^1.3.0"
},
"scripts": {
"test": "mocha && npm run lint",
......@@ -38,5 +39,9 @@
"lcov": "istanbul cover _mocha --report lcovonly -- -R spec",
"coveralls": "npm run lint && npm run lcov && (cat coverage/lcov.info | coveralls || exit 0)"
},
"license": "BSD-like"
"license": "BSD-2-Clause",
"types": "index.d.ts",
"prettier": {
"tabWidth": 4
}
}
{
"env": {
"mocha": true
}
}
var CSSselect = require(".."),
makeDom = require("htmlparser2").parseDOM,
bools = require("boolbase"),
assert = require("assert");
var dom = makeDom("<div id=foo><p>foo</p></div>")[0],
xmlDom = makeDom("<DiV id=foo><P>foo</P></DiV>", {xmlMode: true})[0];
describe("API", function(){
describe("removes duplicates", function(){
it("between identical trees", function(){
var matches = CSSselect.selectAll("div", [dom, dom]);
assert.equal(matches.length, 1, "Removes duplicate matches");
});
it("between a superset and subset", function(){
var matches = CSSselect.selectAll("p", [dom, dom.children[0]]);
assert.equal(matches.length, 1, "Removes duplicate matches");
});
it("betweeen a subset and superset", function(){
var matches = CSSselect.selectAll("p", [dom.children[0], dom]);
assert.equal(matches.length, 1, "Removes duplicate matches");
});
});
describe("can be queried by function", function(){
it("in `is`", function(){
assert(CSSselect.is(dom, function(elem){
return elem.attribs.id === "foo";
}));
});
//probably more cases should be added here
});
describe("selectAll", function(){
it("should query array elements directly when they have no parents", function(){
var divs = [dom];
assert.deepEqual(CSSselect.selectAll("div", divs), divs);
});
it("should query array elements directly when they have parents", function(){
var ps = CSSselect.selectAll("p", [dom]);
assert.deepEqual(CSSselect.selectAll("p", ps), ps);
});
});
describe("unsatisfiable and universally valid selectors", function(){
it("in :not", function(){
var func = CSSselect._compileUnsafe(":not(*)");
assert.equal(func, bools.falseFunc);
func = CSSselect._compileUnsafe(":not(:nth-child(-1n-1))");
assert.equal(func, bools.trueFunc);
func = CSSselect._compileUnsafe(":not(:not(:not(*)))");
assert.equal(func, bools.falseFunc);
});
it("in :has", function(){
var matches = CSSselect.selectAll(":has(*)", [dom]);
assert.equal(matches.length, 1);
assert.equal(matches[0], dom);
var func = CSSselect._compileUnsafe(":has(:nth-child(-1n-1))");
assert.equal(func, bools.falseFunc);
});
it("should skip unsatisfiable", function(){
var func = CSSselect._compileUnsafe("* :not(*) foo");
assert.equal(func, bools.falseFunc);
});
it("should promote universally valid", function(){
var func = CSSselect._compileUnsafe("*, foo");
assert.equal(func, bools.trueFunc);
});
});
describe(":matches", function(){
it("should select multiple elements", function(){
var matches = CSSselect.selectAll(":matches(p, div)", [dom]);
assert.equal(matches.length, 2);
matches = CSSselect.selectAll(":matches(div, :not(div))", [dom]);
assert.equal(matches.length, 2);
matches = CSSselect.selectAll(":matches(boo, baa, tag, div, foo, bar, baz)", [dom]);
assert.equal(matches.length, 1);
assert.equal(matches[0], dom);
});
it("should strip quotes", function(){
var matches = CSSselect.selectAll(":matches('p, div')", [dom]);
assert.equal(matches.length, 2);
matches = CSSselect.selectAll(":matches(\"p, div\")", [dom]);
assert.equal(matches.length, 2);
});
});
describe("parent selector (<)", function(){
it("should select the right element", function(){
var matches = CSSselect.selectAll("p < div", [dom]);
assert.equal(matches.length, 1);
assert.equal(matches[0], dom);
});
it("should not select nodes without children", function(){
var matches = CSSselect.selectAll("p < div", [dom]);
assert.deepEqual(matches, CSSselect.selectAll("* < *", [dom]));
});
});
describe("selectOne", function(){
it("should select elements in traversal order", function(){
var match = CSSselect.selectOne("p", [dom]);
assert.equal(match, dom.children[0]);
match = CSSselect.selectOne(":contains(foo)", [dom]);
assert.equal(match, dom);
});
it("should take shortcuts when applicable", function(){
//TODO this is currently only visible in coverage reports
var match = CSSselect.selectOne(bools.falseFunc, [dom]);
assert.equal(match, null);
match = CSSselect.selectOne("*", []);
assert.equal(match, null);
});
});
describe("options", function(){
var opts = {xmlMode: true};
it("should recognize xmlMode in :has and :not", function(){
assert(CSSselect.is(xmlDom, "DiV:has(P)", opts));
assert(CSSselect.is(xmlDom, "DiV:not(div)", opts));
assert(CSSselect.is(xmlDom.children[0], "DiV:has(P) :not(p)", opts));
});
it("should be strict", function(){
var opts = {strict: true};
assert.throws(CSSselect.compile.bind(null, ":checkbox", opts), Error);
assert.throws(CSSselect.compile.bind(null, "[attr=val i]", opts), Error);
assert.throws(CSSselect.compile.bind(null, "[attr!=val]", opts), Error);
assert.throws(CSSselect.compile.bind(null, "[attr!=val i]", opts), Error);
assert.throws(CSSselect.compile.bind(null, "foo < bar", opts), Error);
assert.throws(CSSselect.compile.bind(null, ":not(:parent)", opts), Error);
assert.throws(CSSselect.compile.bind(null, ":not(a > b)", opts), Error);
assert.throws(CSSselect.compile.bind(null, ":not(a, b)", opts), Error);
});
it("should recognize contexts", function(){
var div = CSSselect.selectAll("div", [dom]),
p = CSSselect.selectAll("p", [dom]);
assert.equal(CSSselect.selectOne("div", div, {context: div}), div[0]);
assert.equal(CSSselect.selectOne("div", div, {context: p}), null);
assert.deepEqual(CSSselect.selectAll("p", div, {context: div}), p);
});
});
var CSSselect = require("..");
var makeDom = require("htmlparser2").parseDOM;
var bools = require("boolbase");
var assert = require("assert");
var dom = makeDom("<div id=foo><p>foo</p></div>")[0];
var xmlDom = makeDom("<DiV id=foo><P>foo</P></DiV>", { xmlMode: true })[0];
describe("API", function() {
describe("removes duplicates", function() {
it("between identical trees", function() {
var matches = CSSselect.selectAll("div", [dom, dom]);
assert.equal(matches.length, 1, "Removes duplicate matches");
});
it("between a superset and subset", function() {
var matches = CSSselect.selectAll("p", [dom, dom.children[0]]);
assert.equal(matches.length, 1, "Removes duplicate matches");
});
it("betweeen a subset and superset", function() {
var matches = CSSselect.selectAll("p", [dom.children[0], dom]);
assert.equal(matches.length, 1, "Removes duplicate matches");
});
});
describe("can be queried by function", function() {
it("in `is`", function() {
assert(
CSSselect.is(dom, function(elem) {
return elem.attribs.id === "foo";
})
);
});
//probably more cases should be added here
});
describe("selectAll", function() {
it("should query array elements directly when they have no parents", function() {
var divs = [dom];
assert.deepEqual(CSSselect.selectAll("div", divs), divs);
});
it("should query array elements directly when they have parents", function() {
var ps = CSSselect.selectAll("p", [dom]);
assert.deepEqual(CSSselect.selectAll("p", ps), ps);
});
});
describe("unsatisfiable and universally valid selectors", function() {
it("in :not", function() {
var func = CSSselect._compileUnsafe(":not(*)");
assert.equal(func, bools.falseFunc);
func = CSSselect._compileUnsafe(":not(:nth-child(-1n-1))");
assert.equal(func, bools.trueFunc);
func = CSSselect._compileUnsafe(":not(:not(:not(*)))");
assert.equal(func, bools.falseFunc);
});
it("in :has", function() {
var matches = CSSselect.selectAll(":has(*)", [dom]);
assert.equal(matches.length, 1);
assert.equal(matches[0], dom);
var func = CSSselect._compileUnsafe(":has(:nth-child(-1n-1))");
assert.equal(func, bools.falseFunc);
});
it("should skip unsatisfiable", function() {
var func = CSSselect._compileUnsafe("* :not(*) foo");
assert.equal(func, bools.falseFunc);
});
it("should promote universally valid", function() {
var func = CSSselect._compileUnsafe("*, foo");
assert.equal(func, bools.trueFunc);
});
});
describe(":matches", function() {
it("should select multiple elements", function() {
var matches = CSSselect.selectAll(":matches(p, div)", [dom]);
assert.equal(matches.length, 2);
matches = CSSselect.selectAll(":matches(div, :not(div))", [dom]);
assert.equal(matches.length, 2);
matches = CSSselect.selectAll(":matches(boo, baa, tag, div, foo, bar, baz)", [dom]);
assert.equal(matches.length, 1);
assert.equal(matches[0], dom);
});
it("should strip quotes", function() {
var matches = CSSselect.selectAll(":matches('p, div')", [dom]);
assert.equal(matches.length, 2);
matches = CSSselect.selectAll(':matches("p, div")', [dom]);
assert.equal(matches.length, 2);
});
});
describe("parent selector (<)", function() {
it("should select the right element", function() {
var matches = CSSselect.selectAll("p < div", [dom]);
assert.equal(matches.length, 1);
assert.equal(matches[0], dom);
});
it("should not select nodes without children", function() {
var matches = CSSselect.selectAll("p < div", [dom]);
assert.deepEqual(matches, CSSselect.selectAll("* < *", [dom]));
});
});
describe("selectOne", function() {
it("should select elements in traversal order", function() {
var match = CSSselect.selectOne("p", [dom]);
assert.equal(match, dom.children[0]);
match = CSSselect.selectOne(":contains(foo)", [dom]);
assert.equal(match, dom);
});
it("should take shortcuts when applicable", function() {
//TODO this is currently only visible in coverage reports
var match = CSSselect.selectOne(bools.falseFunc, [dom]);
assert.equal(match, null);
match = CSSselect.selectOne("*", []);
assert.equal(match, null);
});
});
describe("options", function() {
var opts = { xmlMode: true };
it("should recognize xmlMode in :has and :not", function() {
assert(CSSselect.is(xmlDom, "DiV:has(P)", opts));
assert(CSSselect.is(xmlDom, "DiV:not(div)", opts));
assert(CSSselect.is(xmlDom.children[0], "DiV:has(P) :not(p)", opts));
});
it("should be strict", function() {
var opts = { strict: true };
assert.throws(CSSselect.compile.bind(null, ":checkbox", opts), Error);
assert.throws(CSSselect.compile.bind(null, "[attr=val i]", opts), Error);
assert.throws(CSSselect.compile.bind(null, "[attr!=val]", opts), Error);
assert.throws(CSSselect.compile.bind(null, "[attr!=val i]", opts), Error);
assert.throws(CSSselect.compile.bind(null, "foo < bar", opts), Error);
assert.throws(CSSselect.compile.bind(null, ":not(:parent)", opts), Error);
assert.throws(CSSselect.compile.bind(null, ":not(a > b)", opts), Error);
assert.throws(CSSselect.compile.bind(null, ":not(a, b)", opts), Error);
});
it("should recognize contexts", function() {
var div = CSSselect.selectAll("div", [dom]),
p = CSSselect.selectAll("p", [dom]);
assert.equal(CSSselect.selectOne("div", div, { context: div }), div[0]);
assert.equal(CSSselect.selectOne("div", div, { context: p }), null);
assert.deepEqual(CSSselect.selectAll("p", div, { context: div }), p);
});
});
});
var CSSselect = require("../"),
makeDom = require("htmlparser2").parseDOM,
falseFunc = require("boolbase").falseFunc,
assert = require("assert");
makeDom = require("htmlparser2").parseDOM,
falseFunc = require("boolbase").falseFunc,
assert = require("assert");
var dom = makeDom("<div><div data-foo=\"In the end, it doesn't really matter.\"></div><div data-foo=\"Indeed-that's a delicate matter.\">");
var dom = makeDom(
'<div><div data-foo="In the end, it doesn\'t really matter."></div><div data-foo="Indeed-that\'s a delicate matter.">'
);
describe("Attributes", function(){
describe("ignore case", function(){
it("should for =", function(){
var matches = CSSselect.selectAll("[data-foo=\"indeed-that's a delicate matter.\" i]", dom);
assert.equal(matches.length, 1);
assert.deepEqual(matches, [dom[0].children[1]]);
matches = CSSselect.selectAll("[data-foo=\"inDeeD-THAT's a DELICATE matteR.\" i]", dom);
assert.deepEqual(matches, [dom[0].children[1]]);
});
describe("Attributes", function() {
describe("ignore case", function() {
it("should for =", function() {
var matches = CSSselect.selectAll('[data-foo="indeed-that\'s a delicate matter." i]', dom);
assert.equal(matches.length, 1);
assert.deepEqual(matches, [dom[0].children[1]]);
matches = CSSselect.selectAll('[data-foo="inDeeD-THAT\'s a DELICATE matteR." i]', dom);
assert.deepEqual(matches, [dom[0].children[1]]);
});
it("should for ^=", function(){
var matches = CSSselect.selectAll("[data-foo^=IN i]", dom);
assert.equal(matches.length, 2);
assert.deepEqual(matches, dom[0].children);
matches = CSSselect.selectAll("[data-foo^=in i]", dom);
assert.deepEqual(matches, dom[0].children);
matches = CSSselect.selectAll("[data-foo^=iN i]", dom);
assert.deepEqual(matches, dom[0].children);
});
it("should for ^=", function() {
var matches = CSSselect.selectAll("[data-foo^=IN i]", dom);
assert.equal(matches.length, 2);
assert.deepEqual(matches, dom[0].children);
matches = CSSselect.selectAll("[data-foo^=in i]", dom);
assert.deepEqual(matches, dom[0].children);
matches = CSSselect.selectAll("[data-foo^=iN i]", dom);
assert.deepEqual(matches, dom[0].children);
});
it("should for $=", function(){
var matches = CSSselect.selectAll("[data-foo$=\"MATTER.\" i]", dom);
assert.equal(matches.length, 2);
assert.deepEqual(matches, dom[0].children);
matches = CSSselect.selectAll("[data-foo$=\"matter.\" i]", dom);
assert.deepEqual(matches, dom[0].children);
matches = CSSselect.selectAll("[data-foo$=\"MaTtEr.\" i]", dom);
assert.deepEqual(matches, dom[0].children);
});
it("should for $=", function() {
var matches = CSSselect.selectAll('[data-foo$="MATTER." i]', dom);
assert.equal(matches.length, 2);
assert.deepEqual(matches, dom[0].children);
matches = CSSselect.selectAll('[data-foo$="matter." i]', dom);
assert.deepEqual(matches, dom[0].children);
matches = CSSselect.selectAll('[data-foo$="MaTtEr." i]', dom);
assert.deepEqual(matches, dom[0].children);
});
it("should for !=", function(){
var matches = CSSselect.selectAll("[data-foo!=\"indeed-that's a delicate matter.\" i]", dom);
assert.equal(matches.length, 1);
assert.deepEqual(matches, [dom[0].children[0]]);
matches = CSSselect.selectAll("[data-foo!=\"inDeeD-THAT's a DELICATE matteR.\" i]", dom);
assert.deepEqual(matches, [dom[0].children[0]]);
});
it("should for !=", function() {
var matches = CSSselect.selectAll('[data-foo!="indeed-that\'s a delicate matter." i]', dom);
assert.equal(matches.length, 1);
assert.deepEqual(matches, [dom[0].children[0]]);
matches = CSSselect.selectAll('[data-foo!="inDeeD-THAT\'s a DELICATE matteR." i]', dom);
assert.deepEqual(matches, [dom[0].children[0]]);
});
it("should for *=", function(){
var matches = CSSselect.selectAll("[data-foo*=IT i]", dom);
assert.equal(matches.length, 1);
assert.deepEqual(matches, [dom[0].children[0]]);
matches = CSSselect.selectAll("[data-foo*=tH i]", dom);
assert.deepEqual(matches, dom[0].children);
});
it("should for *=", function() {
var matches = CSSselect.selectAll("[data-foo*=IT i]", dom);
assert.equal(matches.length, 1);
assert.deepEqual(matches, [dom[0].children[0]]);
matches = CSSselect.selectAll("[data-foo*=tH i]", dom);
assert.deepEqual(matches, dom[0].children);
});
it("should for |=", function(){
var matches = CSSselect.selectAll("[data-foo|=indeed i]", dom);
assert.equal(matches.length, 1);
assert.deepEqual(matches, [dom[0].children[1]]);
matches = CSSselect.selectAll("[data-foo|=inDeeD i]", dom);
assert.deepEqual(matches, [dom[0].children[1]]);
});
it("should for |=", function() {
var matches = CSSselect.selectAll("[data-foo|=indeed i]", dom);
assert.equal(matches.length, 1);
assert.deepEqual(matches, [dom[0].children[1]]);
matches = CSSselect.selectAll("[data-foo|=inDeeD i]", dom);
assert.deepEqual(matches, [dom[0].children[1]]);
});
it("should for ~=", function(){
var matches = CSSselect.selectAll("[data-foo~=IT i]", dom);
assert.equal(matches.length, 1);
assert.deepEqual(matches, [dom[0].children[0]]);
matches = CSSselect.selectAll("[data-foo~=dElIcAtE i]", dom);
assert.deepEqual(matches, [dom[0].children[1]]);
});
});
it("should for ~=", function() {
var matches = CSSselect.selectAll("[data-foo~=IT i]", dom);
assert.equal(matches.length, 1);
assert.deepEqual(matches, [dom[0].children[0]]);
matches = CSSselect.selectAll("[data-foo~=dElIcAtE i]", dom);
assert.deepEqual(matches, [dom[0].children[1]]);
});
});
describe("no matches", function(){
it("should for ~=", function(){
assert.equal(CSSselect._compileUnsafe("[foo~='baz bar']"), falseFunc);
});
describe("no matches", function() {
it("should for ~=", function() {
assert.equal(CSSselect._compileUnsafe("[foo~='baz bar']"), falseFunc);
});
it("should for $=", function(){
assert.equal(CSSselect._compileUnsafe("[foo$='']"), falseFunc);
});
it("should for $=", function() {
assert.equal(CSSselect._compileUnsafe("[foo$='']"), falseFunc);
});
it("should for *=", function(){
assert.equal(CSSselect._compileUnsafe("[foo*='']"), falseFunc);
});
});
});
\ No newline at end of file
it("should for *=", function() {
assert.equal(CSSselect._compileUnsafe("[foo*='']"), falseFunc);
});
});
});
function isNode(v) {
return !!(v && v.type);
}
function isNodeArray(v) {
if (Array.isArray(v) && isNode(v[0])) {
return true;
}
}
function NodeWithoutCircularReferences(node) {
var newNode = {};
Object.assign(newNode, node);
delete newNode.next;
delete newNode.prev;
delete newNode.parent;
if (isNodeArray(newNode.children)) {
newNode.children = removeCircularRefs(newNode.children);
}
return newNode;
}
function removeCircularRefs(value) {
if (isNodeArray(value)) {
return value.map(NodeWithoutCircularReferences);
} else if (isNode(value)) {
return NodeWithoutCircularReferences(value);
} else {
return value;
}
}
module.exports = removeCircularRefs;
This diff is collapsed.
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment