934 lines
21 KiB
JavaScript
934 lines
21 KiB
JavaScript
"use strict";
|
|
/* jshint eqnull: true */
|
|
/**
|
|
* Zest (https://github.com/chjj/zest)
|
|
* A css selector engine.
|
|
* Copyright (c) 2011-2012, Christopher Jeffrey. (MIT Licensed)
|
|
* Domino version based on Zest v0.1.3 with bugfixes applied.
|
|
*/
|
|
|
|
/**
|
|
* Helpers
|
|
*/
|
|
|
|
var window = Object.create(null, {
|
|
location: { get: function() {
|
|
throw new Error('window.location is not supported.');
|
|
} }
|
|
});
|
|
|
|
var compareDocumentPosition = function(a, b) {
|
|
return a.compareDocumentPosition(b);
|
|
};
|
|
|
|
var order = function(a, b) {
|
|
/* jshint bitwise: false */
|
|
return compareDocumentPosition(a, b) & 2 ? 1 : -1;
|
|
};
|
|
|
|
var next = function(el) {
|
|
while ((el = el.nextSibling)
|
|
&& el.nodeType !== 1);
|
|
return el;
|
|
};
|
|
|
|
var prev = function(el) {
|
|
while ((el = el.previousSibling)
|
|
&& el.nodeType !== 1);
|
|
return el;
|
|
};
|
|
|
|
var child = function(el) {
|
|
/*jshint -W084 */
|
|
if (el = el.firstChild) {
|
|
while (el.nodeType !== 1
|
|
&& (el = el.nextSibling));
|
|
}
|
|
return el;
|
|
};
|
|
|
|
var lastChild = function(el) {
|
|
/*jshint -W084 */
|
|
if (el = el.lastChild) {
|
|
while (el.nodeType !== 1
|
|
&& (el = el.previousSibling));
|
|
}
|
|
return el;
|
|
};
|
|
|
|
var parentIsElement = function(n) {
|
|
if (!n.parentNode) { return false; }
|
|
var nodeType = n.parentNode.nodeType;
|
|
// The root `html` element can be a first- or last-child, too.
|
|
return nodeType === 1 || nodeType === 9;
|
|
};
|
|
|
|
var unquote = function(str) {
|
|
if (!str) return str;
|
|
var ch = str[0];
|
|
if (ch === '"' || ch === '\'') {
|
|
if (str[str.length-1] === ch) {
|
|
str = str.slice(1, -1);
|
|
} else {
|
|
// bad string.
|
|
str = str.slice(1);
|
|
}
|
|
return str.replace(rules.str_escape, function(s) {
|
|
var m = /^\\(?:([0-9A-Fa-f]+)|([\r\n\f]+))/.exec(s);
|
|
if (!m) { return s.slice(1); }
|
|
if (m[2]) { return ''; /* escaped newlines are ignored in strings. */ }
|
|
var cp = parseInt(m[1], 16);
|
|
return String.fromCodePoint ? String.fromCodePoint(cp) :
|
|
// Not all JavaScript implementations have String.fromCodePoint yet.
|
|
String.fromCharCode(cp);
|
|
});
|
|
} else if (rules.ident.test(str)) {
|
|
return decodeid(str);
|
|
} else {
|
|
// NUMBER, PERCENTAGE, DIMENSION, etc
|
|
return str;
|
|
}
|
|
};
|
|
|
|
var decodeid = function(str) {
|
|
return str.replace(rules.escape, function(s) {
|
|
var m = /^\\([0-9A-Fa-f]+)/.exec(s);
|
|
if (!m) { return s[1]; }
|
|
var cp = parseInt(m[1], 16);
|
|
return String.fromCodePoint ? String.fromCodePoint(cp) :
|
|
// Not all JavaScript implementations have String.fromCodePoint yet.
|
|
String.fromCharCode(cp);
|
|
});
|
|
};
|
|
|
|
var indexOf = (function() {
|
|
if (Array.prototype.indexOf) {
|
|
return Array.prototype.indexOf;
|
|
}
|
|
return function(obj, item) {
|
|
var i = this.length;
|
|
while (i--) {
|
|
if (this[i] === item) return i;
|
|
}
|
|
return -1;
|
|
};
|
|
})();
|
|
|
|
var makeInside = function(start, end) {
|
|
var regex = rules.inside.source
|
|
.replace(/</g, start)
|
|
.replace(/>/g, end);
|
|
|
|
return new RegExp(regex);
|
|
};
|
|
|
|
var replace = function(regex, name, val) {
|
|
regex = regex.source;
|
|
regex = regex.replace(name, val.source || val);
|
|
return new RegExp(regex);
|
|
};
|
|
|
|
var truncateUrl = function(url, num) {
|
|
return url
|
|
.replace(/^(?:\w+:\/\/|\/+)/, '')
|
|
.replace(/(?:\/+|\/*#.*?)$/, '')
|
|
.split('/', num)
|
|
.join('/');
|
|
};
|
|
|
|
/**
|
|
* Handle `nth` Selectors
|
|
*/
|
|
|
|
var parseNth = function(param_, test) {
|
|
var param = param_.replace(/\s+/g, '')
|
|
, cap;
|
|
|
|
if (param === 'even') {
|
|
param = '2n+0';
|
|
} else if (param === 'odd') {
|
|
param = '2n+1';
|
|
} else if (param.indexOf('n') === -1) {
|
|
param = '0n' + param;
|
|
}
|
|
|
|
cap = /^([+-])?(\d+)?n([+-])?(\d+)?$/.exec(param);
|
|
|
|
return {
|
|
group: cap[1] === '-'
|
|
? -(cap[2] || 1)
|
|
: +(cap[2] || 1),
|
|
offset: cap[4]
|
|
? (cap[3] === '-' ? -cap[4] : +cap[4])
|
|
: 0
|
|
};
|
|
};
|
|
|
|
var nth = function(param_, test, last) {
|
|
var param = parseNth(param_)
|
|
, group = param.group
|
|
, offset = param.offset
|
|
, find = !last ? child : lastChild
|
|
, advance = !last ? next : prev;
|
|
|
|
return function(el) {
|
|
if (!parentIsElement(el)) return;
|
|
|
|
var rel = find(el.parentNode)
|
|
, pos = 0;
|
|
|
|
while (rel) {
|
|
if (test(rel, el)) pos++;
|
|
if (rel === el) {
|
|
pos -= offset;
|
|
return group && pos
|
|
? (pos % group) === 0 && (pos < 0 === group < 0)
|
|
: !pos;
|
|
}
|
|
rel = advance(rel);
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Simple Selectors
|
|
*/
|
|
|
|
var selectors = {
|
|
'*': (function() {
|
|
if (false/*function() {
|
|
var el = document.createElement('div');
|
|
el.appendChild(document.createComment(''));
|
|
return !!el.getElementsByTagName('*')[0];
|
|
}()*/) {
|
|
return function(el) {
|
|
if (el.nodeType === 1) return true;
|
|
};
|
|
}
|
|
return function() {
|
|
return true;
|
|
};
|
|
})(),
|
|
'type': function(type) {
|
|
type = type.toLowerCase();
|
|
return function(el) {
|
|
return el.nodeName.toLowerCase() === type;
|
|
};
|
|
},
|
|
'attr': function(key, op, val, i) {
|
|
op = operators[op];
|
|
return function(el) {
|
|
var attr;
|
|
switch (key) {
|
|
case 'for':
|
|
attr = el.htmlFor;
|
|
break;
|
|
case 'class':
|
|
// className is '' when non-existent
|
|
// getAttribute('class') is null
|
|
attr = el.className;
|
|
if (attr === '' && el.getAttribute('class') == null) {
|
|
attr = null;
|
|
}
|
|
break;
|
|
case 'href':
|
|
case 'src':
|
|
attr = el.getAttribute(key, 2);
|
|
break;
|
|
case 'title':
|
|
// getAttribute('title') can be '' when non-existent sometimes?
|
|
attr = el.getAttribute('title') || null;
|
|
break;
|
|
// careful with attributes with special getter functions
|
|
case 'id':
|
|
case 'lang':
|
|
case 'dir':
|
|
case 'accessKey':
|
|
case 'hidden':
|
|
case 'tabIndex':
|
|
case 'style':
|
|
if (el.getAttribute) {
|
|
attr = el.getAttribute(key);
|
|
break;
|
|
}
|
|
/* falls through */
|
|
default:
|
|
if (el.hasAttribute && !el.hasAttribute(key)) {
|
|
break;
|
|
}
|
|
attr = el[key] != null
|
|
? el[key]
|
|
: el.getAttribute && el.getAttribute(key);
|
|
break;
|
|
}
|
|
if (attr == null) return;
|
|
attr = attr + '';
|
|
if (i) {
|
|
attr = attr.toLowerCase();
|
|
val = val.toLowerCase();
|
|
}
|
|
return op(attr, val);
|
|
};
|
|
},
|
|
':first-child': function(el) {
|
|
return !prev(el) && parentIsElement(el);
|
|
},
|
|
':last-child': function(el) {
|
|
return !next(el) && parentIsElement(el);
|
|
},
|
|
':only-child': function(el) {
|
|
return !prev(el) && !next(el) && parentIsElement(el);
|
|
},
|
|
':nth-child': function(param, last) {
|
|
return nth(param, function() {
|
|
return true;
|
|
}, last);
|
|
},
|
|
':nth-last-child': function(param) {
|
|
return selectors[':nth-child'](param, true);
|
|
},
|
|
':root': function(el) {
|
|
return el.ownerDocument.documentElement === el;
|
|
},
|
|
':empty': function(el) {
|
|
return !el.firstChild;
|
|
},
|
|
':not': function(sel) {
|
|
var test = compileGroup(sel);
|
|
return function(el) {
|
|
return !test(el);
|
|
};
|
|
},
|
|
':first-of-type': function(el) {
|
|
if (!parentIsElement(el)) return;
|
|
var type = el.nodeName;
|
|
/*jshint -W084 */
|
|
while (el = prev(el)) {
|
|
if (el.nodeName === type) return;
|
|
}
|
|
return true;
|
|
},
|
|
':last-of-type': function(el) {
|
|
if (!parentIsElement(el)) return;
|
|
var type = el.nodeName;
|
|
/*jshint -W084 */
|
|
while (el = next(el)) {
|
|
if (el.nodeName === type) return;
|
|
}
|
|
return true;
|
|
},
|
|
':only-of-type': function(el) {
|
|
return selectors[':first-of-type'](el)
|
|
&& selectors[':last-of-type'](el);
|
|
},
|
|
':nth-of-type': function(param, last) {
|
|
return nth(param, function(rel, el) {
|
|
return rel.nodeName === el.nodeName;
|
|
}, last);
|
|
},
|
|
':nth-last-of-type': function(param) {
|
|
return selectors[':nth-of-type'](param, true);
|
|
},
|
|
':checked': function(el) {
|
|
return !!(el.checked || el.selected);
|
|
},
|
|
':indeterminate': function(el) {
|
|
return !selectors[':checked'](el);
|
|
},
|
|
':enabled': function(el) {
|
|
return !el.disabled && el.type !== 'hidden';
|
|
},
|
|
':disabled': function(el) {
|
|
return !!el.disabled;
|
|
},
|
|
':target': function(el) {
|
|
return el.id === window.location.hash.substring(1);
|
|
},
|
|
':focus': function(el) {
|
|
return el === el.ownerDocument.activeElement;
|
|
},
|
|
':is': function(sel) {
|
|
return compileGroup(sel);
|
|
},
|
|
// :matches is an older name for :is; see
|
|
// https://github.com/w3c/csswg-drafts/issues/3258
|
|
':matches': function(sel) {
|
|
return selectors[':is'](sel);
|
|
},
|
|
':nth-match': function(param, last) {
|
|
var args = param.split(/\s*,\s*/)
|
|
, arg = args.shift()
|
|
, test = compileGroup(args.join(','));
|
|
|
|
return nth(arg, test, last);
|
|
},
|
|
':nth-last-match': function(param) {
|
|
return selectors[':nth-match'](param, true);
|
|
},
|
|
':links-here': function(el) {
|
|
return el + '' === window.location + '';
|
|
},
|
|
':lang': function(param) {
|
|
return function(el) {
|
|
while (el) {
|
|
if (el.lang) return el.lang.indexOf(param) === 0;
|
|
el = el.parentNode;
|
|
}
|
|
};
|
|
},
|
|
':dir': function(param) {
|
|
return function(el) {
|
|
while (el) {
|
|
if (el.dir) return el.dir === param;
|
|
el = el.parentNode;
|
|
}
|
|
};
|
|
},
|
|
':scope': function(el, con) {
|
|
var context = con || el.ownerDocument;
|
|
if (context.nodeType === 9) {
|
|
return el === context.documentElement;
|
|
}
|
|
return el === context;
|
|
},
|
|
':any-link': function(el) {
|
|
return typeof el.href === 'string';
|
|
},
|
|
':local-link': function(el) {
|
|
if (el.nodeName) {
|
|
return el.href && el.host === window.location.host;
|
|
}
|
|
var param = +el + 1;
|
|
return function(el) {
|
|
if (!el.href) return;
|
|
|
|
var url = window.location + ''
|
|
, href = el + '';
|
|
|
|
return truncateUrl(url, param) === truncateUrl(href, param);
|
|
};
|
|
},
|
|
':default': function(el) {
|
|
return !!el.defaultSelected;
|
|
},
|
|
':valid': function(el) {
|
|
return el.willValidate || (el.validity && el.validity.valid);
|
|
},
|
|
':invalid': function(el) {
|
|
return !selectors[':valid'](el);
|
|
},
|
|
':in-range': function(el) {
|
|
return el.value > el.min && el.value <= el.max;
|
|
},
|
|
':out-of-range': function(el) {
|
|
return !selectors[':in-range'](el);
|
|
},
|
|
':required': function(el) {
|
|
return !!el.required;
|
|
},
|
|
':optional': function(el) {
|
|
return !el.required;
|
|
},
|
|
':read-only': function(el) {
|
|
if (el.readOnly) return true;
|
|
|
|
var attr = el.getAttribute('contenteditable')
|
|
, prop = el.contentEditable
|
|
, name = el.nodeName.toLowerCase();
|
|
|
|
name = name !== 'input' && name !== 'textarea';
|
|
|
|
return (name || el.disabled) && attr == null && prop !== 'true';
|
|
},
|
|
':read-write': function(el) {
|
|
return !selectors[':read-only'](el);
|
|
},
|
|
':hover': function() {
|
|
throw new Error(':hover is not supported.');
|
|
},
|
|
':active': function() {
|
|
throw new Error(':active is not supported.');
|
|
},
|
|
':link': function() {
|
|
throw new Error(':link is not supported.');
|
|
},
|
|
':visited': function() {
|
|
throw new Error(':visited is not supported.');
|
|
},
|
|
':column': function() {
|
|
throw new Error(':column is not supported.');
|
|
},
|
|
':nth-column': function() {
|
|
throw new Error(':nth-column is not supported.');
|
|
},
|
|
':nth-last-column': function() {
|
|
throw new Error(':nth-last-column is not supported.');
|
|
},
|
|
':current': function() {
|
|
throw new Error(':current is not supported.');
|
|
},
|
|
':past': function() {
|
|
throw new Error(':past is not supported.');
|
|
},
|
|
':future': function() {
|
|
throw new Error(':future is not supported.');
|
|
},
|
|
// Non-standard, for compatibility purposes.
|
|
':contains': function(param) {
|
|
return function(el) {
|
|
var text = el.innerText || el.textContent || el.value || '';
|
|
return text.indexOf(param) !== -1;
|
|
};
|
|
},
|
|
':has': function(param) {
|
|
return function(el) {
|
|
return find(param, el).length > 0;
|
|
};
|
|
}
|
|
// Potentially add more pseudo selectors for
|
|
// compatibility with sizzle and most other
|
|
// selector engines (?).
|
|
};
|
|
|
|
/**
|
|
* Attribute Operators
|
|
*/
|
|
|
|
var operators = {
|
|
'-': function() {
|
|
return true;
|
|
},
|
|
'=': function(attr, val) {
|
|
return attr === val;
|
|
},
|
|
'*=': function(attr, val) {
|
|
return attr.indexOf(val) !== -1;
|
|
},
|
|
'~=': function(attr, val) {
|
|
var i
|
|
, s
|
|
, f
|
|
, l;
|
|
|
|
for (s = 0; true; s = i + 1) {
|
|
i = attr.indexOf(val, s);
|
|
if (i === -1) return false;
|
|
f = attr[i - 1];
|
|
l = attr[i + val.length];
|
|
if ((!f || f === ' ') && (!l || l === ' ')) return true;
|
|
}
|
|
},
|
|
'|=': function(attr, val) {
|
|
var i = attr.indexOf(val)
|
|
, l;
|
|
|
|
if (i !== 0) return;
|
|
l = attr[i + val.length];
|
|
|
|
return l === '-' || !l;
|
|
},
|
|
'^=': function(attr, val) {
|
|
return attr.indexOf(val) === 0;
|
|
},
|
|
'$=': function(attr, val) {
|
|
var i = attr.lastIndexOf(val);
|
|
return i !== -1 && i + val.length === attr.length;
|
|
},
|
|
// non-standard
|
|
'!=': function(attr, val) {
|
|
return attr !== val;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Combinator Logic
|
|
*/
|
|
|
|
var combinators = {
|
|
' ': function(test) {
|
|
return function(el) {
|
|
/*jshint -W084 */
|
|
while (el = el.parentNode) {
|
|
if (test(el)) return el;
|
|
}
|
|
};
|
|
},
|
|
'>': function(test) {
|
|
return function(el) {
|
|
/*jshint -W084 */
|
|
if (el = el.parentNode) {
|
|
return test(el) && el;
|
|
}
|
|
};
|
|
},
|
|
'+': function(test) {
|
|
return function(el) {
|
|
/*jshint -W084 */
|
|
if (el = prev(el)) {
|
|
return test(el) && el;
|
|
}
|
|
};
|
|
},
|
|
'~': function(test) {
|
|
return function(el) {
|
|
/*jshint -W084 */
|
|
while (el = prev(el)) {
|
|
if (test(el)) return el;
|
|
}
|
|
};
|
|
},
|
|
'noop': function(test) {
|
|
return function(el) {
|
|
return test(el) && el;
|
|
};
|
|
},
|
|
'ref': function(test, name) {
|
|
var node;
|
|
|
|
function ref(el) {
|
|
var doc = el.ownerDocument
|
|
, nodes = doc.getElementsByTagName('*')
|
|
, i = nodes.length;
|
|
|
|
while (i--) {
|
|
node = nodes[i];
|
|
if (ref.test(el)) {
|
|
node = null;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
node = null;
|
|
}
|
|
|
|
ref.combinator = function(el) {
|
|
if (!node || !node.getAttribute) return;
|
|
|
|
var attr = node.getAttribute(name) || '';
|
|
if (attr[0] === '#') attr = attr.substring(1);
|
|
|
|
if (attr === el.id && test(node)) {
|
|
return node;
|
|
}
|
|
};
|
|
|
|
return ref;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Grammar
|
|
*/
|
|
|
|
var rules = {
|
|
escape: /\\(?:[^0-9A-Fa-f\r\n]|[0-9A-Fa-f]{1,6}[\r\n\t ]?)/g,
|
|
str_escape: /(escape)|\\(\n|\r\n?|\f)/g,
|
|
nonascii: /[\u00A0-\uFFFF]/,
|
|
cssid: /(?:(?!-?[0-9])(?:escape|nonascii|[-_a-zA-Z0-9])+)/,
|
|
qname: /^ *(cssid|\*)/,
|
|
simple: /^(?:([.#]cssid)|pseudo|attr)/,
|
|
ref: /^ *\/(cssid)\/ */,
|
|
combinator: /^(?: +([^ \w*.#\\]) +|( )+|([^ \w*.#\\]))(?! *$)/,
|
|
attr: /^\[(cssid)(?:([^\w]?=)(inside))?\]/,
|
|
pseudo: /^(:cssid)(?:\((inside)\))?/,
|
|
inside: /(?:"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|<[^"'>]*>|\\["'>]|[^"'>])*/,
|
|
ident: /^(cssid)$/
|
|
};
|
|
|
|
rules.cssid = replace(rules.cssid, 'nonascii', rules.nonascii);
|
|
rules.cssid = replace(rules.cssid, 'escape', rules.escape);
|
|
rules.qname = replace(rules.qname, 'cssid', rules.cssid);
|
|
rules.simple = replace(rules.simple, 'cssid', rules.cssid);
|
|
rules.ref = replace(rules.ref, 'cssid', rules.cssid);
|
|
rules.attr = replace(rules.attr, 'cssid', rules.cssid);
|
|
rules.pseudo = replace(rules.pseudo, 'cssid', rules.cssid);
|
|
rules.inside = replace(rules.inside, '[^"\'>]*', rules.inside);
|
|
rules.attr = replace(rules.attr, 'inside', makeInside('\\[', '\\]'));
|
|
rules.pseudo = replace(rules.pseudo, 'inside', makeInside('\\(', '\\)'));
|
|
rules.simple = replace(rules.simple, 'pseudo', rules.pseudo);
|
|
rules.simple = replace(rules.simple, 'attr', rules.attr);
|
|
rules.ident = replace(rules.ident, 'cssid', rules.cssid);
|
|
rules.str_escape = replace(rules.str_escape, 'escape', rules.escape);
|
|
|
|
/**
|
|
* Compiling
|
|
*/
|
|
|
|
var compile = function(sel_) {
|
|
var sel = sel_.replace(/^\s+|\s+$/g, '')
|
|
, test
|
|
, filter = []
|
|
, buff = []
|
|
, subject
|
|
, qname
|
|
, cap
|
|
, op
|
|
, ref;
|
|
|
|
/*jshint -W084 */
|
|
while (sel) {
|
|
if (cap = rules.qname.exec(sel)) {
|
|
sel = sel.substring(cap[0].length);
|
|
qname = decodeid(cap[1]);
|
|
buff.push(tok(qname, true));
|
|
} else if (cap = rules.simple.exec(sel)) {
|
|
sel = sel.substring(cap[0].length);
|
|
qname = '*';
|
|
buff.push(tok(qname, true));
|
|
buff.push(tok(cap));
|
|
} else {
|
|
throw new SyntaxError('Invalid selector.');
|
|
}
|
|
|
|
while (cap = rules.simple.exec(sel)) {
|
|
sel = sel.substring(cap[0].length);
|
|
buff.push(tok(cap));
|
|
}
|
|
|
|
if (sel[0] === '!') {
|
|
sel = sel.substring(1);
|
|
subject = makeSubject();
|
|
subject.qname = qname;
|
|
buff.push(subject.simple);
|
|
}
|
|
|
|
if (cap = rules.ref.exec(sel)) {
|
|
sel = sel.substring(cap[0].length);
|
|
ref = combinators.ref(makeSimple(buff), decodeid(cap[1]));
|
|
filter.push(ref.combinator);
|
|
buff = [];
|
|
continue;
|
|
}
|
|
|
|
if (cap = rules.combinator.exec(sel)) {
|
|
sel = sel.substring(cap[0].length);
|
|
op = cap[1] || cap[2] || cap[3];
|
|
if (op === ',') {
|
|
filter.push(combinators.noop(makeSimple(buff)));
|
|
break;
|
|
}
|
|
} else {
|
|
op = 'noop';
|
|
}
|
|
|
|
if (!combinators[op]) { throw new SyntaxError('Bad combinator.'); }
|
|
filter.push(combinators[op](makeSimple(buff)));
|
|
buff = [];
|
|
}
|
|
|
|
test = makeTest(filter);
|
|
test.qname = qname;
|
|
test.sel = sel;
|
|
|
|
if (subject) {
|
|
subject.lname = test.qname;
|
|
|
|
subject.test = test;
|
|
subject.qname = subject.qname;
|
|
subject.sel = test.sel;
|
|
test = subject;
|
|
}
|
|
|
|
if (ref) {
|
|
ref.test = test;
|
|
ref.qname = test.qname;
|
|
ref.sel = test.sel;
|
|
test = ref;
|
|
}
|
|
|
|
return test;
|
|
};
|
|
|
|
var tok = function(cap, qname) {
|
|
// qname
|
|
if (qname) {
|
|
return cap === '*'
|
|
? selectors['*']
|
|
: selectors.type(cap);
|
|
}
|
|
|
|
// class/id
|
|
if (cap[1]) {
|
|
return cap[1][0] === '.'
|
|
// XXX unescape here? or in attr?
|
|
? selectors.attr('class', '~=', decodeid(cap[1].substring(1)), false)
|
|
: selectors.attr('id', '=', decodeid(cap[1].substring(1)), false);
|
|
}
|
|
|
|
// pseudo-name
|
|
// inside-pseudo
|
|
if (cap[2]) {
|
|
return cap[3]
|
|
? selectors[decodeid(cap[2])](unquote(cap[3]))
|
|
: selectors[decodeid(cap[2])];
|
|
}
|
|
|
|
// attr name
|
|
// attr op
|
|
// attr value
|
|
if (cap[4]) {
|
|
var value = cap[6];
|
|
var i = /["'\s]\s*I$/i.test(value);
|
|
if (i) {
|
|
value = value.replace(/\s*I$/i, '');
|
|
}
|
|
return selectors.attr(decodeid(cap[4]), cap[5] || '-', unquote(value), i);
|
|
}
|
|
|
|
throw new SyntaxError('Unknown Selector.');
|
|
};
|
|
|
|
var makeSimple = function(func) {
|
|
var l = func.length
|
|
, i;
|
|
|
|
// Potentially make sure
|
|
// `el` is truthy.
|
|
if (l < 2) return func[0];
|
|
|
|
return function(el) {
|
|
if (!el) return;
|
|
for (i = 0; i < l; i++) {
|
|
if (!func[i](el)) return;
|
|
}
|
|
return true;
|
|
};
|
|
};
|
|
|
|
var makeTest = function(func) {
|
|
if (func.length < 2) {
|
|
return function(el) {
|
|
return !!func[0](el);
|
|
};
|
|
}
|
|
return function(el) {
|
|
var i = func.length;
|
|
while (i--) {
|
|
if (!(el = func[i](el))) return;
|
|
}
|
|
return true;
|
|
};
|
|
};
|
|
|
|
var makeSubject = function() {
|
|
var target;
|
|
|
|
function subject(el) {
|
|
var node = el.ownerDocument
|
|
, scope = node.getElementsByTagName(subject.lname)
|
|
, i = scope.length;
|
|
|
|
while (i--) {
|
|
if (subject.test(scope[i]) && target === el) {
|
|
target = null;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
target = null;
|
|
}
|
|
|
|
subject.simple = function(el) {
|
|
target = el;
|
|
return true;
|
|
};
|
|
|
|
return subject;
|
|
};
|
|
|
|
var compileGroup = function(sel) {
|
|
var test = compile(sel)
|
|
, tests = [ test ];
|
|
|
|
while (test.sel) {
|
|
test = compile(test.sel);
|
|
tests.push(test);
|
|
}
|
|
|
|
if (tests.length < 2) return test;
|
|
|
|
return function(el) {
|
|
var l = tests.length
|
|
, i = 0;
|
|
|
|
for (; i < l; i++) {
|
|
if (tests[i](el)) return true;
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Selection
|
|
*/
|
|
|
|
var find = function(sel, node) {
|
|
var results = []
|
|
, test = compile(sel)
|
|
, scope = node.getElementsByTagName(test.qname)
|
|
, i = 0
|
|
, el;
|
|
|
|
/*jshint -W084 */
|
|
while (el = scope[i++]) {
|
|
if (test(el)) results.push(el);
|
|
}
|
|
|
|
if (test.sel) {
|
|
while (test.sel) {
|
|
test = compile(test.sel);
|
|
scope = node.getElementsByTagName(test.qname);
|
|
i = 0;
|
|
/*jshint -W084 */
|
|
while (el = scope[i++]) {
|
|
if (test(el) && indexOf.call(results, el) === -1) {
|
|
results.push(el);
|
|
}
|
|
}
|
|
}
|
|
results.sort(order);
|
|
}
|
|
|
|
return results;
|
|
};
|
|
|
|
/**
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = exports = function(sel, context) {
|
|
/* when context isn't a DocumentFragment and the selector is simple: */
|
|
var id, r;
|
|
if (context.nodeType !== 11 && sel.indexOf(' ') === -1) {
|
|
if (sel[0] === '#' && context.rooted && /^#[A-Z_][-A-Z0-9_]*$/i.test(sel)) {
|
|
if (context.doc._hasMultipleElementsWithId) {
|
|
id = sel.substring(1);
|
|
if (!context.doc._hasMultipleElementsWithId(id)) {
|
|
r = context.doc.getElementById(id);
|
|
return r ? [r] : [];
|
|
}
|
|
}
|
|
}
|
|
if (sel[0] === '.' && /^\.\w+$/.test(sel)) {
|
|
return context.getElementsByClassName(sel.substring(1));
|
|
}
|
|
if (/^\w+$/.test(sel)) {
|
|
return context.getElementsByTagName(sel);
|
|
}
|
|
}
|
|
/* do things the hard/slow way */
|
|
return find(sel, context);
|
|
};
|
|
|
|
exports.selectors = selectors;
|
|
exports.operators = operators;
|
|
exports.combinators = combinators;
|
|
|
|
exports.matches = function(el, sel) {
|
|
var test = { sel: sel };
|
|
do {
|
|
test = compile(test.sel);
|
|
if (test(el)) { return true; }
|
|
} while (test.sel);
|
|
return false;
|
|
};
|