Skip to content

Instantly share code, notes, and snippets.

@AKIo0O
Created March 28, 2012 15:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AKIo0O/2227072 to your computer and use it in GitHub Desktop.
Save AKIo0O/2227072 to your computer and use it in GitHub Desktop.
qs - simple query selector
/**
@file qs.js
Query Selector
== Terminology ==
Query: Comma-separated group of selectors (a.nav, button)
Compound: Cluster of simple selectors (a#login.onsite)
Chain: Group of compounds separated by combinators.
Combinator: Separates compounds. Currently only whitespace.
*/
// "use strict";
/**
qs
Perform a simple query selection.
@param String query
@param Node root
optional root node, defaults to `document`.
@return Array<Node>
*/
function qs (query, root){
if (!root) root = qs.global.document;
return qs.run(
qs.cache[query] || qs.compile(query),
{ root: root, nodes: [root], doc: root.ownerDocument || root }
).nodes;
}
qs.global = (function () { return this || [eval][0]('this'); }());
qs.cache = {};
qs.hasClass = function (node, v) {
return (' ' + node.className + ' ').indexOf(' ' + v + ' ') > -1;
};
qs.check = function (node, cmp) {
var cn, i = 0;
if (cmp.tn && (cmp.tn != node.tagName)) return false;
if (cmp.id && (cmp.id != node.id)) return false;
if (cmp.cn) while ((cn = cmp.cn[i++]))
if (!qs.hasClass(node, cn)) return false;
return true;
};
// regexen
qs.r = {
singletons: /^(?:body|head|title)$/i,
cn: /\.[^\s\.#]+/g,
id: /#[^\s\.#]+/g,
tn: /^[^\s\.#]+/g,
lTrim: /^\s\s*/,
rTrim: /\s\s*$/,
comma: /\s*,\s*/,
space: /\s+/
};
// regex madness
qs.compile = function (query) {
var fn, id, out = [], q = new qs.Query(query),
selectors = q.selectors,
selector = selectors[0],
cmp, prevChain, i = -1;
if (qs.cache[q]) {
return (qs.cache[query] = qs.cache[q]);
}
if (selectors.length > 1) {
// FIXME: handle groups of selectors (recursive qs call)
}
prevChain = 0;
while ((cmp = selector[++i])) {
var isLast = (i == selector.length - 1),
isSingleton = qs.r.singletons.test('' + cmp.tn);
if (cmp.id || isSingleton || isLast) {
out = out.concat(qs.compile.compoundToChain(
cmp, selector.slice(prevChain, i), prevChain
));
prevChain = i + 1;
}
}
return (qs.cache[query] = qs.cache[q] = out);
};
qs.compile.compoundToChain = function (cmp, ancestorChecks, hasNodes) {
var out = [], check, hasId, hasAncestorChecks, cn;
cmp = cmp.copy();
if (qs.r.singletons.test('' + cmp.tn)) {
out.push({ fn: qs.cmd.getByTag, args: [cmp.tn, true] });
cmp.tn = false;
}
else if (cmp.id) {
out.push({ fn: qs.cmd.getById, args: [cmp.id] });
hasId = true;
cmp.id = false;
}
else if (cmp.cn.length) {
cn = cmp.cn.shift();
out.push({ fn: qs.cmd.getByClass, args: [cn] });
}
else if (cmp.tn) {
out.push({ fn: qs.cmd.getByTag, args: [cmp.tn] });
cmp.tn = false;
}
if (cmp.id || cmp.tn || cmp.cn && cmp.cn.length) {
out.push({ fn: qs.cmd.filter, args: [cmp] });
}
if (ancestorChecks.length) {
out.push({ fn: qs.cmd.checkAncestors, args: ancestorChecks });
hasAncestorChecks = true;
}
if (hasId) {
out.push({ fn: qs.cmd.checkIdRoot });
}
return out;
};
qs.run = function (commands, context) {
var cmd, i = 0;
if (!context) context = {};
while ((cmd = commands[i++]))
if (cmd.fn.apply(context, cmd.args))
return context;
return context;
};
qs.cmd = {
getById: function (v) { // getById
var e = this.doc.getElementById(v);
this.nodes = e ? [e] : [];
return !e;
},
getByClass: function (v) { // getByClass
this.nodes = this.root.getElementsByClassName(v);
return !this.nodes.length;
},
getByTag: function (v, setRoot) { // getByTag
this.nodes = this.root.getElementsByTagName(v);
if (setRoot) this.root = this.nodes[0];
return !this.nodes.length;
},
filter: function (cmp) { // filter
var nodes = this.nodes, node, i = -1, out = [];
while ((node = nodes[++i]))
if (qs.check(node, cmp))
out.push(node);
this.nodes = out;
return !out.length;
},
checkAncestors: function () { // checkAncestors
var root = this.root, nodes = this.nodes, node, out = [],
check, len = arguments.length, checkIndex = len, i = 0,
ancestor, topAncestor;
while ((node = nodes[i++])) {
ancestor = node;
check = arguments[--checkIndex];
while ((ancestor = ancestor.parentNode) &&
(ancestor != root)) {
if (qs.check(ancestor, check)) {
check = arguments[--checkIndex];
if (checkIndex < 2) {
topAncestor = ancestor;
out.push(node);
break;
}
}
}
checkIndex = len;
}
this.topAncestor = topAncestor;
this.nodes = out;
return !out.length;
},
checkIdRoot: function () { // checkIdRoot
var root = this.root,
node = this.topAncestor || this.nodes[0];
if (!root.ownerDocument) {
root = this.nodes[0];
return false;
}
while ((node = node.parentNode)) {
if (node == root) {
root = this.nodes[0];
return false;
}
}
return true;
}
};
qs.Compound = function (s) {
this.cn = (s.match(qs.r.cn) || []).join('').substring(1).split('.');
this.tn = ((s.match(qs.r.tn) || [])[0] || '').toUpperCase();
this.id = ((s.match(qs.r.id) || [])[0] || ' ').substring(1);
this.cn.sort();
};
qs.Compound.prototype.copy = function () {
return {cn: this.cn.slice(), id:this.id, tn:this.tn};
};
qs.Compound.prototype.toString = function () {
if (this.normalized) return this.normalized;
return (this.normalized = this.tn + this.id + this.cn.join(''));
};
qs.Query = function (selector) {
var selectors = selector.split(qs.r.comma),
compound, compounds, i = -1, j;
this.original = selector;
while ((selector = selectors[++i])) {
selector = selector.replace(qs.r.lTrim, '').replace(qs.r.rTrim, '');
compounds = selector.split(qs.r.space);
j = -1;
while ((compound = compounds[++j])) {
compounds[j] = new qs.Compound(compound);
}
selectors[i] = compounds;
}
this.selectors = selectors.sort();
};
qs.Query.prototype.toString = function () {
if (this.normalized) return this.normalized;
var selector, selectors = this.selectors, i = -1, out = '';
while ((selector = selectors[++i])) {
if (i) out += ', ';
out += selector.join(' ');
}
return (this.normalized = out);
};
// TODO: remove me
qs.global.qs = qs;
qs.global.Element.prototype.querySelectorAll = qs.global.Document.prototype.querySelectorAll = null;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment