diff --git a/README.md b/README.md index fe4b5dc7..eba98817 100644 --- a/README.md +++ b/README.md @@ -109,8 +109,8 @@ SelectorObject format { "raw":"#foo > bar.baz", "expressions": [[ - { "combinator":" ", "tag":"*", "id":"foo", "parts": [] }, - { "combinator":">", "tag":"bar", "classList": ["baz"], "classes": [{ "type":"class", "value":"baz", "regexp":RegExp }], "parts": [{ "type":"class", "value":"baz", "regexp":RegExp }] } + { "combinator":" ", "tag":"*", "id":"foo" }, + { "combinator":">", "tag":"bar", "classList": ["baz"], "classes": [{"value":"baz", "regexp":RegExp }]} ]] } @@ -119,9 +119,9 @@ SelectorObject format { "raw": "h1, h2, ul > li, .things", "expressions": [ - [{ "combinator":" ", "tag": "h1", "parts": [] }], - [{ "combinator":" ", "tag": "h2", "parts": [] }], - [{ "combinator":" ", "tag": "ul", "parts": [] }, { "combinator": ">", "tag": "li", "parts": [] }], - [{ "combinator":" ", "tag": "*", "classList": ["things"], "classes": [{ "type": "class", "value": "things", "regexp":RegExp }], "parts": [{ "type": "class", "value": "things", "regexp":RegExp }] }] + [{ "combinator":" ", "tag": "h1" }], + [{ "combinator":" ", "tag": "h2" }], + [{ "combinator":" ", "tag": "ul" }, { "combinator": ">", "tag": "li" }], + [{ "combinator":" ", "tag": "*", "classList": ["things"], "classes": [{"value": "things", "regexp":RegExp }] }] ] } diff --git a/Source/Slick.Finder.js b/Source/Slick.Finder.js index 9baee5b6..ce27a055 100644 --- a/Source/Slick.Finder.js +++ b/Source/Slick.Finder.js @@ -234,12 +234,9 @@ local.search = function(context, expression, append, first){ // default engine - var currentExpression, currentBit; var j, m, n; - var combinator, tag, id, parts, classes; - var currentItems; - var expressions = parsed.expressions; - var lastBit; + var combinator, tag, id, classList, classes, attributes, pseudos; + var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions; search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){ @@ -248,8 +245,10 @@ local.search = function(context, expression, append, first){ tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase(); id = currentBit.id; - parts = currentBit.parts; - classes = currentBit.classList; + classList = currentBit.classList; + classes = currentBit.classes; + attributes = currentBit.attributes; + pseudos = currentBit.pseudos; lastBit = (j === (currentExpression.length - 1)); this.bitUniques = {}; @@ -263,13 +262,13 @@ local.search = function(context, expression, append, first){ } if (j === 0){ - this[combinator](context, tag, id, parts, classes); + this[combinator](context, tag, id, classes, attributes, pseudos, classList); if (first && lastBit && found.length) break search; } else { if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){ - this[combinator](currentItems[m], tag, id, parts, classes); + this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList); if (found.length) break search; - } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, parts, classes); + } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList); } currentItems = this.found; @@ -359,13 +358,13 @@ local.createNTHPseudo = function(child, sibling, positions, ofType){ } }; -local.pushArray = function(node, tag, id, selector, classes){ - if (this.matchSelector(node, tag, id, selector, classes)) this.found.push(node); +local.pushArray = function(node, tag, id, classes, attributes, pseudos){ + if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node); }; -local.pushUID = function(node, tag, id, selector, classes){ +local.pushUID = function(node, tag, id, classes, attributes, pseudos){ var uid = this.getUID(node); - if (!this.uniques[uid] && this.matchSelector(node, tag, id, selector, classes)){ + if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){ this.uniques[uid] = true; this.found.push(node); } @@ -378,11 +377,11 @@ local.matchNode = function(node, selector){ // simple (single) selectors if(parsed.length == 1 && parsed.expressions[0].length == 1){ var exp = parsed.expressions[0][0]; - return this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.parts); + return this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos); } var nodes = this.search(this.document, parsed); - for (var i=0, item; item = nodes[i++];){ + for (var i = 0, item; item = nodes[i++];){ if (item === node) return true; } return false; @@ -395,7 +394,7 @@ local.matchPseudo = function(node, name, argument){ return (argument) ? argument == attribute : !!attribute; }; -local.matchSelector = function(node, tag, id, parts, classes){ +local.matchSelector = function(node, tag, id, classes, attributes, pseudos){ if (tag){ if (tag == '*'){ if (node.nodeName < '@') return false; // Fix for comment nodes and closed nodes @@ -404,23 +403,28 @@ local.matchSelector = function(node, tag, id, parts, classes){ } } if (id && node.getAttribute('id') != id) return false; - if (parts) for (var i = 0, l = parts.length, part, cls; i < l; i++){ - part = parts[i]; - if (part.type == 'class' && classes !== false){ - cls = ('className' in node) ? node.className : node.getAttribute('class'); - if (!(cls && part.regexp.test(cls))) return false; - } - if (part.type == 'pseudo' && (!this.matchPseudo(node, part.key, part.value))) return false; - if (part.type == 'attribute' && (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key))) return false; + + var i, part, cls; + if (classes) for (i = classes.length; i--;){ + cls = ('className' in node) ? node.className : node.getAttribute('class'); + if (!(cls && classes[i].regexp.test(cls))) return false; + } + if (attributes) for (i = attributes.length; i--;){ + part = attributes[i]; + if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false; + } + if (pseudos) for (i = pseudos.length; i--;){ + part = pseudos[i]; + if (!this.matchPseudo(node, part.key, part.value)) return false; } return true; }; var combinators = { - ' ': function(node, tag, id, parts, classes){ // all child nodes, any level + ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level - var i, l, item, children; + var i, item, children; if (!this.isXMLDocument){ getById: if (id){ @@ -432,7 +436,7 @@ var combinators = { if (!children) return; if (!children[0]) children = [children]; for (i = 0; item = children[i++];) if (item.getAttributeNode('id').nodeValue == id){ - this.push(item, tag, null, parts); + this.push(item, tag, null, classes, attributes, pseudos); break; } return; @@ -442,13 +446,13 @@ var combinators = { if (this.contains(this.document.documentElement, node)) return; else break getById; } else if (this.document !== node && !this.contains(node, item)) return; - this.push(item, tag, null, parts); + this.push(item, tag, null, classes, attributes, pseudos); return; } getByClass: if (node.getElementsByClassName && classes && !this.brokenGEBCN){ - children = node.getElementsByClassName(classes.join(' ')); + children = node.getElementsByClassName(classList.join(' ')); if (!(children && children.length)) break getByClass; - for (i = 0, l = children.length; i < l; i++) this.push(children[i], tag, id, parts, false); + for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos); return; } } @@ -456,84 +460,83 @@ var combinators = { children = node.getElementsByTagName(tag); if (!(children && children.length)) break getByTag; if (!this.brokenStarGEBTN) tag = null; - var child; - for (i = 0; child = children[i++];) this.push(child, tag, id, parts); + for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos); } }, - '!': function(node, tag, id, parts){ // all parent nodes up to document - while ((node = node.parentNode)) if (node !== document) this.push(node, tag, id, parts); + '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document + while ((node = node.parentNode)) if (node !== document) this.push(node, tag, id, classes, attributes, pseudos); }, - '>': function(node, tag, id, parts){ // direct children + '>': function(node, tag, id, classes, attributes, pseudos){ // direct children if ((node = node.firstChild)) do { - if (node.nodeType === 1) this.push(node, tag, id, parts); + if (node.nodeType === 1) this.push(node, tag, id, classes, attributes, pseudos); } while ((node = node.nextSibling)); }, - '!>': function(node, tag, id, parts){ // direct parent (one level) + '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level) node = node.parentNode; - if (node !== document) this.push(node, tag, id, parts); + if (node !== document) this.push(node, tag, id, classes, attributes, pseudos); }, - '+': function(node, tag, id, parts){ // next sibling + '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling while ((node = node.nextSibling)) if (node.nodeType === 1){ - this.push(node, tag, id, parts); + this.push(node, tag, id, classes, attributes, pseudos); break; } }, - '!+': function(node, tag, id, parts){ // previous sibling + '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling while ((node = node.previousSibling)) if (node.nodeType === 1){ - this.push(node, tag, id, parts); + this.push(node, tag, id, classes, attributes, pseudos); break; } }, - '^': function(node, tag, id, parts){ // first child + '^': function(node, tag, id, classes, attributes, pseudos){ // first child node = node.firstChild; if (node){ - if (node.nodeType === 1) this.push(node, tag, id, parts); - else this['combinator:+'](node, tag, id, parts); + if (node.nodeType === 1) this.push(node, tag, id, classes, attributes, pseudos); + else this['combinator:+'](node, tag, id, classes, attributes, pseudos); } }, - '!^': function(node, tag, id, parts){ // last child + '!^': function(node, tag, id, classes, attributes, pseudos){ // last child node = node.lastChild; if (node){ - if (node.nodeType === 1) this.push(node, tag, id, parts); - else this['combinator:!+'](node, tag, id, parts); + if (node.nodeType === 1) this.push(node, tag, id, classes, attributes, pseudos); + else this['combinator:!+'](node, tag, id, classes, attributes, pseudos); } }, - '~': function(node, tag, id, parts){ // next siblings + '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings while ((node = node.nextSibling)){ if (node.nodeType !== 1) continue; var uid = this.getUID(node); if (this.bitUniques[uid]) break; this.bitUniques[uid] = true; - this.push(node, tag, id, parts); + this.push(node, tag, id, classes, attributes, pseudos); } }, - '!~': function(node, tag, id, parts){ // previous siblings + '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings while ((node = node.previousSibling)){ if (node.nodeType !== 1) continue; var uid = this.getUID(node); if (this.bitUniques[uid]) break; this.bitUniques[uid] = true; - this.push(node, tag, id, parts); + this.push(node, tag, id, classes, attributes, pseudos); } }, - '++': function(node, tag, id, parts){ // next sibling and previous sibling - this['combinator:+'](node, tag, id, parts); - this['combinator:!+'](node, tag, id, parts); + '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling + this['combinator:+'](node, tag, id, classes, attributes, pseudos); + this['combinator:!+'](node, tag, id, classes, attributes, pseudos); }, - '~~': function(node, tag, id, parts){ // next siblings and previous siblings - this['combinator:~'](node, tag, id, parts); - this['combinator:!~'](node, tag, id, parts); + '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings + this['combinator:~'](node, tag, id, classes, attributes, pseudos); + this['combinator:!~'](node, tag, id, classes, attributes, pseudos); } }; diff --git a/Source/Slick.Parser.js b/Source/Slick.Parser.js index 936b354a..accf2849 100644 --- a/Source/Slick.Parser.js +++ b/Source/Slick.Parser.js @@ -13,7 +13,6 @@ var exports = this; var parsed, separatorIndex, combinatorIndex, - partIndex, reversed, cache = {}, reverseCache = {}, @@ -129,8 +128,7 @@ function parser( var currentSeparator = parsed.expressions[separatorIndex]; if (reversed && currentSeparator[combinatorIndex]) currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator); - currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*', parts: []}; - partIndex = -1; + currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'}; } var currentParsed = parsed.expressions[separatorIndex][combinatorIndex]; @@ -142,32 +140,26 @@ function parser( currentParsed.id = id.replace(reUnescape, ''); } else if (className){ - if (!currentParsed.classes) currentParsed.classes = []; - if (!currentParsed.classList) currentParsed.classList = []; - className = className.replace(reUnescape, ''); + + if (!currentParsed.classList) currentParsed.classList = []; + if (!currentParsed.classes) currentParsed.classes = []; currentParsed.classList.push(className); - - currentParsed.classes.push(currentParsed.parts[++partIndex] = { - type: 'class', + currentParsed.classes.push({ value: className, regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)') }); } else if (pseudoClass){ - if (!currentParsed.pseudos) currentParsed.pseudos = []; - pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null; - - currentParsed.pseudos.push(currentParsed.parts[++partIndex] = { - type: 'pseudo', + + if (!currentParsed.pseudos) currentParsed.pseudos = []; + currentParsed.pseudos.push({ key: pseudoClass.replace(reUnescape, ''), value: pseudoClassValue }); } else if (attributeKey){ - if (!currentParsed.attributes) currentParsed.attributes = []; - attributeKey = attributeKey.replace(reUnescape, ''); attributeValue = (attributeValue || '').replace(reUnescape, ''); @@ -196,8 +188,8 @@ function parser( return value && regexp.test(value); }; - currentParsed.attributes.push(currentParsed.parts[++partIndex] = { - type: 'attribute', + if (!currentParsed.attributes) currentParsed.attributes = []; + currentParsed.attributes.push({ key: attributeKey, operator: attributeOperator, value: attributeValue,