index.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. module.exports = language
  2. var tokenizer = require('./tokenizer')
  3. function language(lookups, matchComparison) {
  4. return function(selector) {
  5. return parse(selector, remap(lookups),
  6. matchComparison || caseSensitiveComparison)
  7. }
  8. }
  9. function remap(opts) {
  10. for(var key in opts) if(opt_okay(opts, key)) {
  11. opts[key] = Function(
  12. 'return function(node, attr) { return node.' + opts[key] + ' }'
  13. )
  14. opts[key] = opts[key]()
  15. }
  16. return opts
  17. }
  18. function opt_okay(opts, key) {
  19. return opts.hasOwnProperty(key) && typeof opts[key] === 'string'
  20. }
  21. function parse(selector, options, matchComparison) {
  22. var stream = tokenizer()
  23. , default_subj = true
  24. , selectors = [[]]
  25. , traversal
  26. , bits
  27. bits = selectors[0]
  28. traversal = {
  29. '': any_parents
  30. , '>': direct_parent
  31. , '+': direct_sibling
  32. , '~': any_sibling
  33. }
  34. stream
  35. .on('data', group)
  36. .end(selector)
  37. function group(token) {
  38. var crnt
  39. if(token.type === 'comma') {
  40. selectors.unshift(bits = [])
  41. return
  42. }
  43. if(token.type === 'op' || token.type === 'any-child') {
  44. bits.unshift(traversal[token.data])
  45. bits.unshift(check())
  46. return
  47. }
  48. bits[0] = bits[0] || check()
  49. crnt = bits[0]
  50. if(token.type === '!') {
  51. crnt.subject =
  52. selectors[0].subject = true
  53. return
  54. }
  55. crnt.push(
  56. token.type === 'class' ? listContains(token.type, token.data) :
  57. token.type === 'attr' ? attr(token) :
  58. token.type === ':' || token.type === '::' ? pseudo(token) :
  59. token.type === '*' ? Boolean :
  60. matches(token.type, token.data, matchComparison)
  61. )
  62. }
  63. return selector_fn
  64. function selector_fn(node, as_boolean) {
  65. var current
  66. , length
  67. , orig
  68. , subj
  69. , set
  70. orig = node
  71. set = []
  72. for(var i = 0, len = selectors.length; i < len; ++i) {
  73. bits = selectors[i]
  74. current = entry
  75. length = bits.length
  76. node = orig
  77. subj = []
  78. for(var j = 0; j < length; j += 2) {
  79. node = current(node, bits[j], subj)
  80. if(!node) {
  81. break
  82. }
  83. current = bits[j + 1]
  84. }
  85. if(j >= length) {
  86. if(as_boolean) {
  87. return true
  88. }
  89. add(!bits.subject ? [orig] : subj)
  90. }
  91. }
  92. if(as_boolean) {
  93. return false
  94. }
  95. return !set.length ? false :
  96. set.length === 1 ? set[0] :
  97. set
  98. function add(items) {
  99. var next
  100. while(items.length) {
  101. next = items.shift()
  102. if(set.indexOf(next) === -1) {
  103. set.push(next)
  104. }
  105. }
  106. }
  107. }
  108. function check() {
  109. _check.bits = []
  110. _check.subject = false
  111. _check.push = function(token) {
  112. _check.bits.push(token)
  113. }
  114. return _check
  115. function _check(node, subj) {
  116. for(var i = 0, len = _check.bits.length; i < len; ++i) {
  117. if(!_check.bits[i](node)) {
  118. return false
  119. }
  120. }
  121. if(_check.subject) {
  122. subj.push(node)
  123. }
  124. return true
  125. }
  126. }
  127. function listContains(type, data) {
  128. return function(node) {
  129. var val = options[type](node)
  130. val =
  131. Array.isArray(val) ? val :
  132. val ? val.toString().split(/\s+/) :
  133. []
  134. return val.indexOf(data) >= 0
  135. }
  136. }
  137. function attr(token) {
  138. return token.data.lhs ?
  139. valid_attr(
  140. options.attr
  141. , token.data.lhs
  142. , token.data.cmp
  143. , token.data.rhs
  144. ) :
  145. valid_attr(options.attr, token.data)
  146. }
  147. function matches(type, data, matchComparison) {
  148. return function(node) {
  149. return matchComparison(type, options[type](node), data);
  150. }
  151. }
  152. function any_parents(node, next, subj) {
  153. do {
  154. node = options.parent(node)
  155. } while(node && !next(node, subj))
  156. return node
  157. }
  158. function direct_parent(node, next, subj) {
  159. node = options.parent(node)
  160. return node && next(node, subj) ? node : null
  161. }
  162. function direct_sibling(node, next, subj) {
  163. var parent = options.parent(node)
  164. , idx = 0
  165. , children
  166. children = options.children(parent)
  167. for(var i = 0, len = children.length; i < len; ++i) {
  168. if(children[i] === node) {
  169. idx = i
  170. break
  171. }
  172. }
  173. return children[idx - 1] && next(children[idx - 1], subj) ?
  174. children[idx - 1] :
  175. null
  176. }
  177. function any_sibling(node, next, subj) {
  178. var parent = options.parent(node)
  179. , children
  180. children = options.children(parent)
  181. for(var i = 0, len = children.length; i < len; ++i) {
  182. if(children[i] === node) {
  183. return null
  184. }
  185. if(next(children[i], subj)) {
  186. return children[i]
  187. }
  188. }
  189. return null
  190. }
  191. function pseudo(token) {
  192. return valid_pseudo(options, token.data, matchComparison)
  193. }
  194. }
  195. function entry(node, next, subj) {
  196. return next(node, subj) ? node : null
  197. }
  198. function valid_pseudo(options, match, matchComparison) {
  199. switch(match) {
  200. case 'empty': return valid_empty(options)
  201. case 'first-child': return valid_first_child(options)
  202. case 'last-child': return valid_last_child(options)
  203. case 'root': return valid_root(options)
  204. }
  205. if(match.indexOf('contains') === 0) {
  206. return valid_contains(options, match.slice(9, -1))
  207. }
  208. if(match.indexOf('any') === 0) {
  209. return valid_any_match(options, match.slice(4, -1), matchComparison)
  210. }
  211. if(match.indexOf('not') === 0) {
  212. return valid_not_match(options, match.slice(4, -1), matchComparison)
  213. }
  214. if(match.indexOf('nth-child') === 0) {
  215. return valid_nth_child(options, match.slice(10, -1))
  216. }
  217. return function() {
  218. return false
  219. }
  220. }
  221. function valid_not_match(options, selector, matchComparison) {
  222. var fn = parse(selector, options, matchComparison)
  223. return not_function
  224. function not_function(node) {
  225. return !fn(node, true)
  226. }
  227. }
  228. function valid_any_match(options, selector, matchComparison) {
  229. var fn = parse(selector, options, matchComparison)
  230. return fn
  231. }
  232. function valid_attr(fn, lhs, cmp, rhs) {
  233. return function(node) {
  234. var attr = fn(node, lhs)
  235. if(!cmp) {
  236. return !!attr
  237. }
  238. if(cmp.length === 1) {
  239. return attr == rhs
  240. }
  241. if(attr === void 0 || attr === null) {
  242. return false
  243. }
  244. return checkattr[cmp.charAt(0)](attr, rhs)
  245. }
  246. }
  247. function valid_first_child(options) {
  248. return function(node) {
  249. return options.children(options.parent(node))[0] === node
  250. }
  251. }
  252. function valid_last_child(options) {
  253. return function(node) {
  254. var children = options.children(options.parent(node))
  255. return children[children.length - 1] === node
  256. }
  257. }
  258. function valid_empty(options) {
  259. return function(node) {
  260. return options.children(node).length === 0
  261. }
  262. }
  263. function valid_root(options) {
  264. return function(node) {
  265. return !options.parent(node)
  266. }
  267. }
  268. function valid_contains(options, contents) {
  269. return function(node) {
  270. return options.contents(node).indexOf(contents) !== -1
  271. }
  272. }
  273. function valid_nth_child(options, nth) {
  274. var test = function(){ return false }
  275. if (nth == 'odd') {
  276. nth = '2n+1'
  277. } else if (nth == 'even') {
  278. nth = '2n'
  279. }
  280. var regexp = /( ?([-|\+])?(\d*)n)? ?((\+|-)? ?(\d+))? ?/
  281. var matches = nth.match(regexp)
  282. if (matches) {
  283. var growth = 0;
  284. if (matches[1]) {
  285. var positiveGrowth = (matches[2] != '-')
  286. growth = parseInt(matches[3] == '' ? 1 : matches[3])
  287. growth = growth * (positiveGrowth ? 1 : -1)
  288. }
  289. var offset = 0
  290. if (matches[4]) {
  291. offset = parseInt(matches[6])
  292. var positiveOffset = (matches[5] != '-')
  293. offset = offset * (positiveOffset ? 1 : -1)
  294. }
  295. if (growth == 0) {
  296. if (offset != 0) {
  297. test = function(children, node) {
  298. return children[offset - 1] === node
  299. }
  300. }
  301. } else {
  302. test = function(children, node) {
  303. var validPositions = []
  304. var len = children.length
  305. for (var position=1; position <= len; position++) {
  306. var divisible = ((position - offset) % growth) == 0;
  307. if (divisible) {
  308. if (growth > 0) {
  309. validPositions.push(position);
  310. } else {
  311. if ((position - offset) / growth >= 0) {
  312. validPositions.push(position);
  313. }
  314. }
  315. }
  316. }
  317. for(var i=0; i < validPositions.length; i++) {
  318. if (children[validPositions[i] - 1] === node) {
  319. return true
  320. }
  321. }
  322. return false
  323. }
  324. }
  325. }
  326. return function(node) {
  327. var children = options.children(options.parent(node))
  328. return test(children, node)
  329. }
  330. }
  331. var checkattr = {
  332. '$': check_end
  333. , '^': check_beg
  334. , '*': check_any
  335. , '~': check_spc
  336. , '|': check_dsh
  337. }
  338. function check_end(l, r) {
  339. return l.slice(l.length - r.length) === r
  340. }
  341. function check_beg(l, r) {
  342. return l.slice(0, r.length) === r
  343. }
  344. function check_any(l, r) {
  345. return l.indexOf(r) > -1
  346. }
  347. function check_spc(l, r) {
  348. return l.split(/\s+/).indexOf(r) > -1
  349. }
  350. function check_dsh(l, r) {
  351. return l.split('-').indexOf(r) > -1
  352. }
  353. function caseSensitiveComparison(type, pattern, data) {
  354. return pattern === data;
  355. }