You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

parser.js 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. /*
  2. Slick Parser
  3. - originally created by the almighty Thomas Aylott <@subtlegradient> (http://subtlegradient.com)
  4. */"use strict"
  5. // Notable changes from Slick.Parser 1.0.x
  6. // The parser now uses 2 classes: Expressions and Expression
  7. // `new Expressions` produces an array-like object containing a list of Expression objects
  8. // - Expressions::toString() produces a cleaned up expressions string
  9. // `new Expression` produces an array-like object
  10. // - Expression::toString() produces a cleaned up expression string
  11. // The only exposed method is parse, which produces a (cached) `new Expressions` instance
  12. // parsed.raw is no longer present, use .toString()
  13. // parsed.expression is now useless, just use the indices
  14. // parsed.reverse() has been removed for now, due to its apparent uselessness
  15. // Other changes in the Expressions object:
  16. // - classNames are now unique, and save both escaped and unescaped values
  17. // - attributes now save both escaped and unescaped values
  18. // - pseudos now save both escaped and unescaped values
  19. var escapeRe = /([-.*+?^${}()|[\]\/\\])/g,
  20. unescapeRe = /\\/g
  21. var escape = function(string){
  22. // XRegExp v2.0.0-beta-3
  23. // « https://github.com/slevithan/XRegExp/blob/master/src/xregexp.js
  24. return (string + "").replace(escapeRe, '\\$1')
  25. }
  26. var unescape = function(string){
  27. return (string + "").replace(unescapeRe, '')
  28. }
  29. var slickRe = RegExp(
  30. /*
  31. #!/usr/bin/env ruby
  32. puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'')
  33. __END__
  34. "(?x)^(?:\
  35. \\s* ( , ) \\s* # Separator \n\
  36. | \\s* ( <combinator>+ ) \\s* # Combinator \n\
  37. | ( \\s+ ) # CombinatorChildren \n\
  38. | ( <unicode>+ | \\* ) # Tag \n\
  39. | \\# ( <unicode>+ ) # ID \n\
  40. | \\. ( <unicode>+ ) # ClassName \n\
  41. | # Attribute \n\
  42. \\[ \
  43. \\s* (<unicode1>+) (?: \
  44. \\s* ([*^$!~|]?=) (?: \
  45. \\s* (?:\
  46. ([\"']?)(.*?)\\9 \
  47. )\
  48. ) \
  49. )? \\s* \
  50. \\](?!\\]) \n\
  51. | :+ ( <unicode>+ )(?:\
  52. \\( (?:\
  53. (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\
  54. ) \\)\
  55. )?\
  56. )"
  57. */
  58. "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)"
  59. .replace(/<combinator>/, '[' + escape(">+~`!@$%^&={}\\;</") + ']')
  60. .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
  61. .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
  62. )
  63. // Part
  64. var Part = function Part(combinator){
  65. this.combinator = combinator || " "
  66. this.tag = "*"
  67. }
  68. Part.prototype.toString = function(){
  69. if (!this.raw){
  70. var xpr = "", k, part
  71. xpr += this.tag || "*"
  72. if (this.id) xpr += "#" + this.id
  73. if (this.classes) xpr += "." + this.classList.join(".")
  74. if (this.attributes) for (k = 0; part = this.attributes[k++];){
  75. xpr += "[" + part.name + (part.operator ? part.operator + '"' + part.value + '"' : '') + "]"
  76. }
  77. if (this.pseudos) for (k = 0; part = this.pseudos[k++];){
  78. xpr += ":" + part.name
  79. if (part.value) xpr += "(" + part.value + ")"
  80. }
  81. this.raw = xpr
  82. }
  83. return this.raw
  84. }
  85. // Expression
  86. var Expression = function Expression(){
  87. this.length = 0
  88. }
  89. Expression.prototype.toString = function(){
  90. if (!this.raw){
  91. var xpr = ""
  92. for (var j = 0, bit; bit = this[j++];){
  93. if (j !== 1) xpr += " "
  94. if (bit.combinator !== " ") xpr += bit.combinator + " "
  95. xpr += bit
  96. }
  97. this.raw = xpr
  98. }
  99. return this.raw
  100. }
  101. var replacer = function(
  102. rawMatch,
  103. separator,
  104. combinator,
  105. combinatorChildren,
  106. tagName,
  107. id,
  108. className,
  109. attributeKey,
  110. attributeOperator,
  111. attributeQuote,
  112. attributeValue,
  113. pseudoMarker,
  114. pseudoClass,
  115. pseudoQuote,
  116. pseudoClassQuotedValue,
  117. pseudoClassValue
  118. ){
  119. var expression, current
  120. if (separator || !this.length){
  121. expression = this[this.length++] = new Expression
  122. if (separator) return ''
  123. }
  124. if (!expression) expression = this[this.length - 1]
  125. if (combinator || combinatorChildren || !expression.length){
  126. current = expression[expression.length++] = new Part(combinator)
  127. }
  128. if (!current) current = expression[expression.length - 1]
  129. if (tagName){
  130. current.tag = unescape(tagName)
  131. } else if (id){
  132. current.id = unescape(id)
  133. } else if (className){
  134. var unescaped = unescape(className)
  135. var classes = current.classes || (current.classes = {})
  136. if (!classes[unescaped]){
  137. classes[unescaped] = escape(className)
  138. var classList = current.classList || (current.classList = [])
  139. classList.push(unescaped)
  140. classList.sort()
  141. }
  142. } else if (pseudoClass){
  143. pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue
  144. ;(current.pseudos || (current.pseudos = [])).push({
  145. type : pseudoMarker.length == 1 ? 'class' : 'element',
  146. name : unescape(pseudoClass),
  147. escapedName : escape(pseudoClass),
  148. value : pseudoClassValue ? unescape(pseudoClassValue) : null,
  149. escapedValue : pseudoClassValue ? escape(pseudoClassValue) : null
  150. })
  151. } else if (attributeKey){
  152. attributeValue = attributeValue ? escape(attributeValue) : null
  153. ;(current.attributes || (current.attributes = [])).push({
  154. operator : attributeOperator,
  155. name : unescape(attributeKey),
  156. escapedName : escape(attributeKey),
  157. value : attributeValue ? unescape(attributeValue) : null,
  158. escapedValue : attributeValue ? escape(attributeValue) : null
  159. })
  160. }
  161. return ''
  162. }
  163. // Expressions
  164. var Expressions = function Expressions(expression){
  165. this.length = 0
  166. var self = this
  167. var original = expression, replaced
  168. while (expression){
  169. replaced = expression.replace(slickRe, function(){
  170. return replacer.apply(self, arguments)
  171. })
  172. if (replaced === expression) throw new Error(original + ' is an invalid expression')
  173. expression = replaced
  174. }
  175. }
  176. Expressions.prototype.toString = function(){
  177. if (!this.raw){
  178. var expressions = []
  179. for (var i = 0, expression; expression = this[i++];) expressions.push(expression)
  180. this.raw = expressions.join(", ")
  181. }
  182. return this.raw
  183. }
  184. var cache = {}
  185. var parse = function(expression){
  186. if (expression == null) return null
  187. expression = ('' + expression).replace(/^\s+|\s+$/g, '')
  188. return cache[expression] || (cache[expression] = new Expressions(expression))
  189. }
  190. module.exports = parse