Added UTF8 Selector support (#1001) and fixed infinite selector loops (#1025). You can now select elements by UTF8-based IDs, classes, and attributes.

This commit is contained in:
John Resig 2007-03-25 00:58:19 +00:00
parent ec2b688920
commit af79bb1f25
3 changed files with 210 additions and 169 deletions

View file

@ -1,5 +1,6 @@
<html id="html"> <html id="html">
<head> <head>
<meta http-equiv="Content-Type" content="text/css; charset=utf-8" />
<title>jQuery Test Suite</title> <title>jQuery Test Suite</title>
<link rel="Stylesheet" media="screen" href="data/testsuite.css" /> <link rel="Stylesheet" media="screen" href="data/testsuite.css" />
<script type="text/javascript" src="../dist/jquery.js"></script> <script type="text/javascript" src="../dist/jquery.js"></script>
@ -77,6 +78,11 @@
<param name="p1" value="x1" /> <param name="p1" value="x1" />
<param name="p2" value="x2" /> <param name="p2" value="x2" />
</object> </object>
<span id="台北Táiběi"></span>
<span id="台北" lang="中文"></span>
<span id="utf8class1" class="台北Táiběi 台北"></span>
<span id="utf8class2" class="台北"></span>
</form> </form>
<b id="floatTest">Float test.</b> <b id="floatTest">Float test.</b>
<iframe id="iframe"></iframe> <iframe id="iframe"></iframe>

View file

@ -67,16 +67,16 @@ jQuery.extend({
// The regular expressions that power the parsing engine // The regular expressions that power the parsing engine
parse: [ parse: [
// Match: [@value='test'], [@foo] // Match: [@value='test'], [@foo]
/^\[ *(@)([a-z0-9_-]*) *([!*$^=]*) *('?"?)(.*?)\4 *\]/i, /^\[ *(@)([\w-]+) *([!*$^=]*) *('?"?)(.*?)\4 *\]/i,
// Match: [div], [div p] // Match: [div], [div p]
/^(\[)\s*(.*?(\[.*?\])?[^[]*?)\s*\]/, /^(\[)\s*(.*?(\[.*?\])?[^[]*?)\s*\]/,
// Match: :contains('foo') // Match: :contains('foo')
/^(:)([a-z0-9_-]*)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/i, /^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/i,
// Match: :even, :last-chlid // Match: :even, :last-chlid, #id, .class
/^([:.#]*)([a-z0-9_*-]*)/i /^([:.#]*)([\w\u0128-\uFFFF*-]+)/i
], ],
token: [ token: [
@ -134,7 +134,7 @@ jQuery.extend({
} }
// Initialize the search // Initialize the search
var ret = [context], done = [], last = null; var ret = [context], done = [], last;
// Continue while a selector expression exists, and while // Continue while a selector expression exists, and while
// we're no longer looping upon ourselves // we're no longer looping upon ourselves
@ -142,13 +142,13 @@ jQuery.extend({
var r = []; var r = [];
last = t; last = t;
t = jQuery.trim(t).replace( /^\/\//i, "" ); t = jQuery.trim(t).replace( /^\/\//, "" );
var foundToken = false; var foundToken = false;
// An attempt at speeding up child selectors that // An attempt at speeding up child selectors that
// point to a specific element tag // point to a specific element tag
var re = /^[\/>]\s*([a-z0-9*-]+)/i; var re = /^[\/>]\s*([\w*-]+)/i;
var m = re.exec(t); var m = re.exec(t);
if ( m ) { if ( m ) {
@ -205,7 +205,7 @@ jQuery.extend({
} else { } else {
// Optomize for the case nodeName#idName // Optomize for the case nodeName#idName
var re2 = /^([a-z0-9_-]+)(#)([a-z0-9\\*_-]*)/i; var re2 = /^(\w+)(#)([\w\u0128-\uFFFF*-]+)/i;
var m = re2.exec(t); var m = re2.exec(t);
// Re-organize the results, so that they're consistent // Re-organize the results, so that they're consistent
@ -215,21 +215,21 @@ jQuery.extend({
} else { } else {
// Otherwise, do a traditional filter check for // Otherwise, do a traditional filter check for
// ID, class, and element selectors // ID, class, and element selectors
re2 = /^([#.]?)([a-z0-9\\*_-]*)/i; re2 = /^([#.]?)([\w\u0128-\uFFFF*-]*)/i;
m = re2.exec(t); m = re2.exec(t);
} }
var last = ret[ret.length-1]; var elem = ret[ret.length-1];
// Try to do a global search by ID, where we can // Try to do a global search by ID, where we can
if ( m[1] == "#" && last && last.getElementById ) { if ( m[1] == "#" && elem && elem.getElementById ) {
// Optimization for HTML document case // Optimization for HTML document case
var oid = last.getElementById(m[2]); var oid = elem.getElementById(m[2]);
// Do a quick check for the existence of the actual ID attribute // Do a quick check for the existence of the actual ID attribute
// to avoid selecting by the name attribute in IE // to avoid selecting by the name attribute in IE
if ( jQuery.browser.msie && oid && oid.id != m[2] ) if ( jQuery.browser.msie && oid && oid.id != m[2] )
oid = jQuery('[@id="'+m[2]+'"]', last)[0]; oid = jQuery('[@id="'+m[2]+'"]', elem)[0];
// Do a quick check for node name (where applicable) so // Do a quick check for node name (where applicable) so
// that div#foo searches will be really fast // that div#foo searches will be really fast
@ -292,8 +292,14 @@ jQuery.extend({
} }
} }
// An error occurred with the selector;
// just return an empty set instead
if ( t )
ret = [];
// Remove the root context // Remove the root context
if ( ret && ret[0] == context ) ret.shift(); if ( ret && ret[0] == context )
ret.shift();
// And combine the results // And combine the results
done = jQuery.merge( done, ret ); done = jQuery.merge( done, ret );
@ -302,8 +308,11 @@ jQuery.extend({
}, },
filter: function(t,r,not) { filter: function(t,r,not) {
var last;
// Look for common filter expressions // Look for common filter expressions
while ( t && /^[a-z[({<*:.#]/i.test(t) ) { while ( t && t != last ) {
last = t;
var p = jQuery.parse, m; var p = jQuery.parse, m;
@ -325,6 +334,9 @@ jQuery.extend({
} }
}); });
if ( !m )
continue;
// :not() is a special case that can be optimized by // :not() is a special case that can be optimized by
// keeping it out of the expression list // keeping it out of the expression list
if ( m[1] == ":" && m[2] == "not" ) if ( m[1] == ":" && m[2] == "not" )

View file

@ -1,6 +1,6 @@
module("selector"); module("selector");
test("expressions - element", function() { test("element", function() {
expect(6); expect(6);
ok( $("*").size() >= 30, "Select all" ); ok( $("*").size() >= 30, "Select all" );
t( "Element Selector", "div", ["main","foo"] ); t( "Element Selector", "div", ["main","foo"] );
@ -10,13 +10,28 @@ test("expressions - element", function() {
ok( $("param", "#object1").length == 2, "Object/param as context" ); ok( $("param", "#object1").length == 2, "Object/param as context" );
}); });
test("expressions - id", function() { test("broken", function() {
expect(13); expect(7);
t( "Broken Selector", "[", [] );
t( "Broken Selector", "(", [] );
t( "Broken Selector", "{", [] );
t( "Broken Selector", "<", [] );
t( "Broken Selector", "()", [] );
t( "Broken Selector", "<>", [] );
t( "Broken Selector", "{}", [] );
});
test("id", function() {
expect(17);
t( "ID Selector", "#body", ["body"] ); t( "ID Selector", "#body", ["body"] );
t( "ID Selector w/ Element", "body#body", ["body"] ); t( "ID Selector w/ Element", "body#body", ["body"] );
t( "ID Selector w/ Element", "ul#first", [] ); t( "ID Selector w/ Element", "ul#first", [] );
t( "ID selector with existing ID descendant", "#firstp #simon1", ["simon1"] ); t( "ID selector with existing ID descendant", "#firstp #simon1", ["simon1"] );
t( "ID selector with non-existant descendant", "#firstp #foobar", [] ); t( "ID selector with non-existant descendant", "#firstp #foobar", [] );
t( "ID selector using UTF8", "#台北Táiběi", ["台北Táiběi"] );
t( "Multiple ID selectors using UTF8", "#台北Táiběi, #台北", ["台北Táiběi","台北"] );
t( "Descendant ID selector using UTF8", "div #台北", ["台北"] );
t( "Child ID selector using UTF8", "form > #台北", ["台北"] );
t( "ID Selector, child ID present", "#form > #radio1", ["radio1"] ); // bug #267 t( "ID Selector, child ID present", "#form > #radio1", ["radio1"] ); // bug #267
t( "ID Selector, not an ancestor ID", "#form #first", [] ); t( "ID Selector, not an ancestor ID", "#form #first", [] );
@ -32,16 +47,22 @@ test("expressions - id", function() {
t( "ID selector with non-existant ancestor", "#asdfasdf #foobar", [] ); // bug #986 t( "ID selector with non-existant ancestor", "#asdfasdf #foobar", [] ); // bug #986
}); });
test("class", function() {
test("expressions - class", function() { expect(10);
expect(4);
t( "Class Selector", ".blog", ["mark","simon"] ); t( "Class Selector", ".blog", ["mark","simon"] );
t( "Class Selector", ".blog.link", ["simon"] ); t( "Class Selector", ".blog.link", ["simon"] );
t( "Class Selector w/ Element", "a.blog", ["mark","simon"] ); t( "Class Selector w/ Element", "a.blog", ["mark","simon"] );
t( "Parent Class Selector", "p .blog", ["mark","simon"] ); t( "Parent Class Selector", "p .blog", ["mark","simon"] );
t( "Class selector using UTF8", ".台北Táiběi", ["utf8class1"] );
t( "Class selector using UTF8", ".台北", ["utf8class1","utf8class2"] );
t( "Class selector using UTF8", ".台北Táiběi.台北", ["utf8class1"] );
t( "Class selector using UTF8", ".台北Táiběi, .台北", ["utf8class1","utf8class2"] );
t( "Descendant class selector using UTF8", "div .台北Táiběi", ["utf8class1"] );
t( "Child class selector using UTF8", "form > .台北Táiběi", ["utf8class1"] );
}); });
test("expressions - multiple", function() { test("multiple", function() {
expect(4); expect(4);
t( "Comma Support", "a.blog, div", ["mark","simon","main","foo"] ); t( "Comma Support", "a.blog, div", ["mark","simon","main","foo"] );
t( "Comma Support", "a.blog , div", ["mark","simon","main","foo"] ); t( "Comma Support", "a.blog , div", ["mark","simon","main","foo"] );
@ -49,7 +70,7 @@ test("expressions - multiple", function() {
t( "Comma Support", "a.blog,div", ["mark","simon","main","foo"] ); t( "Comma Support", "a.blog,div", ["mark","simon","main","foo"] );
}); });
test("expressions - child and adjacent", function() { test("child and adjacent", function() {
expect(14); expect(14);
t( "Child", "p > a", ["simon1","google","groups","mark","yahoo","simon"] ); t( "Child", "p > a", ["simon1","google","groups","mark","yahoo","simon"] );
t( "Child", "p> a", ["simon1","google","groups","mark","yahoo","simon"] ); t( "Child", "p> a", ["simon1","google","groups","mark","yahoo","simon"] );
@ -67,8 +88,8 @@ test("expressions - child and adjacent", function() {
t( "First Child", "p:first-child", ["firstp","sndp"] ); t( "First Child", "p:first-child", ["firstp","sndp"] );
}); });
test("expressions - attributes", function() { test("attributes", function() {
expect(19); expect(20);
t( "Attribute Exists", "a[@title]", ["google"] ); t( "Attribute Exists", "a[@title]", ["google"] );
t( "Attribute Exists", "*[@title]", ["google"] ); t( "Attribute Exists", "*[@title]", ["google"] );
t( "Attribute Exists", "[@title]", ["google"] ); t( "Attribute Exists", "[@title]", ["google"] );
@ -80,6 +101,8 @@ test("expressions - attributes", function() {
t( "Multiple Attribute Equals", "input[@type=\"hidden\"],input[@type='radio']", ["hidden1","radio1","radio2"] ); t( "Multiple Attribute Equals", "input[@type=\"hidden\"],input[@type='radio']", ["hidden1","radio1","radio2"] );
t( "Multiple Attribute Equals", "input[@type=hidden],input[@type=radio]", ["hidden1","radio1","radio2"] ); t( "Multiple Attribute Equals", "input[@type=hidden],input[@type=radio]", ["hidden1","radio1","radio2"] );
t( "Attribute selector using UTF8", "span[@lang=中文]", ["台北"] );
t( "Attribute Begins With", "a[@href ^= 'http://www']", ["google","yahoo"] ); t( "Attribute Begins With", "a[@href ^= 'http://www']", ["google","yahoo"] );
t( "Attribute Ends With", "a[@href $= 'org/']", ["mark"] ); t( "Attribute Ends With", "a[@href $= 'org/']", ["mark"] );
t( "Attribute Contains", "a[@href *= 'google']", ["google","groups"] ); t( "Attribute Contains", "a[@href *= 'google']", ["google","groups"] );
@ -95,7 +118,7 @@ test("expressions - attributes", function() {
t( ":not() Equals quoted attribute", "select:not([@name='select1'])", ["select2", "select3"]); t( ":not() Equals quoted attribute", "select:not([@name='select1'])", ["select2", "select3"]);
}); });
test("expressions - pseudo (:) selctors", function() { test("pseudo (:) selctors", function() {
expect(30); expect(30);
t( "First Child", "p:first-child", ["firstp","sndp"] ); t( "First Child", "p:first-child", ["firstp","sndp"] );
t( "Last Child", "p:last-child", ["sap"] ); t( "Last Child", "p:last-child", ["sap"] );
@ -131,7 +154,7 @@ test("expressions - pseudo (:) selctors", function() {
t( "Form element :checkbox:checked, :radio:checked", ":checkbox:checked, :radio:checked", ["check1", "radio2"] ); t( "Form element :checkbox:checked, :radio:checked", ":checkbox:checked, :radio:checked", ["check1", "radio2"] );
}); });
test("expressions - basic xpath", function() { test("basic xpath", function() {
expect(15); expect(15);
ok( jQuery.find("//*").length >= 30, "All Elements (//*)" ); ok( jQuery.find("//*").length >= 30, "All Elements (//*)" );
t( "All Div Elements", "//div", ["main","foo"] ); t( "All Div Elements", "//div", ["main","foo"] );