prototype: Optimize Selector#match and Element#match for simple selectors. Closes #9082.

This commit is contained in:
Sam Stephenson 2007-07-24 20:49:55 +00:00
parent a529bcf590
commit 82bedcee84
3 changed files with 80 additions and 2 deletions

View File

@ -1,5 +1,7 @@
*SVN*
* Optimize Selector#match and Element#match for simple selectors. Closes #9082. [Andrew Dupont]
* Remove the dependency on Element.ClassNames from Element#addClassName/removeClassName/toggleClassName, and deprecate Element.ClassNames. Closes #9073. [Tobie Langel]
* Make Element#wrap accept a second argument for setting attributes on the wrapper. Allow wrapping elements which are not part of the document. Closes #9071. [Tobie Langel]

View File

@ -74,7 +74,39 @@ Selector.prototype = {
},
match: function(element) {
return this.findElements(document).include(element);
this.tokens = [];
var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
var le, p, m;
while (e && le !== e && (/\S/).test(e)) {
le = e;
for (var i in ps) {
p = ps[i];
if (m = e.match(p)) {
// use the Selector.assertions methods unless the selector
// is too complex.
if (as[i]) {
this.tokens.push([i, Object.clone(m)]);
e = e.replace(m[0], '');
} else {
// reluctantly do a document-wide search
// and look for a match in the array
return this.findElements(document).include(element);
}
}
}
}
var match = true, name, matches;
for (var i = 0, token; token = this.tokens[i]; i++) {
name = token[0], matches = token[1];
if (!Selector.assertions[name](element, matches)) {
match = false; break;
}
}
return match;
},
toString: function() {
@ -223,6 +255,30 @@ Object.extend(Selector, {
attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/
},
// for Selector.match and Element#match
assertions: {
tagName: function(element, matches) {
return matches[1].toUpperCase() == element.tagName.toUpperCase();
},
className: function(element, matches) {
return Element.hasClassName(element, matches[1]);
},
id: function(element, matches) {
return element.id === matches[1];
},
attrPresence: function(element, matches) {
return Element.hasAttribute(element, matches[1]);
},
attr: function(element, matches) {
var nodeValue = Element.readAttribute(element, matches[1]);
return Selector.operators[matches[2]](nodeValue, matches[3]);
}
},
handlers: {
// UTILITY FUNCTIONS
// joins two collections

View File

@ -61,7 +61,7 @@
</div> <!-- #level1 -->
<div id="dupContainer">
<span id="dupL1">
<span id="dupL1" class="span_foo span_bar">
<span id="dupL2">
<span id="dupL3">
<span id="dupL4">
@ -198,6 +198,26 @@
assertElementMatches(Selector.findElement($('list').descendants(), 'li#item_3'), 'li');
assertEqual(undefined, Selector.findElement($('list').descendants(), 'em'));
}},
testElementMatch: function() {with(this) {
var span = $('dupL1');
// tests that should pass
assert(span.match('span'));
assert(span.match('span#dupL1'));
assert(span.match('div > span'), 'child combinator');
assert(span.match('#dupContainer span'), 'descendant combinator');
assert(span.match('#dupL1'), 'ID only');
assert(span.match('span.span_foo'), 'class name 1');
assert(span.match('span.span_bar'), 'class name 2');
assert(span.match('span:first-child'), 'first-child pseudoclass');
assert(!span.match('span.span_wtf'), 'bogus class name');
assert(!span.match('#dupL2'), 'different ID');
assert(!span.match('div'), 'different tag name');
assert(!span.match('span span'), 'different ancestry');
assert(!span.match('span > span'), 'different parent');
assert(!span.match('span:nth-child(5)'), 'different pseudoclass');
}},
testSelectorWithSpaceInAttributeValue: function() {with(this) {
assertEnumEqual([$('with_title')], $$('cite[title="hello world!"]'));