Showing with 79 additions and 73 deletions.
  1. +65 −67 sizzle.js
  2. +14 −6 test/unit/selector.js
132 sizzle.js
@@ -29,36 +29,35 @@ var cachedruns,
// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
whitespace = "[\\x20\\t\\r\\n\\f]",
// http://www.w3.org/TR/css3-syntax/#characters
characterEncoding = "(?:[-\\w]|[^\\x00-\\xa0]|\\\\.)",
characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])",

// Javascript identifier syntax (with added # for unquoted hash)
identifier = "(?:-?[#_a-zA-Z][-\\w]*|[^\\x00-\\xa0]|\\\\.)",
// Loosely modeled on Javascript identifier characters
identifier = "(?:[\\w#_-]|[^\\x00-\\xa0]|\\\\.)",
// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
operators = "([*^$|!~]?=)",
attributes = "\\[" + whitespace + "*(" + characterEncoding + "+)" + whitespace +
"*(?:" + operators + whitespace + "*(?:(['\"])((?:[^\\\\]|\\\\.)*?)\\3|(" + identifier + "+)|)|)" + whitespace + "*\\]",

pseudos = ":(" + characterEncoding + "+)(?:\\((?:(['\"])((?:[^\\\\]|\\\\.)*)\\2|([^()]*|.*))\\))?",
"*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + "+)|)|)" + whitespace + "*\\]",
pseudos = ":(" + characterEncoding + "+)(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|(.*))\\)|)",
pos = ":(nth|eq|gt|lt|first|last|even|odd)(?:\\((\\d*)\\)|)(?=[^-]|$)",
combinators = whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*",
groups = "(?=[^\\x20\\t\\r\\n\\f])(?:\\\\.|" + attributes + "|" + pseudos.replace( 2, 6 ) + "|[^\\\\(),])+",

// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),

rcombinators = new RegExp( "^" + combinators ),
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),

rtrailingPseudo = new RegExp("((?:\\)(?!" + whitespace + "*:)|[^)])*)\\)" + whitespace + "*:(?!\\\\)"),
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
// All simple (non-comma) selectors, excluding insignifant trailing whitespace
rgroups = new RegExp( groups + "?(?=" + whitespace + "*,|$)", "g" ),

rtokens = new RegExp( "(?:" +
attributes + "|" +
pseudos.replace( 2, 6 ) +
"|[^\\\\\\x20\\t\\r\\n\\f>+~]|\\\\.)+|" +
combinators, "g" ),
// A selector, or everything after leading whitespace
// Optionally followed in either case by a ")" for terminating sub-selectors
rselector = new RegExp( "^(?:(?!,)(?:(?:^|,)" + whitespace + "*" + groups + ")*?|" + whitespace + "*(.*?))(\\)|$)" ),

rgroups = new RegExp( "(?=[^\\x20\\t\\r\\n\\f])(?:" +
attributes + "|" +
pseudos.replace( 2, 6 ) +
"|[^\\\\,()[\\]])+", "g"),
// All combinators and selector components (attribute test, tag, pseudo, etc.), the latter appearing together when consecutive
rtokens = new RegExp( groups.slice( 19, -6 ) + "\\x20\\t\\r\\n\\f>+~])+|" + combinators, "g" ),

// Easily-parseable/retrievable ID or TAG or CLASS selectors
rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,

rsibling = /^[\x20\t\r\n\f]*[+~]/,
@@ -355,29 +354,24 @@ var Expr = Sizzle.selectors = {
},

"PSEUDO": function( match ) {
var unquoted, beforeClosing;
var argument,
unquoted = match[4];

if ( matchExpr["CHILD"].test( match[0] ) ) {
return null;
}

// Clean up unquoted
if ( (unquoted = match[4]) ) {

// Check if we've picked up trailing pseudos
if ( (beforeClosing = rtrailingPseudo.exec( unquoted )) && !beforeClosing.index ) {
unquoted = beforeClosing[1];
match[0] = match[0].slice( 0, match[0].indexOf( unquoted ) + unquoted.length + 1 );
}
// Relinquish our claim on characters in `unquoted` from a closing parenthesis on
if ( unquoted && (argument = rselector.exec( unquoted )) && argument.pop() ) {

match[2] = unquoted;
} else {
match[2] = match[3];
match[0] = match[0].slice( 0, argument[0].length - unquoted.length - 1 );
unquoted = argument[0].slice( 0, -1 );
}

// Reduce the match to needed captures for passing
// arguments to the pseudo filter method
return match.slice( 0, 3 );
// Quoted or unquoted, we have the full argument
// Return only captures needed by the pseudo filter method (type and argument)
match.splice( 2, 3, unquoted || match[3] );
return match;
}
},

@@ -419,7 +413,7 @@ var Expr = Sizzle.selectors = {
}
}
return function( elem ) {
return pattern.test( elem.className || elem.getAttribute("class") || "" );
return pattern.test( elem.className || (elem.getAttribute && elem.getAttribute("class")) || "" );
};
},

@@ -1045,54 +1039,58 @@ function handlePOS( selector, context, results, seed, groups ) {
}

function tokenize( selector, context, xml ) {
var match, tokens, type,
unmatched = true,
var tokens, soFar, type,
groups = [],
soFar = selector,
i = 0,

// Catch obvious selector issues: terminal ")"; nonempty fallback match
// rselector never fails to match *something*
match = rselector.exec( selector ),
matched = !match.pop() && !match.pop(),
selectorGroups = matched && selector.match( rgroups ) || [""],

preFilters = Expr.preFilter,
filters = Expr.filter,
checkContext = !xml && context !== document;

while ( soFar ) {
for ( ; (soFar = selectorGroups[i]) != null && matched; i++ ) {
groups.push( tokens = [] );

// Comma or start
if ( unmatched || (match = rcomma.exec( soFar )) ) {
groups.push(tokens = []);
if ( match ) {
// Need to make sure we're within a narrower context if necessary
// Adding a descendant combinator will generate what is needed
if ( checkContext ) {
soFar = " " + soFar;
}

while ( soFar ) {
matched = false;

// Combinators
if ( (match = rcombinators.exec( soFar )) ) {
soFar = soFar.slice( match[0].length );
}

// Need to make sure we're within a narrower context if necessary
// Adding a descendant combinator will generate what is needed
if ( checkContext ) {
soFar = " " + soFar;
// Cast descendant combinators to space
matched = tokens.push({ part: match.pop().replace( rtrim, " " ), captures: match });
}
}
unmatched = true;

// Combinators
if ( (match = rcombinators.exec( soFar )) ) {
soFar = soFar.slice( match[0].length );

// Cast whitespace combinators to space
tokens.push({ part: match.pop().replace(rtrim, " "), captures: match });
unmatched = false;
}
// Filters
for ( type in filters ) {
if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
(match = preFilters[ type ]( match, context, xml )) ) ) {

// Filters
for ( type in filters ) {
if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
(match = preFilters[ type ]( match, context, xml )) ) ) {
soFar = soFar.slice( match.shift().length );
matched = tokens.push({ part: type, captures: match });
}
}

soFar = soFar.slice( match.shift().length );
tokens.push({ part: type, captures: match });
unmatched = false;
if ( !matched ) {
break;
}
}
}

if ( unmatched ) {
Sizzle.error( selector );
}
if ( !matched ) {
Sizzle.error( selector );
}

return groups;
@@ -152,7 +152,7 @@ test("XML Document Selectors", function() {
});

test("broken", function() {
expect( 21 );
expect( 22 );

function broken( name, selector ) {
raises(function() {
@@ -189,6 +189,7 @@ test("broken", function() {
broken( "First-child", ":first-child(n)" );
broken( "Last-child", ":last-child(n)" );
broken( "Only-child", ":only-child(n)" );
broken( "Missing quotes", "a:contains(Google Groups (Link))" );

// Make sure attribute value quoting works correctly. See: #6093
var attrbad = jQuery('<input type="hidden" value="2" name="foo.baz" id="attrbad1"/><input type="hidden" value="2" name="foo[baz]" id="attrbad2"/>').appendTo("body");
@@ -252,7 +253,7 @@ test("id", function() {
});

test("class", function() {
expect( 22 );
expect( 24 );

t( "Class Selector", ".blog", ["mark","simon"] );
t( "Class Selector", ".GROUPS", ["groups"] );
@@ -287,6 +288,8 @@ test("class", function() {
div.className = "null";
ok( Sizzle.matchesSelector( div, ".null"), ".null matches element with class 'null'" );
ok( Sizzle.matchesSelector( div.firstChild, ".null div"), "caching system respects DOM changes" );
ok( !Sizzle.matchesSelector( document, ".foo" ), "testing class on document doesn't error" );
ok( !Sizzle.matchesSelector( window, ".foo" ), "testing class on window doesn't error" );
});

test("name", function() {
@@ -389,7 +392,7 @@ test("child and adjacent", function() {
});

test("attributes", function() {
expect( 52 );
expect( 54 );

t( "Attribute Exists", "#qunit-fixture a[title]", ["google"] );
t( "Attribute Exists (case-insensitive)", "#qunit-fixture a[TITLE]", ["google"] );
@@ -405,6 +408,8 @@ test("attributes", function() {
t( "Attribute Equals", "#qunit-fixture a[rel=bookmark]", ["simon1"] );
t( "Attribute Equals", "#qunit-fixture a[href='http://www.google.com/']", ["google"] );
t( "Attribute Equals", "#qunit-fixture a[ rel = 'bookmark' ]", ["simon1"] );
t( "Attribute Equals Number", "#qunit-fixture option[value=1]", ["option1b","option2b","option3b","option4b","option5c"] );
t( "Attribute Equals Number", "#qunit-fixture li[tabindex=-1]", ["foodWithNegativeTabIndex"] );

document.getElementById("anchor2").href = "#2";
t( "href Attribute", "p a[href^=#]", ["anchor2"] );
@@ -543,7 +548,7 @@ test("pseudo - child", function() {
});

test("pseudo - misc", function() {
expect( 28 );
expect( 30 );

t( "Headers", ":header", ["qunit-header", "qunit-banner", "qunit-userAgent"] );
t( "Headers(case-insensitive)", ":Header", ["qunit-header", "qunit-banner", "qunit-userAgent"] );
@@ -560,8 +565,6 @@ test("pseudo - misc", function() {

t( "Text Contains", "a:contains('Google Groups (Link)')", ["groups"] );
t( "Text Contains", "a:contains(\"(Link)\")", ["groups"] );
t( "Text Contains", "a:contains(Google Groups (Link))", ["groups"] );
t( "Text Contains", "a:contains((Link))", ["groups"] );

var tmp = document.createElement("div");
tmp.id = "tmp_input";
@@ -617,6 +620,11 @@ test("pseudo - misc", function() {

t( "Sequential pseudos", "#qunit-fixture p:has(:contains(mark)):has(code)", ["ap"] );
t( "Sequential pseudos", "#qunit-fixture p:has(:contains(mark)):has(code):contains(This link)", ["ap"] );

t( "Pseudo argument containing ')'", "p:has(>a.GROUPS[src!=')'])", ["ap"] );
t( "Pseudo argument containing ')'", "p:has(>a.GROUPS[src!=')'])", ["ap"] );
t( "Pseudo followed by token containing ')'", "p:contains(id=\"foo\")[id!=\\)]", ["sndp"] );
t( "Pseudo followed by token containing ')'", "p:contains(id=\"foo\")[id!=')']", ["sndp"] );
});