/* Slick Parser - originally created by the almighty Thomas Aylott <@subtlegradient> (http://subtlegradient.com) */"use strict" // Notable changes from Slick.Parser 1.0.x // The parser now uses 2 classes: Expressions and Expression // `new Expressions` produces an array-like object containing a list of Expression objects // - Expressions::toString() produces a cleaned up expressions string // `new Expression` produces an array-like object // - Expression::toString() produces a cleaned up expression string // The only exposed method is parse, which produces a (cached) `new Expressions` instance // parsed.raw is no longer present, use .toString() // parsed.expression is now useless, just use the indices // parsed.reverse() has been removed for now, due to its apparent uselessness // Other changes in the Expressions object: // - classNames are now unique, and save both escaped and unescaped values // - attributes now save both escaped and unescaped values // - pseudos now save both escaped and unescaped values var escapeRe = /([-.*+?^${}()|[\]\/\\])/g, unescapeRe = /\\/g var escape = function(string){ // XRegExp v2.0.0-beta-3 // « https://github.com/slevithan/XRegExp/blob/master/src/xregexp.js return (string + "").replace(escapeRe, '\\$1') } var unescape = function(string){ return (string + "").replace(unescapeRe, '') } var slickRe = RegExp( /* #!/usr/bin/env ruby puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'') __END__ "(?x)^(?:\ \\s* ( , ) \\s* # Separator \n\ | \\s* ( + ) \\s* # Combinator \n\ | ( \\s+ ) # CombinatorChildren \n\ | ( + | \\* ) # Tag \n\ | \\# ( + ) # ID \n\ | \\. ( + ) # ClassName \n\ | # Attribute \n\ \\[ \ \\s* (+) (?: \ \\s* ([*^$!~|]?=) (?: \ \\s* (?:\ ([\"']?)(.*?)\\9 \ )\ ) \ )? \\s* \ \\](?!\\]) \n\ | :+ ( + )(?:\ \\( (?:\ (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\ ) \\)\ )?\ )" */ "^(?:\\s*(,)\\s*|\\s*(+)\\s*|(\\s+)|(+|\\*)|\\#(+)|\\.(+)|\\[\\s*(+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)" .replace(//, '[' + escape(">+~`!@$%^&={}\\;/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])') .replace(//g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])') ) // Part var Part = function Part(combinator){ this.combinator = combinator || " " this.tag = "*" } Part.prototype.toString = function(){ if (!this.raw){ var xpr = "", k, part xpr += this.tag || "*" if (this.id) xpr += "#" + this.id if (this.classes) xpr += "." + this.classList.join(".") if (this.attributes) for (k = 0; part = this.attributes[k++];){ xpr += "[" + part.name + (part.operator ? part.operator + '"' + part.value + '"' : '') + "]" } if (this.pseudos) for (k = 0; part = this.pseudos[k++];){ xpr += ":" + part.name if (part.value) xpr += "(" + part.value + ")" } this.raw = xpr } return this.raw } // Expression var Expression = function Expression(){ this.length = 0 } Expression.prototype.toString = function(){ if (!this.raw){ var xpr = "" for (var j = 0, bit; bit = this[j++];){ if (j !== 1) xpr += " " if (bit.combinator !== " ") xpr += bit.combinator + " " xpr += bit } this.raw = xpr } return this.raw } var replacer = function( rawMatch, separator, combinator, combinatorChildren, tagName, id, className, attributeKey, attributeOperator, attributeQuote, attributeValue, pseudoMarker, pseudoClass, pseudoQuote, pseudoClassQuotedValue, pseudoClassValue ){ var expression, current if (separator || !this.length){ expression = this[this.length++] = new Expression if (separator) return '' } if (!expression) expression = this[this.length - 1] if (combinator || combinatorChildren || !expression.length){ current = expression[expression.length++] = new Part(combinator) } if (!current) current = expression[expression.length - 1] if (tagName){ current.tag = unescape(tagName) } else if (id){ current.id = unescape(id) } else if (className){ var unescaped = unescape(className) var classes = current.classes || (current.classes = {}) if (!classes[unescaped]){ classes[unescaped] = escape(className) var classList = current.classList || (current.classList = []) classList.push(unescaped) classList.sort() } } else if (pseudoClass){ pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue ;(current.pseudos || (current.pseudos = [])).push({ type : pseudoMarker.length == 1 ? 'class' : 'element', name : unescape(pseudoClass), escapedName : escape(pseudoClass), value : pseudoClassValue ? unescape(pseudoClassValue) : null, escapedValue : pseudoClassValue ? escape(pseudoClassValue) : null }) } else if (attributeKey){ attributeValue = attributeValue ? escape(attributeValue) : null ;(current.attributes || (current.attributes = [])).push({ operator : attributeOperator, name : unescape(attributeKey), escapedName : escape(attributeKey), value : attributeValue ? unescape(attributeValue) : null, escapedValue : attributeValue ? escape(attributeValue) : null }) } return '' } // Expressions var Expressions = function Expressions(expression){ this.length = 0 var self = this var original = expression, replaced while (expression){ replaced = expression.replace(slickRe, function(){ return replacer.apply(self, arguments) }) if (replaced === expression) throw new Error(original + ' is an invalid expression') expression = replaced } } Expressions.prototype.toString = function(){ if (!this.raw){ var expressions = [] for (var i = 0, expression; expression = this[i++];) expressions.push(expression) this.raw = expressions.join(", ") } return this.raw } var cache = {} var parse = function(expression){ if (expression == null) return null expression = ('' + expression).replace(/^\s+|\s+$/g, '') return cache[expression] || (cache[expression] = new Expressions(expression)) } module.exports = parse