From 82bedcee8401b6d7eb3bc9dd4294294946498c24 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Tue, 24 Jul 2007 20:49:55 +0000 Subject: [PATCH] prototype: Optimize Selector#match and Element#match for simple selectors. Closes #9082. --- CHANGELOG | 2 ++ src/selector.js | 58 ++++++++++++++++++++++++++++++++++++++++- test/unit/selector.html | 22 +++++++++++++++- 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3f8b16f..59f5020 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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] diff --git a/src/selector.js b/src/selector.js index d98d099..3880b1d 100644 --- a/src/selector.js +++ b/src/selector.js @@ -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 diff --git a/test/unit/selector.html b/test/unit/selector.html index baa6ba2..90a33b6 100644 --- a/test/unit/selector.html +++ b/test/unit/selector.html @@ -61,7 +61,7 @@
- + @@ -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!"]'));