Changeset 6186
- Timestamp:
- 02/21/07 12:04:56 (1 year ago)
- Files:
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
spinoffs/prototype/branches/selector/src/selector.js
r6183 r6186 84 84 85 85 match: function(element) { 86 // XXX TDD->Andrew: this prevents reusing the Selector on a changed DOM 86 87 this.results = this.results || this.findElements(document); 87 88 return this.results.include(element); 88 //return this.findElements(document).include(element);89 89 } 90 90 }; … … 97 97 child: "/", 98 98 adjacent: "/following-sibling::[1]", 99 sibling:'/following-sibling::',99 laterSibling: '/following-sibling::', 100 100 tagName: "#{1}", 101 101 className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", … … 108 108 pseudo: function(m) { 109 109 var h = Selector.xpath.pseudos[m[1]]; 110 // XXX TDD->Andrew: shouldn't we return '' instead? 110 111 if (!h) return; 111 112 if (typeof h === 'function') return h(m); … … 116 117 '=': "[@#{1}='#{3}']", 117 118 '!=': "[@#{1}!='#{3}']", 118 '^=': "[s ubstring(@#{1}, 1, string-length('#{3}'))='#{3}']",119 '^=': "[starts-with(@#{1}, '#{3}')]", 119 120 '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", 120 121 '*=': "[contains(@#{1}, '#{3}')]", … … 123 124 }, 124 125 pseudos: { 125 'first-child': '[position()=1]', 126 'last-child': '[position()=last()]', 126 'first-child': '[not(preceding-sibling::*)]', 127 'last-child': '[not(following-sibling::*)]', 128 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', 127 129 'empty': '[not(node())]', 128 'checked': "[@checked='checked']", 129 'enabled': "[not(@disabled='disabled')]", 130 'disabled': "[@disabled='disabled']", 131 'not': "[not(#{4})]", 130 'checked': "[@checked='' or @checked='checked' or @checked='true' or @checked='yes']", 131 'enabled': "[@disabled and not(@disabled='' or @disabled='disabled' or @disabled='true' or @disabled='yes')]", 132 'disabled': "[@disabled='' or @disabled='disabled' or @disabled='true' or @disabled='yes']", 132 133 'not': function(m) { 134 // XXX TDD->Andrew: shouldn't we return '' instead? 133 135 if (!m[4]) return; 134 136 for (var i in Selector.patterns) { … … 150 152 var a = m[1] !== undefined ? Number(m[1]) : 1; 151 153 var b = m[3] !== undefined ? Number(m[2]) : 0; 154 // TODO: fix this, it won't work due to position's semantics. 152 155 predicate = "position() mod " + mm[1] + " = " + mm[3]; 153 156 } … … 170 173 child: 'd = "child";', 171 174 adjacent: 'd = "adjacent";', 172 sibling: 'd = "sibling";'175 laterSibling: 'd = "laterSibling";' 173 176 }, 174 177 … … 176 179 // combinators must be listed first 177 180 // (and descendant needs to be last combinator) 178 sibling:/^\s*~\s*/,181 laterSibling: /^\s*~\s*/, 179 182 child: /^\s*>\s*/, 180 183 adjacent: /^\s*\+\s*/, … … 199 202 200 203 child: function(nodes) { 201 var results = []; 202 for (var i = 0, node; node = nodes[i]; i++) 204 for (var i = 0, results = [], node; node = nodes[i]; i++) 203 205 Selector.handlers.concat(results, Element.immediateDescendants(node)); 204 206 return results; … … 206 208 207 209 adjacent: function(nodes) { 208 var results = []; 209 for (var i = 0, node; node = nodes[i]; i++) { 210 for (var i = 0, results = [], node; node = nodes[i]; i++) { 210 211 var next = this.nextElementSibling(node); 211 212 if (next) results.push(next); … … 214 215 }, 215 216 216 sibling: function(nodes) { 217 var results = []; 218 for (var i = 0, node; node = nodes[i]; i++) 219 Selector.handlers.concat(results, Element.siblings(node)); 217 laterSibling: function(nodes) { 218 for (var i = 0, results = [], node; node = nodes[i]; i++) 219 Selector.handlers.concat(results, Element.nextSiblings(node)); 220 220 return results; 221 221 }, … … 235 235 } 236 236 nodes = this[combinator](nodes); 237 if ('*' == tagName) return nodes; 237 238 } 238 239 for (var i = 0, node; node = nodes[i]; i++) { … … 246 247 247 248 className: function(nodes, root, className, combinator) { 248 var results = [],h = Selector.handlers;249 var h = Selector.handlers; 249 250 if (nodes) { 250 251 if (combinator) nodes = this[combinator](nodes); … … 252 253 } 253 254 return h.byClassName(nodes, root, className); 254 //return document.getElementsByClassName(className, root);255 255 }, 256 256 257 257 id: function(nodes, root, id, combinator) { 258 258 var targetNode = $(id); 259 if (!nodes && root == document) return [targetNode];259 if (!nodes && root == document) return targetNode ? [targetNode] : []; 260 260 if (nodes) { 261 261 if (combinator) { … … 268 268 } else if (combinator == 'adjacent') { 269 269 for (var i = 0, node; node = nodes[i]; i++) 270 if (targetNode.previousSibling == node) return [targetNode]; 271 } else if (combinator == 'sibling') { 272 if (nodes.include(targetNode)) return [targetNode]; 270 if (Selector.handlers.previousElementSibling(targetNode) == node) return [targetNode]; 273 271 } else nodes = Selector.handlers[combinator](nodes); 274 272 } … … 313 311 node._counted = true; 314 312 return nodes; 315 }, 313 }, // RESUME 316 314 317 315 unmark: function(nodes) { … … 333 331 } 334 332 } 333 // TODO: check whether multiple calls won't require unmarking nodes 335 334 return Selector.handlers.unmark(results); 336 335 }, … … 338 337 byClassName: function(nodes, root, className) { 339 338 if (!nodes) nodes = Selector.handlers.descendant([root]); 340 var needle = [" ", className, " "].join('');339 var needle = ' ' + className + ' '; 341 340 for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { 342 341 nodeClassName = node.className; 343 342 if (nodeClassName.length == 0) continue; 344 if (nodeClassName == className || 345 [" ", nodeClassName, " "].join('').include(needle)) 343 if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) 346 344 results.push(node); 347 345 } … … 350 348 351 349 nextElementSibling: function(node) { 352 do { 353 var node = node.nextSibling; 354 } while (node.nodeType != 1 && node.nextSibling); 355 return (node.nodeType == 1) ? node : null; 350 while (node = node.nextSibling) 351 if (node.nodeType == 1) return node; 352 return null; 356 353 }, 357 354 358 355 previousElementSibling: function(node) { 359 do { 360 var node = node.previousSibling; 361 } while (node.nodeType != 1 && node.previousSibling); 362 return (node.nodeType == 1) ? node : null; 356 while (node = node.previousSibling) 357 if (node.nodeType == 1) return node; 358 return null; 363 359 }, 364 360 365 361 pseudo: function(nodes, name, value, root, combinator) { 366 362 if (combinator) nodes = this[combinator](nodes); 367 return Selector.pseudos[name](nodes, value, root , combinator);363 return Selector.pseudos[name](nodes, value, root); 368 364 }, 369 365 … … 406 402 for (var i = 0, node; node = nodes[i]; i++) 407 403 if ((node.nodeIndex + 1) % 2 == mod) results.push(node); 408 } 409 410 if (formula.match(/^\d+$/)) { // just a number 404 } else if (formula.match(/^\d+$/)) { // just a number 411 405 formula = Number(formula); 412 406 for (var i = 0, node; node = nodes[i]; i++) 413 407 if (node.nodeIndex == formula) results.push(node); 414 } 415 416 if (m = formula.match(/^(\d+)?n(\+(\d+))?$/)) { // an+b 408 } else if (m = formula.match(/^(\d+)?n(\+(\d+))?$/)) { // an+b 417 409 var a = m[1] !== undefined ? Number(m[1]) : 1; 418 410 var b = m[3] !== undefined ? Number(m[2]) : 0; … … 428 420 'only-child': function(nodes, value, root) { 429 421 var h = Selector.handlers; 430 for (var i = 0, results = [], node; node = nodes[i]; i++) { 431 if (!h.previousElementSibling(node) && 432 !h.nextElementSibling(node)) { 422 for (var i = 0, results = [], node; node = nodes[i]; i++) 423 if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) 433 424 results.push(node); 434 }435 }436 425 return results; 437 426 }, … … 488 477 for (var i = 0, l = expressions.length; i < l; i++) { 489 478 selector = new Selector(expressions[i].strip()); 490 results = results.concat(selector.findElements(element));479 Selector.handlers.concat(results, selector.findElements(element)); 491 480 } 492 481 return results; … … 498 487 } 499 488 500 501 // TODO: 502 // (remaining pseudoclasses) 503 // nth-last-child 504 // nth-of-type 505 // nth-last-of-type 506 // 489 /* 490 TODO: 491 492 - PC/JS: TEST unique on multiple calls. unmark(nodes) if needed. 493 - PC/JS: IMPROVE attr ~= and |= (strings). Benchmark. Revert if negligible. 494 - PC/XPath: FIX empty. TEST. 495 496 - PC/XPath: ADD (first|last)-of-type 497 - PC/Pattern: ADD (first|last)-of-type 498 - PC/JS: ADD (first|last)-of-type. TEST. 499 500 - PC/XPath: CHECK/IMPROVE not. 501 - PC/JS: CHECK/IMPROVE not. TEST. 502 503 - PC/XPath: ADD nth[-last]-(child|of-type) 504 - PC/Pattern: ADD nth[-last]-(child|of-type) 505 - PC/JS: CHECK/IMPROVE nth-child 506 - PC/JS: ADD nth-[-last]-(child|of-type). TEST. 507 */ spinoffs/prototype/branches/selector/test/lib/unittest.js
r5649 r6186 306 306 assertEnumEqual: function(expected, actual) { 307 307 var message = arguments[2] || "assertEnumEqual"; 308 try { $A(expected).length == $A(actual).length && 308 expected = $A(expected); 309 actual = $A(actual); 310 try { expected.length == actual.length && 309 311 expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ? 310 312 this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + spinoffs/prototype/branches/selector/test/unit/selector.html
r6183 r6186 1 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 2 2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 3 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 3 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" 4 xmlns:html="http://www.w3.org/1999/xhtml"> 4 5 <head> 5 6 <title>Prototype Unit test file</title> … … 39 40 <form id="troubleForm"><input type="hidden" name="id" /></form> 40 41 42 <html:p id="nsP"></html:p> 41 43 44 <div id="level1"> 45 <span id="level2_1"> 46 <span id="level3_1"></span> 47 <!-- This comment should be ignored by the adjacent selector --> 48 <span id="level3_2"></span> 49 </span> 50 <span id="level2_2"><em id="level_only_child"></em></span> 51 <div id="level2_3"></div> 52 </div> 53 54 <div id="dupContainer"> 55 <span id="dupL1"> 56 <span id="dupL2"> 57 <span id="dupL3"> 58 <span id="dupL4"> 59 <span id="dupL5"></span> 60 </span> 61 </span> 62 </span> 63 </span> 64 </div> 65 </div> 66 42 67 <div id="grandfather"> grandfather 43 68 <div id="father" class="brothers men"> father … … 54 79 <script type="text/javascript" language="javascript" charset="utf-8"> 55 80 // <![CDATA[ 81 82 // Added by TDD - 2007.02.20 83 $RunBenchmarks = false; 84 56 85 new Test.Unit.Runner({ 57 86 … … 135 164 }}, 136 165 137 testSelectorWithSubstringAttributeValues: function() {with(this) {138 assertEnumEqual($('father', 'uncle'), $$('div[class^=bro]'), 'matching beginning of string');139 assertEnumEqual($('father', 'uncle'), $$('div[class$=men]'), 'matching end of string');140 assertEnumEqual($('father', 'uncle'), $$('div[class*="ers m"]'), 'matching substring')141 }},142 143 166 test$$WithNestedAttributeSelectors: function() {with(this) { 144 167 assertEnumEqual([$('strong')], $$('div[style] p[id] strong')); … … 167 190 }}, 168 191 169 testChildCombinator: function() {with(this) { 192 // AND NOW COME THOSE NEW TESTS AFTER ANDREW'S REWRITE! 193 194 testSelectorWithNamespacedAttributes: function() { with(this) { 195 if (Prototype.BrowserFeatures.XPath) { 196 assertUndefined(new Selector('html[xml:lang]').xpath); 197 assertUndefined(new Selector('body p[xml:lang]').xpath); 198 assertNotUndefined(new Selector('html:p#nsP').xpath); 199 } else 200 info("Could not test XPath bypass: no XPath to begin with!"); 201 }}, 202 203 testSelectorWithChild: function() { with(this) { 170 204 assertEnumEqual($('link_1', 'link_2'), $$('p.first > a')); 171 205 assertEnumEqual($('father', 'uncle'), $$('div#grandfather > div')); 172 }}, 173 174 testAdjacencyCombinator: function() {with(this) { 206 assertEnumEqual($('level2_1', 'level2_2'), $$('#level1>span')); 207 assertEnumEqual($('level2_1', 'level2_2'), $$('#level1 > span')); 208 assertEnumEqual($('level3_1', 'level3_2'), $$('#level2_1 > *')); 209 $RunBenchmarks && wait(500, function() { 210 benchmark(function() { $$('#level1 > span') }, 1000); 211 }); 212 }}, 213 214 testSelectorWithAdjacence: function() { with(this) { 175 215 assertEnumEqual([$('uncle')], $$('div.brothers + div.brothers')); 176 216 assertEnumEqual([$('uncle')], $$('div.brothers + div')); 177 }}, 178 179 testGeneralSiblingCombinator: function() {with(this) { 217 assertEqual($('level2_2'), $$('#level2_1+span').reduce()); 218 assertEqual($('level2_2'), $$('#level2_1 + span').reduce()); 219 assertEqual($('level2_2'), $$('#level2_1 + *').reduce()); 220 assertEnumEqual([], $$('#level2_2 + span')); 221 assertEqual($('level3_2'), $$('#level3_1 + span').reduce()); 222 assertEqual($('level3_2'), $$('#level3_1 + *').reduce()); 223 assertEnumEqual([], $$('#level3_2 + *')); 224 assertEnumEqual([], $$('#level3_1 + em')); 225 $RunBenchmarks && wait(500, function() { 226 benchmark(function() { $$('#level3_1 + span') }, 1000); 227 }); 228 }}, 229 230 testSelectorWithLaterSibling: function() { with(this) { 180 231 assertEnumEqual([$('list')], $$('h1 ~ ul')); 181 }}, 182 183 testUniquenessOfResultSet: function() {with(this) { 232 assertEqual($('level2_2'), $$('#level2_1 ~ span').reduce()); 233 assertEnumEqual($('level2_2', 'level2_3'), $$('#level2_1 ~ *').reduce()); 234 assertEnumEqual([], $$('#level2_2 ~ span')); 235 assertEnumEqual([], $$('#level3_2 ~ *')); 236 assertEnumEqual([], $$('#level3_1 ~ em')); 237 assertEnumEqual([$('level3_2')], $$('#level3_1 ~ #level3_2')); 238 assertEnumEqual([$('level3_2')], $$('span ~ #level3_2')); 239 assertEnumEqual([], $$('div ~ #level3_2')); 240 assertEnumEqual([], $$('div ~ #level2_3')); 241 $RunBenchmarks && wait(500, function() { 242 benchmark(function() { $$('#level2_1 ~ span') }, 1000); 243 }); 244 }}, 245 246 testSelectorWithNewAttributeOperators: function() { with(this) { 247 assertEnumEqual($('father', 'uncle'), $$('div[class^=bro]'), 'matching beginning of string'); 248 assertEnumEqual($('father', 'uncle'), $$('div[class$=men]'), 'matching end of string'); 249 assertEnumEqual($('father', 'uncle'), $$('div[class*="ers m"]'), 'matching substring') 250 assertEnumEqual($('level2_1', 'level2_2', 'level2_3'), $$('#level1 *[id^="level2_"]')); 251 assertEnumEqual($('level2_1', 'level2_2', 'level2_3'), $$('#level1 *[id^=level2_]')); 252 assertEnumEqual($('level2_1', 'level3_1'), $$('#level1 *[id$="_1"]')); 253 assertEnumEqual($('level2_1', 'level3_1'), $$('#level1 *[id$=_1]')); 254 assertEnumEqual($('level2_1', 'level3_2', 'level2_2', 'level2_3'), $$('#level1 *[id*="2"]')); 255 assertEnumEqual($('level2_1', 'level3_2', 'level2_2', 'level2_3'), $$('#level1 *[id*=2]')); 256 $RunBenchmarks && wait(500, function() { 257 benchmark(function() { $$('#level1 *[id^=level2_]') }, 1000, '[^=]'); 258 benchmark(function() { $$('#level1 *[id$=_1]') }, 1000, '[$=]'); 259 benchmark(function() { $$('#level1 *[id*=_2]') }, 1000, '[*=]'); 260 }); 261 }}, 262 263 testSelectorWithDuplicates: function() { with(this) { 184 264 assertEnumEqual($$('div div'), $$('div div').uniq()); 185 }} 186 265 assertEnumEqual($('dupL2', 'dupL3', 'dupL4', 'dupL5'), $$('#dupContainer span span')); 266 $RunBenchmarks && wait(500, function() { 267 benchmark(function() { $$('#dupContainer span span') }, 1000); 268 }); 269 }}, 270 271 testSelectorPCFirstLastOnlyChild: function() { with(this) { 272 assertEnumEqual([$('level2_1')], $$('#level1>*:first-child')); 273 assertEnumEqual($('level2_1', 'level3_1', 'level_only_child'), $$('#level1 *:first-child')); 274 assertEnumEqual([$('level2_3')], $$('#level1>*:last-child')); 275 assertEnumEqual($('level3_2', 'level_only_child', 'level2_3'), $$('#level1 *:last-child')); 276 assertEnumEqual([$('level2_3')], $$('#level1>div:last-child')); 277 assertEnumEqual([$('level2_3')], $$('#level1 div:last-child')); 278 assertEnumEqual([], $$('#level1>div:first-child')); 279 assertEnumEqual([], $$('#level1>span:last-child')); 280 assertEnumEqual($('level2_1', 'level3_1'), $$('#level1 span:first-child')); 281 assertEnumEqual([], $$('#level1:first-child')); 282 assertEnumEqual([], $$('#level1>*:only-child')); 283 assertEnumEqual([$('level_only_child')], $$('#level1 *:only-child')); 284 assertEnumEqual([], $$('#level1:only-child')); 285 $RunBenchmarks && wait(500, function() { 286 benchmark(function() { $$('#level1 *:first-child') }, 1000, ':first-child'); 287 benchmark(function() { $$('#level1 *:last-child') }, 1000, ':last-child'); 288 benchmark(function() { $$('#level1 *:only-child') }, 1000, ':only-child'); 289 }); 290 }}, 291 187 292 }, 'testlog'); 188 293 // ]]>