| 4 | | this.params = {classNames: []}; |
|---|
| 5 | | this.expression = expression.toString().strip(); |
|---|
| 6 | | this.parseExpression(); |
|---|
| 7 | | this.compileMatcher(); |
|---|
| 8 | | }, |
|---|
| 9 | | |
|---|
| 10 | | parseExpression: function() { |
|---|
| 11 | | function abort(message) { throw 'Parse error in selector: ' + message; } |
|---|
| 12 | | |
|---|
| 13 | | if (this.expression == '') abort('empty expression'); |
|---|
| 14 | | |
|---|
| 15 | | var params = this.params, expr = this.expression, match, modifier, clause, rest; |
|---|
| 16 | | while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { |
|---|
| 17 | | params.attributes = params.attributes || []; |
|---|
| 18 | | params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); |
|---|
| 19 | | expr = match[1]; |
|---|
| 20 | | } |
|---|
| 21 | | |
|---|
| 22 | | if (expr == '*') return this.params.wildcard = true; |
|---|
| 23 | | |
|---|
| 24 | | while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { |
|---|
| 25 | | modifier = match[1], clause = match[2], rest = match[3]; |
|---|
| 26 | | switch (modifier) { |
|---|
| 27 | | case '#': params.id = clause; break; |
|---|
| 28 | | case '.': params.classNames.push(clause); break; |
|---|
| 29 | | case '': |
|---|
| 30 | | case undefined: params.tagName = clause.toUpperCase(); break; |
|---|
| 31 | | default: abort(expr.inspect()); |
|---|
| 32 | | } |
|---|
| 33 | | expr = rest; |
|---|
| 34 | | } |
|---|
| 35 | | |
|---|
| 36 | | if (expr.length > 0) abort(expr.inspect()); |
|---|
| 37 | | }, |
|---|
| 38 | | |
|---|
| 39 | | buildMatchExpression: function() { |
|---|
| 40 | | var params = this.params, conditions = [], clause; |
|---|
| 41 | | |
|---|
| 42 | | if (params.wildcard) |
|---|
| 43 | | conditions.push('true'); |
|---|
| 44 | | if (clause = params.id) |
|---|
| 45 | | conditions.push('element.readAttribute("id") == ' + clause.inspect()); |
|---|
| 46 | | if (clause = params.tagName) |
|---|
| 47 | | conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); |
|---|
| 48 | | if ((clause = params.classNames).length > 0) |
|---|
| 49 | | for (var i = 0, length = clause.length; i < length; i++) |
|---|
| 50 | | conditions.push('element.hasClassName(' + clause[i].inspect() + ')'); |
|---|
| 51 | | if (clause = params.attributes) { |
|---|
| 52 | | clause.each(function(attribute) { |
|---|
| 53 | | var value = 'element.readAttribute(' + attribute.name.inspect() + ')'; |
|---|
| 54 | | var splitValueBy = function(delimiter) { |
|---|
| 55 | | return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; |
|---|
| 56 | | } |
|---|
| | 5 | this.expression = expression.strip(); |
|---|
| | 6 | this.compileMatcher(); |
|---|
| | 7 | }, |
|---|
| | 8 | |
|---|
| | 9 | compileMatcher: function() { |
|---|
| | 10 | // FIXME: There's no way to query namespaced attributes with |
|---|
| | 11 | // document.evaluate, so I think punting back to the non-XPath approach |
|---|
| | 12 | // is the only option |
|---|
| | 13 | if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression)) |
|---|
| | 14 | return this.compileXPathMatcher(); |
|---|
| | 15 | |
|---|
| | 16 | var e = this.expression, ps = Selector.patterns, h = Selector.handlers, |
|---|
| | 17 | c = Selector.criteria, le, p, m; |
|---|
| | 18 | |
|---|
| | 19 | if (Selector._cache[e]) { |
|---|
| | 20 | this.matcher = Selector._cache[e]; |
|---|
| | 21 | return; |
|---|
| | 22 | } |
|---|
| | 23 | this.nodes = undefined; |
|---|
| | 24 | this.matcher = ["this.matcher = function(root) {\n", |
|---|
| | 25 | "var n = this.nodes, r = root,\n", |
|---|
| | 26 | "h = Selector.handlers, d = false;\n"]; |
|---|
| | 27 | |
|---|
| | 28 | while (e && (/\S/).test(e) && le != e) { |
|---|
| | 29 | le = e; |
|---|
| | 30 | for (var i in ps) { |
|---|
| | 31 | p = ps[i]; |
|---|
| | 32 | if (m = e.match(p)) { |
|---|
| | 33 | this.matcher.push(typeof c[i] == 'function' ? c[i](m) : |
|---|
| | 34 | new Template(c[i]).evaluate(m)); |
|---|
| | 35 | e = e.replace(m[0], ''); |
|---|
| | 36 | break; |
|---|
| | 37 | } |
|---|
| | 38 | } |
|---|
| | 39 | } |
|---|
| | 40 | |
|---|
| | 41 | this.matcher.push("return h.unique(n);\n}"); |
|---|
| | 42 | eval(this.matcher.join('')); |
|---|
| | 43 | Selector._cache[this.expression] = this.matcher; |
|---|
| | 44 | }, |
|---|
| | 45 | |
|---|
| | 46 | compileXPathMatcher: function() { |
|---|
| | 47 | var e = this.expression, ps = Selector.patterns, |
|---|
| | 48 | x = Selector.xpath, le, p, m; |
|---|
| | 49 | |
|---|
| | 50 | if (Selector._cache[e]) { |
|---|
| | 51 | this.xpath = Selector._cache[e]; |
|---|
| | 52 | return; |
|---|
| | 53 | } |
|---|
| | 54 | this.matcher = [".//"]; |
|---|
| | 55 | while (e && (/\S/).test(e) && le != e) { |
|---|
| | 56 | le = e; |
|---|
| | 57 | for (var i in ps) { |
|---|
| | 58 | if (m = e.match(ps[i])) { |
|---|
| | 59 | this.matcher.push(typeof x[i] == 'function' ? x[i](m) : |
|---|
| | 60 | new Template(x[i]).evaluate(m)); |
|---|
| | 61 | e = e.replace(m[0], ''); |
|---|
| | 62 | break; |
|---|
| | 63 | } |
|---|
| | 64 | } |
|---|
| | 65 | } |
|---|
| | 66 | |
|---|
| | 67 | this.xpath = this.matcher.join(''); |
|---|
| | 68 | |
|---|
| | 69 | // FIXME: there's got to be a better way to do this... |
|---|
| | 70 | // adjacency needs "[1]" as the first predicate |
|---|
| | 71 | this.xpath = this.xpath.replace(/following-sibling::\[1\](.*?)(\[|\/|$)/g, |
|---|
| | 72 | 'following-sibling::$1[1]$2'); |
|---|
| | 73 | // any axes not immediately followed by tag names need "*" |
|---|
| | 74 | this.xpath = this.xpath.replace(/(\/\/|::|\/)\[/g, '$1*['); |
|---|
| | 75 | Selector._cache[this.expression] = this.xpath; |
|---|
| | 76 | }, |
|---|
| | 77 | |
|---|
| | 78 | findElements: function(root) { |
|---|
| | 79 | root = root || document; |
|---|
| | 80 | if (this.xpath) |
|---|
| | 81 | return document._getElementsByXPath(this.xpath, root); |
|---|
| | 82 | return this.matcher(root); |
|---|
| | 83 | }, |
|---|
| | 84 | |
|---|
| | 85 | match: function(element) { |
|---|
| | 86 | this.results = this.results || this.findElements(document); |
|---|
| | 87 | return this.results.include(element); |
|---|
| | 88 | //return this.findElements(document).include(element); |
|---|
| | 89 | } |
|---|
| | 90 | }; |
|---|
| | 91 | |
|---|
| | 92 | Object.extend(Selector, { |
|---|
| | 93 | _cache: {}, |
|---|
| | 94 | |
|---|
| | 95 | xpath: { |
|---|
| | 96 | descendant: "//", |
|---|
| | 97 | child: "/", |
|---|
| | 98 | adjacent: "/following-sibling::[1]", |
|---|
| | 99 | sibling: '/following-sibling::', |
|---|
| | 100 | tagName: "#{1}", |
|---|
| | 101 | className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", |
|---|
| | 102 | id: "[@id='#{1}']", |
|---|
| | 103 | attrPresence: "[@#{1}]", |
|---|
| | 104 | attr: function(m) { |
|---|
| | 105 | m[3] = m[5] || m[6]; |
|---|
| | 106 | return new Template(Selector.xpath.operators[m[2]]).evaluate(m); |
|---|
| | 107 | }, |
|---|
| | 108 | pseudo: function(m) { |
|---|
| | 109 | var h = Selector.xpath.pseudos[m[1]]; |
|---|
| | 110 | if (!h) return; |
|---|
| | 111 | if (typeof h === 'function') return h(m); |
|---|
| | 112 | return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); |
|---|
| | 113 | }, |
|---|
| | 114 | |
|---|
| | 115 | operators: { |
|---|
| | 116 | '=': "[@#{1}='#{3}']", |
|---|
| | 117 | '!=': "[@#{1}!='#{3}']", |
|---|
| | 118 | '^=': "[substring(@#{1}, 1, string-length('#{3}'))='#{3}']", |
|---|
| | 119 | '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", |
|---|
| | 120 | '*=': "[contains(@#{1}, '#{3}')]", |
|---|
| | 121 | '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", |
|---|
| | 122 | '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" |
|---|
| | 123 | }, |
|---|
| | 124 | pseudos: { |
|---|
| | 125 | 'first-child': '[position()=1]', |
|---|
| | 126 | 'last-child': '[position()=last()]', |
|---|
| | 127 | 'empty': '[not(node())]', |
|---|
| | 128 | 'checked': "[@checked='checked']", |
|---|
| | 129 | 'enabled': "[not(@disabled='disabled')]", |
|---|
| | 130 | 'disabled': "[@disabled='disabled']", |
|---|
| | 131 | 'not': "[not(#{4})]", |
|---|
| | 132 | 'not': function(m) { |
|---|
| | 133 | if (!m[4]) return; |
|---|
| | 134 | for (var i in Selector.patterns) { |
|---|
| | 135 | if (mm = m[4].match(Selector.patterns[i])) { |
|---|
| | 136 | var ss = new Template(Selector.xpath[i]).evaluate(mm); |
|---|
| | 137 | // FIXME: This is an ugly way to trim the brackets |
|---|
| | 138 | m[4] = ss.substring(1, ss.length - 1); |
|---|
| | 139 | } |
|---|
| | 140 | } |
|---|
| | 141 | return "[not(" + m[4] + ")]"; |
|---|
| | 142 | }, |
|---|
| | 143 | 'nth-child': function(m) { |
|---|
| | 144 | var predicate, mm, formula = m[4]; |
|---|
| | 145 | if (["odd", "even"].include(formula)) |
|---|
| | 146 | predicate = "position() mod 2 = " + (formula == 'odd' ? 1 : 0); |
|---|
| | 147 | if (mm = formula.match(/^(\d+)$/)) // digit only |
|---|
| | 148 | predicate = "position() = " + mm[1]; |
|---|
| | 149 | if (mm = formula.match(/^(\d+)?n(\+(\d+))?/)) { // an+b |
|---|
| | 150 | var a = m[1] !== undefined ? Number(m[1]) : 1; |
|---|
| | 151 | var b = m[3] !== undefined ? Number(m[2]) : 0; |
|---|
| | 152 | predicate = "position() mod " + mm[1] + " = " + mm[3]; |
|---|
| | 153 | } |
|---|
| | 154 | return "[" + predicate + "]"; |
|---|
| | 155 | } |
|---|
| | 156 | } |
|---|
| | 157 | }, |
|---|
| | 158 | |
|---|
| | 159 | criteria: { |
|---|
| | 160 | tagName: 'n = h.tagName(n, r, "#{1}", d); d = false;', |
|---|
| | 161 | className: 'n = h.className(n, r, "#{1}", d); d = false;', |
|---|
| | 162 | id: 'n = h.id(n, r, "#{1}", d); d = false;', |
|---|
| | 163 | attrPresence: 'n = h.attrPresence(n, r, "#{1}"); d = false;', |
|---|
| | 164 | attr: function(m) { |
|---|
| | 165 | m[3] = m[5] || m[6]; |
|---|
| | 166 | return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); d = false;').evaluate(m); |
|---|
| | 167 | }, |
|---|
| | 168 | pseudo: 'n = h.pseudo(n, "#{1}", "#{4}", r, d); d = false;', |
|---|
| | 169 | descendant: 'd = "descendant";', |
|---|
| | 170 | child: 'd = "child";', |
|---|
| | 171 | adjacent: 'd = "adjacent";', |
|---|
| | 172 | sibling: 'd = "sibling";' |
|---|
| | 173 | }, |
|---|
| | 174 | |
|---|
| | 175 | patterns: { |
|---|
| | 176 | // combinators must be listed first |
|---|
| | 177 | // (and descendant needs to be last combinator) |
|---|
| | 178 | sibling: /^\s*~\s*/, |
|---|
| | 179 | child: /^\s*>\s*/, |
|---|
| | 180 | adjacent: /^\s*\+\s*/, |
|---|
| | 181 | descendant: /^\s/, |
|---|
| | 182 | |
|---|
| | 183 | // selectors follow |
|---|
| | 184 | tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, |
|---|
| | 185 | id: /^#([\w\-\*]+)(\b|$)/, |
|---|
| | 186 | className: /^\.([\w\-\*]+)(\b|$)/, |
|---|
| | 187 | attrPresence: /^\[([\w]+)\]/, |
|---|
| | 188 | pseudo: /^:((first|last|nth|only)-child|empty|checked|enabled|disabled|not)(\((.*?)\))?(\b|$)/, |
|---|
| | 189 | attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/ |
|---|
| | 190 | }, |
|---|
| | 191 | |
|---|
| | 192 | handlers: { |
|---|
| | 193 | // returns a flat list of all descendants of the node collection |
|---|
| | 194 | descendant: function(nodes) { |
|---|
| | 195 | for (var i = 0, results = [], node; node = nodes[i]; i++) |
|---|
| | 196 | Selector.handlers.concat(results, Element.descendants(node)); |
|---|
| | 197 | return results; |
|---|
| | 198 | }, |
|---|
| | 199 | |
|---|
| | 200 | child: function(nodes) { |
|---|
| | 201 | var results = []; |
|---|
| | 202 | for (var i = 0, node; node = nodes[i]; i++) |
|---|
| | 203 | Selector.handlers.concat(results, Element.immediateDescendants(node)); |
|---|
| | 204 | return results; |
|---|
| | 205 | }, |
|---|
| | 206 | |
|---|
| | 207 | adjacent: function(nodes) { |
|---|
| | 208 | var results = []; |
|---|
| | 209 | for (var i = 0, node; node = nodes[i]; i++) { |
|---|
| | 210 | var next = this.nextElementSibling(node); |
|---|
| | 211 | if (next) results.push(next); |
|---|
| | 212 | } |
|---|
| | 213 | return results; |
|---|
| | 214 | }, |
|---|
| | 215 | |
|---|
| | 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)); |
|---|
| | 220 | return results; |
|---|
| | 221 | }, |
|---|
| | 222 | |
|---|
| | 223 | tagName: function(nodes, root, tagName, combinator) { |
|---|
| | 224 | tagName = tagName.toUpperCase(); |
|---|
| | 225 | var results = [], h = Selector.handlers; |
|---|
| | 226 | if (nodes) { |
|---|
| | 227 | if (combinator) { |
|---|
| | 228 | // fastlane for ordinary descendant combinators |
|---|
| | 229 | if (combinator == "descendant") { |
|---|
| | 230 | for (var i = 0, node; node = nodes[i]; i++) { |
|---|
| | 231 | var subnodes = node.getElementsByTagName(tagName); |
|---|
| | 232 | h.concat(results, subnodes); |
|---|
| | 233 | } |
|---|
| | 234 | return results; |
|---|
| | 235 | } |
|---|
| | 236 | nodes = this[combinator](nodes); |
|---|
| | 237 | } |
|---|
| | 238 | for (var i = 0, node; node = nodes[i]; i++) { |
|---|
| | 239 | if (node.tagName.toUpperCase() == tagName) |
|---|
| | 240 | results.push(node); |
|---|
| | 241 | } |
|---|
| | 242 | return results; |
|---|
| | 243 | } |
|---|
| | 244 | return root.getElementsByTagName(tagName); |
|---|
| | 245 | }, |
|---|
| | 246 | |
|---|
| | 247 | className: function(nodes, root, className, combinator) { |
|---|
| | 248 | var results = [], h = Selector.handlers; |
|---|
| | 249 | if (nodes) { |
|---|
| | 250 | if (combinator) nodes = this[combinator](nodes); |
|---|
| | 251 | return h.byClassName(nodes, root, className); |
|---|
| | 252 | } |
|---|
| | 253 | return h.byClassName(nodes, root, className); |
|---|
| | 254 | //return document.getElementsByClassName(className, root); |
|---|
| | 255 | }, |
|---|
| | 256 | |
|---|
| | 257 | id: function(nodes, root, id, combinator) { |
|---|
| | 258 | var targetNode = $(id); |
|---|
| | 259 | if (!nodes && root == document) return [targetNode]; |
|---|
| | 260 | if (nodes) { |
|---|
| | 261 | if (combinator) { |
|---|
| | 262 | if (combinator == 'child') { |
|---|
| | 263 | for (var i = 0, node; node = nodes[i]; i++) |
|---|
| | 264 | if (targetNode.parentNode == node) return [targetNode]; |
|---|
| | 265 | } else if (combinator == 'descendant') { |
|---|
| | 266 | for (var i = 0, node; node = nodes[i]; i++) |
|---|
| | 267 | if (Element.descendantOf(targetNode, node)) return [targetNode]; |
|---|
| | 268 | } else if (combinator == 'adjacent') { |
|---|
| | 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]; |
|---|
| | 273 | } else nodes = Selector.handlers[combinator](nodes); |
|---|
| | 274 | } |
|---|
| | 275 | for (var i = 0, node; node = nodes[i]; i++) |
|---|
| | 276 | if (node == targetNode) return [targetNode]; |
|---|
| | 277 | return []; |
|---|
| | 278 | } |
|---|
| | 279 | return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; |
|---|
| | 280 | }, |
|---|
| | 281 | |
|---|
| | 282 | attrPresence: function(nodes, root, attr) { |
|---|
| | 283 | var results = []; |
|---|
| | 284 | for (var i = 0, node; node = nodes[i]; i++) { |
|---|
| | 285 | if (!node.hasAttribute) { |
|---|
| | 286 | if (Element.Methods.Simulated.hasAttribute(node, attr)) |
|---|
| | 287 | results.push(node); |
|---|
| | 288 | } else if (node.hasAttribute(attr)) results.push(node); |
|---|
| | 289 | } |
|---|
| | 290 | return results; |
|---|
| | 291 | }, |
|---|
| | 292 | |
|---|
| | 293 | attr: function(nodes, root, attr, value, operator) { |
|---|
| | 294 | var handler = Selector.operators[operator], results = []; |
|---|
| | 295 | for (var i = 0, node; node = nodes[i]; i++) { |
|---|
| | 296 | var nodeValue = Element.readAttribute(node, attr); |
|---|
| | 297 | if (nodeValue === null) continue; |
|---|
| | 298 | if (handler(nodeValue, value)) results.push(node); |
|---|
| | 299 | } |
|---|
| | 300 | return results; |
|---|
| | 301 | }, |
|---|
| | 302 | |
|---|
| | 303 | // joins two collections |
|---|
| | 304 | concat: function(a, b) { |
|---|
| | 305 | for (var i = 0, node; node = b[i]; i++) |
|---|
| | 306 | a.push(node); |
|---|
| | 307 | return a; |
|---|
| | 308 | }, |
|---|
| | 309 | |
|---|
| | 310 | // marks an array of nodes for counting |
|---|
| | 311 | mark: function(nodes) { |
|---|
| | 312 | for (var i = 0, node; node = nodes[i]; i++) |
|---|
| | 313 | node._counted = true; |
|---|
| | 314 | return nodes; |
|---|
| | 315 | }, |
|---|
| | 316 | |
|---|
| | 317 | unmark: function(nodes) { |
|---|
| | 318 | for (var i = 0, node; node = nodes[i]; i++) |
|---|
| | 319 | node._counted = undefined; |
|---|
| | 320 | return nodes; |
|---|
| | 321 | }, |
|---|
| | 322 | |
|---|
| | 323 | // filters out duplicates and extends all nodes |
|---|
| | 324 | unique: function(nodes) { |
|---|
| | 325 | if (nodes.length == 0) return nodes; |
|---|
| | 326 | var results = [nodes[0]], n; |
|---|
| | 327 | nodes[0]._counted = true; |
|---|
| | 328 | for (var i = 0, l = nodes.length; i < l; i++) { |
|---|
| | 329 | n = nodes[i]; |
|---|
| | 330 | if (!n._counted) { |
|---|
| | 331 | n._counted = true; |
|---|
| | 332 | results.push(Element.extend(n)); |
|---|
| | 333 | } |
|---|
| | 334 | } |
|---|
| | 335 | return Selector.handlers.unmark(results); |
|---|
| | 336 | }, |
|---|
| | 337 | |
|---|
| | 338 | byClassName: function(nodes, root, className) { |
|---|
| | 339 | if (!nodes) nodes = Selector.handlers.descendant([root]); |
|---|
| | 340 | var needle = [" ", className, " "].join(''); |
|---|
| | 341 | for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { |
|---|
| | 342 | nodeClassName = node.className; |
|---|
| | 343 | if (nodeClassName.length == 0) continue; |
|---|
| | 344 | if (nodeClassName == className || |
|---|
| | 345 | [" ", nodeClassName, " "].join('').include(needle)) |
|---|
| | 346 | results.push(node); |
|---|
| | 347 | } |
|---|
| | 348 | return results; |
|---|
| | 349 | }, |
|---|
| 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('element.hasAttribute(' + attribute.name.inspect() + ')'); break; |
|---|
| 67 | | default: throw 'Unknown operator ' + attribute.operator + ' in selector'; |
|---|
| 68 | | } |
|---|
| 69 | | }); |
|---|
| 70 | | } |
|---|
| 71 | | |
|---|
| 72 | | return conditions.join(' && '); |
|---|
| 73 | | }, |
|---|
| 74 | | |
|---|
| 75 | | compileMatcher: function() { |
|---|
| 76 | | this.match = new Function('element', 'if (!element.tagName) return false; \ |
|---|
| 77 | | element = $(element); \ |
|---|
| 78 | | return ' + this.buildMatchExpression()); |
|---|
| 79 | | }, |
|---|
| 80 | | |
|---|
| 81 | | findElements: function(scope) { |
|---|
| 82 | | var element; |
|---|
| 83 | | |
|---|
| 84 | | if (element = $(this.params.id)) |
|---|
| 85 | | if (this.match(element)) |
|---|
| 86 | | if (!scope || Element.childOf(element, scope)) |
|---|
| 87 | | return [element]; |
|---|
| 88 | | |
|---|
| 89 | | scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); |
|---|
| 90 | | |
|---|
| 91 | | var results = []; |
|---|
| 92 | | for (var i = 0, length = scope.length; i < length; i++) |
|---|
| 93 | | if (this.match(element = scope[i])) |
|---|
| 94 | | results.push(Element.extend(element)); |
|---|
| 95 | | |
|---|
| | 351 | nextElementSibling: function(node) { |
|---|
| | 352 | do { |
|---|
| | 353 | var node = node.nextSibling; |
|---|
| | 354 | } while (node.nodeType != 1 && node.nextSibling); |
|---|
| | 355 | return (node.nodeType == 1) ? node : null; |
|---|
| | 356 | }, |
|---|
| | 357 | |
|---|
| | 358 | previousElementSibling: function(node) { |
|---|
| | 359 | do { |
|---|
| | 360 | var node = node.previousSibling; |
|---|
| | 361 | } while (node.nodeType != 1 && node.previousSibling); |
|---|
| | 362 | return (node.nodeType == 1) ? node : null; |
|---|
| | 363 | }, |
|---|
| | 364 | |
|---|
| | 365 | pseudo: function(nodes, name, value, root, combinator) { |
|---|
| | 366 | if (combinator) nodes = this[combinator](nodes); |
|---|
| | 367 | return Selector.pseudos[name](nodes, value, root, combinator); |
|---|
| | 368 | }, |
|---|
| | 369 | |
|---|
| | 370 | index: function(node) { |
|---|
| | 371 | node._counted = true; |
|---|
| | 372 | for (var i = 0, j = 1, nodes = node.childNodes; node = nodes[i]; i++) |
|---|
| | 373 | if (node.nodeType == 1) node.nodeIndex = j++; |
|---|
| | 374 | } |
|---|
| | 375 | }, |
|---|
| | 376 | |
|---|
| | 377 | pseudos: { |
|---|
| | 378 | 'first-child': function(nodes, value, root) { |
|---|
| | 379 | for (var i = 0, results = [], node; node = nodes[i]; i++) { |
|---|
| | 380 | if (!Selector.handlers.previousElementSibling(node)) |
|---|
| | 381 | results.push(node); |
|---|
| | 382 | } |
|---|
| | 383 | return results; |
|---|
| | 384 | }, |
|---|
| | 385 | |
|---|
| | 386 | 'last-child': function(nodes, value, root) { |
|---|
| | 387 | for (var i = 0, results = [], node; node = nodes[i]; i++) { |
|---|
| | 388 | if (!Selector.handlers.nextElementSibling(node)) |
|---|
| | 389 | results.push(node); |
|---|
| | 390 | } |
|---|
| | 391 | return results; |
|---|
| | 392 | }, |
|---|
| | 393 | |
|---|
| | 394 | 'nth-child': function(nodes, formula, root) { |
|---|
| | 395 | var h = Selector.handlers, results = [], indexed = [], m; |
|---|
| | 396 | |
|---|
| | 397 | // index nodes ahead of time |
|---|
| | 398 | for (var i = 0, node; node = nodes[i]; i++) { |
|---|
| | 399 | if (!node.parentNode._counted) { |
|---|
| | 400 | h.index(node.parentNode); |
|---|
| | 401 | indexed.push(node.parentNode); |
|---|
| | 402 | } |
|---|
| | 403 | } |
|---|
| | 404 | if (formula == 'odd' || formula == 'even') { |
|---|
| | 405 | var mod = (formula == 'odd') ? 1 : 0; |
|---|
| | 406 | for (var i = 0, node; node = nodes[i]; i++) |
|---|
| | 407 | if ((node.nodeIndex + 1) % 2 == mod) results.push(node); |
|---|
| | 408 | } |
|---|
| | 409 | |
|---|
| | 410 | if (formula.match(/^\d+$/)) { // just a number |
|---|
| | 411 | formula = Number(formula); |
|---|
| | 412 | for (var i = 0, node; node = nodes[i]; i++) |
|---|
| | 413 | if (node.nodeIndex == formula) results.push(node); |
|---|
| | 414 | } |
|---|
| | 415 | |
|---|
| | 416 | if (m = formula.match(/^(\d+)?n(\+(\d+))?$/)) { // an+b |
|---|
| | 417 | var a = m[1] !== undefined ? Number(m[1]) : 1; |
|---|
| | 418 | var b = m[3] !== undefined ? Number(m[2]) : 0; |
|---|
| | 419 | for (var i = 0, node; node = nodes[i]; i++) |
|---|
| | 420 | if (node.nodeIndex % a == b) results.push(node); |
|---|
| | 421 | } |
|---|
| | 422 | |
|---|
| | 423 | // clean up indexing |
|---|
| | 424 | h.unmark(indexed); |
|---|
| | 425 | return results; |
|---|
| | 426 | }, |
|---|
| | 427 | |
|---|
| | 428 | 'only-child': function(nodes, value, root) { |
|---|
| | 429 | var h = Selector.handlers; |
|---|
| | 430 | for (var i = 0, results = [], node; node = nodes[i]; i++) { |
|---|
| | 431 | if (!h.previousElementSibling(node) && |
|---|
| | 432 | !h.nextElementSibling(node)) { |
|---|
| | 433 | results.push(node); |
|---|
| | 434 | } |
|---|
| | 435 | } |
|---|
| | 436 | return results; |
|---|
| | 437 | }, |
|---|
| | 438 | |
|---|
| | 439 | 'empty': function(nodes, value, root) { |
|---|
| | 440 | for (var i = 0, results = [], node; node = nodes[i]; i++) { |
|---|
| | 441 | if (node.firstChild || node.innerHTML.length > 0) continue; |
|---|
| | 442 | results.push(node); |
|---|
| | 443 | } |
|---|
| | 444 | return results; |
|---|
| | 445 | }, |
|---|
| | 446 | |
|---|
| | 447 | 'not': function(nodes, selector, root) { |
|---|
| | 448 | var h = Selector.handlers; |
|---|
| | 449 | |
|---|
| | 450 | var matches = new Selector(selector).findElements(root); |
|---|
| | 451 | h.mark(matches); |
|---|
| | 452 | for (var i = 0, results = [], node; node = nodes[i]; i++) |
|---|
| | 453 | if (!node._counted) results.push(node); |
|---|
| | 454 | h.unmark(matches); |
|---|
| | 455 | return results; |
|---|
| | 456 | } |
|---|
| | 457 | }, |
|---|
| | 458 | |
|---|
| | 459 | operators: { |
|---|
| | 460 | '=': function(nv, v) { return nv == v; }, |
|---|
| | 461 | '!=': function(nv, v) { return nv != v; }, |
|---|
| | 462 | '^=': function(nv, v) { return nv.startsWith(v); }, |
|---|
| | 463 | '$=': function(nv, v) { return nv.endsWith(v); }, |
|---|
| | 464 | '*=': function(nv, v) { return nv.include(v); }, |
|---|
| | 465 | '~=': function(nv, v) { return nv.split(/\s/).include(v); }, |
|---|
| | 466 | '|=': function(nv, v) { return nv.toUpperCase().split('-').include(v.toUpperCase()); } |
|---|
| | 467 | }, |
|---|
| | 468 | |
|---|
| | 469 | matchElements: function(elements, expression) { |
|---|
| | 470 | var selector = new Selector(expression), results = []; |
|---|
| | 471 | for (var i = 0, l = elements.length; i < l; i++) { |
|---|
| | 472 | if (selector.match(elements[i])) |
|---|
| | 473 | results.push(Element.extend(elements[i])); |
|---|
| | 474 | } |
|---|