|
95 | 95 | if (rulesetNode.root) {
|
96 | 96 | return;
|
97 | 97 | }
|
98 |
| - var i, j, k, selector, element, allExtends = this.allExtendsStack[this.allExtendsStack.length-1], selectorsToAdd = []; |
| 98 | + var matches, pathIndex, extendIndex, allExtends = this.allExtendsStack[this.allExtendsStack.length-1], selectorsToAdd = [], extendVisitor = this; |
99 | 99 |
|
100 |
| - for(k = 0; k < allExtends.length; k++) { |
101 |
| - for(i = 0; i < rulesetNode.paths.length; i++) { |
102 |
| - selectorPath = rulesetNode.paths[i]; |
103 |
| - var matches = this.findMatch(allExtends[k], selectorPath); |
104 |
| - if (matches.length) { |
105 |
| - allExtends[k].selfSelectors.forEach(function(selfSelector) { |
106 |
| - var currentSelectorPathIndex = 0, |
107 |
| - currentSelectorPathElementIndex = 0, |
108 |
| - path = []; |
109 |
| - for(j = 0; j < matches.length; j++) { |
110 |
| - match = matches[j]; |
111 |
| - var selector = selectorPath[match.pathIndex], |
112 |
| - firstElement = new tree.Element( |
113 |
| - match.initialCombinator, |
114 |
| - selfSelector.elements[0].value, |
115 |
| - selfSelector.elements[0].index |
116 |
| - ); |
117 |
| - |
118 |
| - if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) { |
119 |
| - path[path.length-1].elements = path[path.length-1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex)); |
120 |
| - currentSelectorPathElementIndex = 0; |
121 |
| - currentSelectorPathIndex++; |
122 |
| - } |
123 |
| - |
124 |
| - path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex)); |
125 |
| - |
126 |
| - path.push(new tree.Selector( |
127 |
| - selector.elements |
128 |
| - .slice(currentSelectorPathElementIndex, match.index) |
129 |
| - .concat([firstElement]) |
130 |
| - .concat(selfSelector.elements.slice(1)) |
131 |
| - )); |
132 |
| - currentSelectorPathIndex = match.endPathIndex; |
133 |
| - currentSelectorPathElementIndex = match.endPathElementIndex; |
134 |
| - if (currentSelectorPathElementIndex >= selector.elements.length) { |
135 |
| - currentSelectorPathElementIndex = 0; |
136 |
| - currentSelectorPathIndex++; |
137 |
| - } |
138 |
| - } |
| 100 | + // look at each selector path in the ruleset, find any extend matches and then copy, find and replace |
139 | 101 |
|
140 |
| - if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) { |
141 |
| - path[path.length-1].elements = path[path.length-1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex)); |
142 |
| - currentSelectorPathElementIndex = 0; |
143 |
| - currentSelectorPathIndex++; |
144 |
| - } |
| 102 | + for(extendIndex = 0; extendIndex < allExtends.length; extendIndex++) { |
| 103 | + for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) { |
145 | 104 |
|
146 |
| - path = path.concat(selectorPath.slice(currentSelectorPathIndex, selectorPath.length)); |
| 105 | + selectorPath = rulesetNode.paths[pathIndex]; |
| 106 | + matches = this.findMatch(allExtends[extendIndex], selectorPath); |
| 107 | + |
| 108 | + if (matches.length) { |
147 | 109 |
|
148 |
| - selectorsToAdd.push(path); |
| 110 | + allExtends[extendIndex].selfSelectors.forEach(function(selfSelector) { |
| 111 | + selectorsToAdd.push(extendVisitor.extendSelector(matches, selfSelector)); |
149 | 112 | });
|
150 | 113 | }
|
151 | 114 | }
|
152 | 115 | }
|
153 | 116 | rulesetNode.paths = rulesetNode.paths.concat(selectorsToAdd);
|
154 | 117 | },
|
155 |
| - findMatch: function (extend, selectorPath) { |
156 |
| - var i, k, l, element, hasMatch, potentialMatches = [], potentialMatch, matches = []; |
157 |
| - for(k = 0; k < selectorPath.length; k++) { |
158 |
| - selector = selectorPath[k]; |
159 |
| - for(i = 0; i < selector.elements.length; i++) { |
160 |
| - if (extend.allowBefore || (k == 0 && i == 0)) { |
161 |
| - potentialMatches.push({pathIndex: k, index: i, matched: 0, initialCombinator: selector.elements[i].combinator}); |
| 118 | + findMatch: function (extend, haystackSelectorPath) { |
| 119 | + // |
| 120 | + // look through the haystack selector path to try and find the needle - extend.selector |
| 121 | + // returns an array of selector matches that can then be replaced |
| 122 | + // |
| 123 | + var haystackSelectorIndex, hackstackSelector, hackstackElementIndex, haystackElement, |
| 124 | + targetCombinator, i, |
| 125 | + needleElements = extend.selector.elements, |
| 126 | + potentialMatches = [], potentialMatch, matches = []; |
| 127 | + |
| 128 | + // loop through the haystack elements |
| 129 | + for(haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) { |
| 130 | + hackstackSelector = haystackSelectorPath[haystackSelectorIndex]; |
| 131 | + |
| 132 | + for(hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) { |
| 133 | + |
| 134 | + haystackElement = hackstackSelector.elements[hackstackElementIndex]; |
| 135 | + |
| 136 | + // if we allow elements before our match we can add a potential match every time. otherwise only at the first element. |
| 137 | + if (extend.allowBefore || (haystackSelectorIndex == 0 && hackstackElementIndex == 0)) { |
| 138 | + potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0, initialCombinator: haystackElement.combinator}); |
162 | 139 | }
|
163 | 140 |
|
164 |
| - for(l = 0; l < potentialMatches.length; l++) { |
165 |
| - potentialMatch = potentialMatches[l]; |
| 141 | + for(i = 0; i < potentialMatches.length; i++) { |
| 142 | + potentialMatch = potentialMatches[i]; |
166 | 143 |
|
167 |
| - var targetCombinator = selector.elements[i].combinator.value; |
168 |
| - if (targetCombinator == '' && i === 0) { |
| 144 | + // selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't |
| 145 | + // then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out |
| 146 | + // what the resulting combinator will be |
| 147 | + targetCombinator = haystackElement.combinator.value; |
| 148 | + if (targetCombinator == '' && hackstackElementIndex === 0) { |
169 | 149 | targetCombinator = ' ';
|
170 | 150 | }
|
171 |
| - if (extend.selector.elements[potentialMatch.matched].value !== selector.elements[i].value || |
172 |
| - (potentialMatch.matched > 0 && extend.selector.elements[potentialMatch.matched].combinator.value !== targetCombinator)) { |
| 151 | + |
| 152 | + // if we don't match, null our match to indicate failure |
| 153 | + if (needleElements[potentialMatch.matched].value !== haystackElement.value || |
| 154 | + (potentialMatch.matched > 0 && needleElements[potentialMatch.matched].combinator.value !== targetCombinator)) { |
173 | 155 | potentialMatch = null;
|
174 | 156 | } else {
|
175 | 157 | potentialMatch.matched++;
|
176 | 158 | }
|
177 | 159 |
|
| 160 | + // if we are still valid and have finished, test whether we have elements after and whether these are allowed |
178 | 161 | if (potentialMatch) {
|
179 |
| - potentialMatch.finished = potentialMatch.matched === extend.selector.elements.length; |
| 162 | + potentialMatch.finished = potentialMatch.matched === needleElements.length; |
180 | 163 | if (potentialMatch.finished &&
|
181 |
| - (!extend.allowAfter && (i+1 < selector.elements.length || |
182 |
| - k+1 < selectorPath.length))) { |
| 164 | + (!extend.allowAfter && (hackstackElementIndex+1 < hackstackSelector.elements.length || haystackSelectorIndex+1 < haystackSelectorPath.length))) { |
183 | 165 | potentialMatch = null;
|
184 | 166 | }
|
185 | 167 | }
|
| 168 | + // if null we remove, if not, we are still valid, so either push as a valid match or continue |
186 | 169 | if (potentialMatch) {
|
187 | 170 | if (potentialMatch.finished) {
|
188 |
| - potentialMatch.length = extend.selector.elements.length; |
189 |
| - potentialMatch.endPathIndex = k; |
190 |
| - potentialMatch.endPathElementIndex = i+1; // index after end of match |
191 |
| - potentialMatches.length = 0; |
| 171 | + potentialMatch.length = needleElements.length; |
| 172 | + potentialMatch.endPathIndex = haystackSelectorIndex; |
| 173 | + potentialMatch.endPathElementIndex = hackstackElementIndex + 1; // index after end of match |
| 174 | + potentialMatches.length = 0; // we don't allow matches to overlap, so start matching again |
192 | 175 | matches.push(potentialMatch);
|
193 |
| - break; |
194 | 176 | }
|
195 | 177 | } else {
|
196 |
| - potentialMatches.splice(l, 1); |
197 |
| - l--; |
| 178 | + potentialMatches.splice(i, 1); |
| 179 | + i--; |
198 | 180 | }
|
199 | 181 | }
|
200 | 182 | }
|
201 | 183 | }
|
202 | 184 | return matches;
|
203 | 185 | },
|
| 186 | + extendSelector:function (matches, replacementSelector) { |
| 187 | + |
| 188 | + //for a set of matches, replace each match with the replacement selector |
| 189 | + |
| 190 | + var currentSelectorPathIndex = 0, |
| 191 | + currentSelectorPathElementIndex = 0, |
| 192 | + path = [], |
| 193 | + matchIndex, |
| 194 | + selector, |
| 195 | + firstElement; |
| 196 | + |
| 197 | + for (matchIndex = 0; matchIndex < matches.length; matchIndex++) { |
| 198 | + match = matches[matchIndex]; |
| 199 | + selector = selectorPath[match.pathIndex]; |
| 200 | + firstElement = new tree.Element( |
| 201 | + match.initialCombinator, |
| 202 | + replacementSelector.elements[0].value, |
| 203 | + replacementSelector.elements[0].index |
| 204 | + ); |
| 205 | + |
| 206 | + if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) { |
| 207 | + path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex)); |
| 208 | + currentSelectorPathElementIndex = 0; |
| 209 | + currentSelectorPathIndex++; |
| 210 | + } |
| 211 | + |
| 212 | + path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex)); |
| 213 | + |
| 214 | + path.push(new tree.Selector( |
| 215 | + selector.elements |
| 216 | + .slice(currentSelectorPathElementIndex, match.index) |
| 217 | + .concat([firstElement]) |
| 218 | + .concat(replacementSelector.elements.slice(1)) |
| 219 | + )); |
| 220 | + currentSelectorPathIndex = match.endPathIndex; |
| 221 | + currentSelectorPathElementIndex = match.endPathElementIndex; |
| 222 | + if (currentSelectorPathElementIndex >= selector.elements.length) { |
| 223 | + currentSelectorPathElementIndex = 0; |
| 224 | + currentSelectorPathIndex++; |
| 225 | + } |
| 226 | + } |
| 227 | + |
| 228 | + if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) { |
| 229 | + path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex)); |
| 230 | + currentSelectorPathElementIndex = 0; |
| 231 | + currentSelectorPathIndex++; |
| 232 | + } |
| 233 | + |
| 234 | + path = path.concat(selectorPath.slice(currentSelectorPathIndex, selectorPath.length)); |
| 235 | + |
| 236 | + return path; |
| 237 | + }, |
204 | 238 | visitRulesetOut: function (rulesetNode) {
|
205 | 239 | },
|
206 | 240 | visitMedia: function (mediaNode, visitArgs) {
|
|
0 commit comments