Created
March 28, 2012 15:14
-
-
Save AKIo0O/2227072 to your computer and use it in GitHub Desktop.
qs - simple query selector
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
@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