Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #5170: selectors.diff

File selectors.diff, 12.9 kB (added by glazedginger@gmail.com, 3 years ago)
  • test/unit/selector.html

    old new  
    2424  <h1 class="title">Some title <span>here</span></h1> 
    2525  <p id="p" class="first summary"> 
    2626    <strong id="strong">This</strong> is a short blurb 
    27     <a id="link_1" class="first internal" href="#">with a link</a> or  
    28     <a id="link_2" class="internal highlight" href="#"><em id="em">two</em></a>. 
     27    <a id="link_1" class="first internal" title="link" href="#">with a link</a> or  
     28    <a id="link_2" class="internal highlight" href="#" title=""><em id="em">two</em></a>. 
    2929  </p> 
    3030  <ul id="list"> 
    3131    <li id="item_1" class="first"><a id="link_3" href="#" class="external"><span id="span">Another link</span></a></li> 
    32     <li id="item_2">Some text</li> 
    33                <li id="item_3" xml:lang="es-us" class="">Otra cosa</li> 
     32    <li id="item_2" xml:lang="en">Some text</li> 
     33    <li id="item_3" xml:lang="es-us" class="" title="">Otra cosa</li> 
    3434  </ul> 
     35  <ol> 
     36        <li id="item_4"><a id="link_4" href="#header">a last link</a></li> 
     37  </ol> 
    3538</div> 
    3639 
    3740<!-- Log output --> 
     
    9497      assertEnumEqual($('link_1', 'link_2', 'item_1', 'item_2', 'item_3'), $$('#p a', ' ul#list li ')); 
    9598    }}, 
    9699 
    97                 testSelectorWithTagNameAndAttributeExistence: function() {with(this) { 
     100                testSelectorWithTagNameAndClassAttributeExistence: function() {with(this) { 
    98101                        assertEnumEqual($$('#fixtures h1'), $$('h1[class]')); 
    99102                        assertEnumEqual($$('#fixtures h1'), $$('h1[CLASS]')); 
    100103                        assertEnumEqual([$('item_3')], $$('li#item_3[class]')); 
     104                        assertEnumEqual([], $$('#link_4[class]')); 
    101105                }}, 
     106 
     107                testSelectorWithTagNameAndNegatedClassAttributeExistence: function() {with(this) { 
     108                        assertEnumEqual([], $$('#fixtures h1:not([class])')); 
     109                        assertEnumEqual([], $$('#fixtures h1:not([CLASS])')); 
     110                        assertEnumEqual([], $$('#item_3:not([class])')); 
     111                        assertEnumEqual([$('link_4')], $$('#link_4:not([class])')); 
     112                         
     113                }}, 
    102114                 
     115                testSelectorWithTagNameAndOtherAttributeExistence: function() {with(this) { 
     116                        assertEnumEqual([$('item_3')], $$('li#item_3[title]')); 
     117                }}, 
     118                 
     119                testSelectorWithTagNameAndNegatedOtherAttributeExistence: function() {with(this) { 
     120                        assertEnumEqual([], $$('li#item_3:not([title])')); 
     121                }},  
     122                 
    103123                testSelectorWithTagNameAndSpecificAttributeValue: function() {with(this) { 
    104124                        assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href="#"]')); 
    105125                        assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href=#]')); 
    106126                }}, 
    107127                 
     128                testSelectorWithTagNameAndNegatedSpecificAttributeValue: function() {with(this) { 
     129                        assertEnumEqual([$('link_4')], $$('a:not([href="#"])')); 
     130                        assertEnumEqual([$('link_4')], $$('a:not([href=#])')); 
     131                }}, 
     132                 
     133                testSelectorWithTagNameAndEmptySpecificAttributeValue: function() {with(this) { 
     134                        assertEnumEqual([$('item_3')], $$('li[title=""]')); 
     135                        assertEnumEqual([$('item_3')], $$('li[title=]')); 
     136                         
     137                }}, 
     138                 
     139                testSelectorWithTagNameAndNegatedEmptySpecificAttributeValue: function() {with(this) { 
     140                        assertEnumEqual($('link_1', 'link_3', 'link_4'), $$('a:not([title=""])')); 
     141                        assertEnumEqual($('link_1', 'link_3', 'link_4'), $$('a:not([title=])')); 
     142                         
     143                }},              
    108144                testSelectorWithTagNameAndWhitespaceTokenizedAttributeValue: function() {with(this) { 
    109145                        assertEnumEqual($('link_1', 'link_2'), $$('a[class~="internal"]')); 
    110146                        assertEnumEqual($('link_1', 'link_2'), $$('a[class~=internal]')); 
     147                        }}, 
     148 
     149                testSelectorWithTagNameAndNegatedWhitespaceTokenizedAttributeValue: function() {with(this) { 
     150                        assertEnumEqual($('link_3', 'link_4'), $$('a:not([class~="internal"])')); 
     151                        assertEnumEqual($('link_3', 'link_4'), $$('a:not([class~=internal])')); 
    111152                }}, 
    112153                 
    113154                testSelectorWithUniversalAndHyphenTokenizedAttributeValue: function() {with(this) { 
    114155                        assertEnumEqual([$('item_3')], $$('*[xml:lang|="es"]')); 
    115156                        assertEnumEqual([$('item_3')], $$('*[xml:lang|="ES"]')); 
     157 
     158                        }}, 
     159 
     160                testSelectorWithUniversalAndHyphenTokenizedAttributeValue: function() {with(this) { 
     161                        assertEnumEqual($('item_1', 'item_2', 'item_4'), $$('li:not([xml:lang|="es"])')); 
     162                        assertEnumEqual($('item_1', 'item_2', 'item_4'), $$('li:not([xml:lang|="ES"])')); 
     163 
     164 
    116165                }}, 
    117166                 
    118                 testSelectorWithTagNameAndNegatedAttributeValue: function() {with(this) { 
    119                   assertEnumEqual([], $$('a[href!=#]')); 
     167                testSelectorWithTagNameAndAttributeValueStartingWithString: function() {with(this) { 
     168                  assertEnumEqual([$('link_4')], $$('a[href^="#hea"]')); 
     169                  assertEnumEqual([$('link_4')], $$('a[href^=#hea]')); 
     170                  assertEnumEqual($('link_1', 'link_2', 'link_3', 'link_4'), $$('a[href^=#]')); 
    120171                }}, 
    121172                 
     173                testSelectorWithTagNameAndNegatedAttributeValueStartingWithString: function() {with(this) { 
     174                  assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a:not([href^="#hea"])')); 
     175                  assertEnumEqual([], $$('a:not([href^=#])')); 
     176                }}, 
     177                 
     178                testSelectorWithTagNameAndAttributeValueEndingWithString: function() {with(this) { 
     179                  assertEnumEqual([$('link_4')], $$('a[href$="der"]')); 
     180                  assertEnumEqual([$('link_4')], $$('a[href$=der]')); 
     181                }}, 
     182                 
     183                testSelectorWithTagNameAndNegatedAttributeValueEndingWithString: function() {with(this) { 
     184                  assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a:not([href$="der"])')); 
     185                  assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a:not([href$=der])')); 
     186                }}, 
     187                 
     188                testSelectorWithTagNameAndAttributeValueContainingString: function() {with(this) { 
     189                  assertEnumEqual([$('link_4')], $$('a[href*="head"]')); 
     190                  assertEnumEqual([$('link_4')], $$('a[href*=head]')); 
     191                  assertEnumEqual([$('link_4')], $$('a[href*=#header]')); 
     192                  assertEnumEqual($('link_1', 'link_2', 'link_3', 'link_4'), $$('a[href*=#]')); 
     193                }}, 
     194                 
     195                testSelectorWithTagNameAndNegatedAttributeValueContainingString: function() {with(this) { 
     196                  assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a:not([href*="head"])')); 
     197                  assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a:not([href*=head])')); 
     198                  assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a:not([href*=#header])   ')); 
     199                  assertEnumEqual([], $$('a:not([href*=#])')); 
     200                }}, 
     201                 
    122202                test$$WithNestedAttributeSelectors: function() {with(this) { 
    123203                  assertEnumEqual([$('strong')], $$('div[style] p[id] strong')); 
    124204                }}, 
    125205                 
    126206                testSelectorWithMultipleConditions: function() {with(this) { 
    127                   assertEnumEqual([$('link_3')], $$('a[class~=external][href="#"]')); 
    128                   assertEnumEqual([], $$('a[class~=external][href!="#"]')); 
     207                  assertEnumEqual([$('link_1')], $$('a[class~=internal][title="link"]')); 
     208                }}, 
     209                 
     210                testSelectorWithMultipleNegatedConditions: function() {with(this) { 
     211                  assertEnumEqual([$('link_4')], $$('a:not([class~=external]):not([href="#"])')); 
     212                }}, 
     213                 
     214                testSelectorWithMultipleMixedConditions: function() {with(this) { 
     215                  assertEnumEqual($('link_1', 'link_2'), $$('a:not([class~=external])[href="#"]')); 
    129216                }} 
    130217  }, 'testlog'); 
    131218// ]]> 
  • test/test.css

    old new  
     1body { 
     2        font-family: verdana, sans-serif; 
     3        font-size: 12px; 
     4} 
     5 
     6td { 
     7        padding: 5px; 
     8} 
     9 
     10.passed td { 
     11        border: 2px solid #090; 
     12        background: #af0; 
     13} 
     14 
     15.failed td { 
     16        border: 2px solid #e00; 
     17        background: #fcc; 
     18} 
     19 
     20.error td { 
     21        border: 2px solid #000; 
     22        background: #333; 
     23        color: #fff; 
     24} 
  • src/selector.js

    old new  
    1313    if (this.expression == '')  abort('empty expression'); 
    1414 
    1515    var params = this.params, expr = this.expression, match, modifier, clause, rest; 
    16     while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { 
     16    while (match = expr.match(/^(.*?)(\:not\()?\[([a-z0-9_:-]+?)(?:([~\|^$*]?=)(?:"([^"]*)"|([^\]\s]*)))?\](\))?$/i)) { 
    1717      params.attributes = params.attributes || []; 
    18       params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); 
     18      params.attributes.push({negation: (match[2] + match[7] == ':not()'), name: match[3], operator: match[4], value: match[5] || match[6] || ''}); 
    1919      expr = match[1]; 
    2020    } 
    2121 
    2222    if (expr == '*') return this.params.wildcard = true; 
    23      
     23 
    2424    while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { 
    2525      modifier = match[1], clause = match[2], rest = match[3]; 
    2626      switch (modifier) { 
     
    3232      } 
    3333      expr = rest; 
    3434    } 
    35      
     35 
    3636    if (expr.length > 0) abort(expr.inspect()); 
    3737  }, 
    3838 
    3939  buildMatchExpression: function() { 
    4040    var params = this.params, conditions = [], clause; 
    41  
    4241    if (params.wildcard) 
    4342      conditions.push('true'); 
    4443    if (clause = params.id) 
     
    5049        conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')'); 
    5150    if (clause = params.attributes) { 
    5251      clause.each(function(attribute) { 
    53         var value = 'element.getAttribute(' + attribute.name.inspect() + ')'; 
     52 
     53        var attr = 'element.getAttribute(' + attribute.name.inspect() + ', 2)'; 
     54        if (attribute.name.toLowerCase() == 'class') 
     55          attr = '(element.getAttribute(\'class\') || element.getAttribute(\'className\'))'; 
     56        var attrNode = 'element.getAttributeNode(' + attribute.name.inspect() + ')'; 
     57        var existence = attrNode + ' && ' + attrNode + '.specified'; 
     58        var nonExistence = '((' + existence + ' ) ? ' + attrNode + ' : null) == null'; 
    5459        var splitValueBy = function(delimiter) { 
    55           return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; 
     60          return attr + '.split(' + delimiter.inspect() + ')'; 
    5661        } 
    57          
    58         switch (attribute.operator) { 
    59           case '=':       conditions.push(value + ' == ' + attribute.value.inspect()); break; 
    60           case '~=':      conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; 
    61           case '|=':      conditions.push( 
    62                             splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() 
    63                           ); break; 
    64           case '!=':      conditions.push(value + ' != ' + attribute.value.inspect()); break; 
    65           case '': 
    66           case undefined: conditions.push(value + ' != null'); break; 
    67           default:        throw 'Unknown operator ' + attribute.operator + ' in selector'; 
     62        if (attribute.negation) { 
     63          switch (attribute.operator) { 
     64            case '=':       conditions.push('(' + nonExistence + ' || ' + attr + ' != ' + attribute.value.inspect() + ')'); break; 
     65            case '~=':      conditions.push('(' + nonExistence + ' || !' + splitValueBy(' ') + '.include(' + attribute.value.inspect() + '))'); break; 
     66            case '|=':      conditions.push('(' + nonExistence + ' || ' + splitValueBy('-') + '.first().toUpperCase() != ' + attribute.value.toUpperCase().inspect() + ')'); break; 
     67            case '^=':       conditions.push('!' + attr + '.match(/^' + attribute.value + '/)'); break; 
     68            case '$=':       conditions.push('!' + attr + '.match(/' + attribute.value + '$/)'); break; 
     69            case '*=':       conditions.push('!' + attr + '.match(/' + attribute.value + '/)'); break; 
     70            case '': 
     71            case undefined: conditions.push(nonExistence); break; 
     72            default:        throw 'Unknown operator ' + attribute.operator + ' in selector'; 
     73          }; 
     74        } else { 
     75          switch (attribute.operator) { 
     76            case '=':       conditions.push(existence + ' && ' + attr + ' == ' + attribute.value.inspect()); break; 
     77            case '~=':      conditions.push(existence + ' && ' + splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; 
     78            case '|=':      conditions.push(existence + ' && ' + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()); break; 
     79            case '^=':       conditions.push(attr + '.match(/^' + attribute.value + '/)'); break; 
     80            case '$=':       conditions.push(attr + '.match(/' + attribute.value + '$/)'); break; 
     81            case '*=':       conditions.push(attr + '.match(/' + attribute.value + '/)'); break; 
     82            case '': 
     83            case undefined:  conditions.push(existence); break; 
     84            default:         throw 'Unknown operator ' + attribute.operator + ' in selector'; 
     85          }; 
    6886        } 
    6987      }); 
    7088    } 
    71  
    7289    return conditions.join(' && '); 
    7390  }, 
    7491 
     
    85102        if (!scope || Element.childOf(element, scope)) 
    86103          return [element]; 
    87104 
    88     scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); 
     105    scope = (scope || document).getElementsByTagName(this.params.tagName ? this.params.tagName.toLowerCase() : '*'); 
    89106 
    90107    var results = []; 
    91108    for (var i = 0; i < scope.length; i++) 
    92109      if (this.match(element = scope[i])) 
    93110        results.push(Element.extend(element)); 
    94  
    95111    return results; 
    96112  }, 
    97113