Ticket #5170: selectors.diff
| File selectors.diff, 12.9 kB (added by glazedginger@gmail.com, 3 years ago) |
|---|
-
test/unit/selector.html
old new 24 24 <h1 class="title">Some title <span>here</span></h1> 25 25 <p id="p" class="first summary"> 26 26 <strong id="strong">This</strong> is a short blurb 27 <a id="link_1" class="first internal" href="#">with a link</a> or28 <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>. 29 29 </p> 30 30 <ul id="list"> 31 31 <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> 34 34 </ul> 35 <ol> 36 <li id="item_4"><a id="link_4" href="#header">a last link</a></li> 37 </ol> 35 38 </div> 36 39 37 40 <!-- Log output --> … … 94 97 assertEnumEqual($('link_1', 'link_2', 'item_1', 'item_2', 'item_3'), $$('#p a', ' ul#list li ')); 95 98 }}, 96 99 97 testSelectorWithTagNameAnd AttributeExistence: function() {with(this) {100 testSelectorWithTagNameAndClassAttributeExistence: function() {with(this) { 98 101 assertEnumEqual($$('#fixtures h1'), $$('h1[class]')); 99 102 assertEnumEqual($$('#fixtures h1'), $$('h1[CLASS]')); 100 103 assertEnumEqual([$('item_3')], $$('li#item_3[class]')); 104 assertEnumEqual([], $$('#link_4[class]')); 101 105 }}, 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 }}, 102 114 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 103 123 testSelectorWithTagNameAndSpecificAttributeValue: function() {with(this) { 104 124 assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href="#"]')); 105 125 assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href=#]')); 106 126 }}, 107 127 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 }}, 108 144 testSelectorWithTagNameAndWhitespaceTokenizedAttributeValue: function() {with(this) { 109 145 assertEnumEqual($('link_1', 'link_2'), $$('a[class~="internal"]')); 110 146 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])')); 111 152 }}, 112 153 113 154 testSelectorWithUniversalAndHyphenTokenizedAttributeValue: function() {with(this) { 114 155 assertEnumEqual([$('item_3')], $$('*[xml:lang|="es"]')); 115 156 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 116 165 }}, 117 166 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^=#]')); 120 171 }}, 121 172 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 122 202 test$$WithNestedAttributeSelectors: function() {with(this) { 123 203 assertEnumEqual([$('strong')], $$('div[style] p[id] strong')); 124 204 }}, 125 205 126 206 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="#"]')); 129 216 }} 130 217 }, 'testlog'); 131 218 // ]]> -
test/test.css
old new 1 body { 2 font-family: verdana, sans-serif; 3 font-size: 12px; 4 } 5 6 td { 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 13 13 if (this.expression == '') abort('empty expression'); 14 14 15 15 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)) { 17 17 params.attributes = params.attributes || []; 18 params.attributes.push({n ame: 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] || ''}); 19 19 expr = match[1]; 20 20 } 21 21 22 22 if (expr == '*') return this.params.wildcard = true; 23 23 24 24 while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { 25 25 modifier = match[1], clause = match[2], rest = match[3]; 26 26 switch (modifier) { … … 32 32 } 33 33 expr = rest; 34 34 } 35 35 36 36 if (expr.length > 0) abort(expr.inspect()); 37 37 }, 38 38 39 39 buildMatchExpression: function() { 40 40 var params = this.params, conditions = [], clause; 41 42 41 if (params.wildcard) 43 42 conditions.push('true'); 44 43 if (clause = params.id) … … 50 49 conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')'); 51 50 if (clause = params.attributes) { 52 51 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'; 54 59 var splitValueBy = function(delimiter) { 55 return value + ' && ' + value+ '.split(' + delimiter.inspect() + ')';60 return attr + '.split(' + delimiter.inspect() + ')'; 56 61 } 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 }; 68 86 } 69 87 }); 70 88 } 71 72 89 return conditions.join(' && '); 73 90 }, 74 91 … … 85 102 if (!scope || Element.childOf(element, scope)) 86 103 return [element]; 87 104 88 scope = (scope || document).getElementsByTagName(this.params.tagName ||'*');105 scope = (scope || document).getElementsByTagName(this.params.tagName ? this.params.tagName.toLowerCase() : '*'); 89 106 90 107 var results = []; 91 108 for (var i = 0; i < scope.length; i++) 92 109 if (this.match(element = scope[i])) 93 110 results.push(Element.extend(element)); 94 95 111 return results; 96 112 }, 97 113