| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- module.exports = language
- var tokenizer = require('./tokenizer')
- function language(lookups, matchComparison) {
- return function(selector) {
- return parse(selector, remap(lookups),
- matchComparison || caseSensitiveComparison)
- }
- }
- function remap(opts) {
- for(var key in opts) if(opt_okay(opts, key)) {
- opts[key] = Function(
- 'return function(node, attr) { return node.' + opts[key] + ' }'
- )
- opts[key] = opts[key]()
- }
- return opts
- }
- function opt_okay(opts, key) {
- return opts.hasOwnProperty(key) && typeof opts[key] === 'string'
- }
- function parse(selector, options, matchComparison) {
- var stream = tokenizer()
- , default_subj = true
- , selectors = [[]]
- , traversal
- , bits
- bits = selectors[0]
- traversal = {
- '': any_parents
- , '>': direct_parent
- , '+': direct_sibling
- , '~': any_sibling
- }
- stream
- .on('data', group)
- .end(selector)
- function group(token) {
- var crnt
- if(token.type === 'comma') {
- selectors.unshift(bits = [])
- return
- }
- if(token.type === 'op' || token.type === 'any-child') {
- bits.unshift(traversal[token.data])
- bits.unshift(check())
- return
- }
- bits[0] = bits[0] || check()
- crnt = bits[0]
- if(token.type === '!') {
- crnt.subject =
- selectors[0].subject = true
- return
- }
- crnt.push(
- token.type === 'class' ? listContains(token.type, token.data) :
- token.type === 'attr' ? attr(token) :
- token.type === ':' || token.type === '::' ? pseudo(token) :
- token.type === '*' ? Boolean :
- matches(token.type, token.data, matchComparison)
- )
- }
- return selector_fn
- function selector_fn(node, as_boolean) {
- var current
- , length
- , orig
- , subj
- , set
- orig = node
- set = []
- for(var i = 0, len = selectors.length; i < len; ++i) {
- bits = selectors[i]
- current = entry
- length = bits.length
- node = orig
- subj = []
- for(var j = 0; j < length; j += 2) {
- node = current(node, bits[j], subj)
- if(!node) {
- break
- }
- current = bits[j + 1]
- }
- if(j >= length) {
- if(as_boolean) {
- return true
- }
- add(!bits.subject ? [orig] : subj)
- }
- }
- if(as_boolean) {
- return false
- }
- return !set.length ? false :
- set.length === 1 ? set[0] :
- set
- function add(items) {
- var next
- while(items.length) {
- next = items.shift()
- if(set.indexOf(next) === -1) {
- set.push(next)
- }
- }
- }
- }
- function check() {
- _check.bits = []
- _check.subject = false
- _check.push = function(token) {
- _check.bits.push(token)
- }
- return _check
- function _check(node, subj) {
- for(var i = 0, len = _check.bits.length; i < len; ++i) {
- if(!_check.bits[i](node)) {
- return false
- }
- }
- if(_check.subject) {
- subj.push(node)
- }
- return true
- }
- }
- function listContains(type, data) {
- return function(node) {
- var val = options[type](node)
- val =
- Array.isArray(val) ? val :
- val ? val.toString().split(/\s+/) :
- []
- return val.indexOf(data) >= 0
- }
- }
- function attr(token) {
- return token.data.lhs ?
- valid_attr(
- options.attr
- , token.data.lhs
- , token.data.cmp
- , token.data.rhs
- ) :
- valid_attr(options.attr, token.data)
- }
- function matches(type, data, matchComparison) {
- return function(node) {
- return matchComparison(type, options[type](node), data);
- }
- }
- function any_parents(node, next, subj) {
- do {
- node = options.parent(node)
- } while(node && !next(node, subj))
- return node
- }
- function direct_parent(node, next, subj) {
- node = options.parent(node)
- return node && next(node, subj) ? node : null
- }
- function direct_sibling(node, next, subj) {
- var parent = options.parent(node)
- , idx = 0
- , children
- children = options.children(parent)
- for(var i = 0, len = children.length; i < len; ++i) {
- if(children[i] === node) {
- idx = i
- break
- }
- }
- return children[idx - 1] && next(children[idx - 1], subj) ?
- children[idx - 1] :
- null
- }
- function any_sibling(node, next, subj) {
- var parent = options.parent(node)
- , children
- children = options.children(parent)
- for(var i = 0, len = children.length; i < len; ++i) {
- if(children[i] === node) {
- return null
- }
- if(next(children[i], subj)) {
- return children[i]
- }
- }
- return null
- }
- function pseudo(token) {
- return valid_pseudo(options, token.data, matchComparison)
- }
- }
- function entry(node, next, subj) {
- return next(node, subj) ? node : null
- }
- function valid_pseudo(options, match, matchComparison) {
- switch(match) {
- case 'empty': return valid_empty(options)
- case 'first-child': return valid_first_child(options)
- case 'last-child': return valid_last_child(options)
- case 'root': return valid_root(options)
- }
- if(match.indexOf('contains') === 0) {
- return valid_contains(options, match.slice(9, -1))
- }
- if(match.indexOf('any') === 0) {
- return valid_any_match(options, match.slice(4, -1), matchComparison)
- }
- if(match.indexOf('not') === 0) {
- return valid_not_match(options, match.slice(4, -1), matchComparison)
- }
- if(match.indexOf('nth-child') === 0) {
- return valid_nth_child(options, match.slice(10, -1))
- }
- return function() {
- return false
- }
- }
- function valid_not_match(options, selector, matchComparison) {
- var fn = parse(selector, options, matchComparison)
- return not_function
- function not_function(node) {
- return !fn(node, true)
- }
- }
- function valid_any_match(options, selector, matchComparison) {
- var fn = parse(selector, options, matchComparison)
- return fn
- }
- function valid_attr(fn, lhs, cmp, rhs) {
- return function(node) {
- var attr = fn(node, lhs)
- if(!cmp) {
- return !!attr
- }
- if(cmp.length === 1) {
- return attr == rhs
- }
- if(attr === void 0 || attr === null) {
- return false
- }
- return checkattr[cmp.charAt(0)](attr, rhs)
- }
- }
- function valid_first_child(options) {
- return function(node) {
- return options.children(options.parent(node))[0] === node
- }
- }
- function valid_last_child(options) {
- return function(node) {
- var children = options.children(options.parent(node))
- return children[children.length - 1] === node
- }
- }
- function valid_empty(options) {
- return function(node) {
- return options.children(node).length === 0
- }
- }
- function valid_root(options) {
- return function(node) {
- return !options.parent(node)
- }
- }
- function valid_contains(options, contents) {
- return function(node) {
- return options.contents(node).indexOf(contents) !== -1
- }
- }
- function valid_nth_child(options, nth) {
- var test = function(){ return false }
- if (nth == 'odd') {
- nth = '2n+1'
- } else if (nth == 'even') {
- nth = '2n'
- }
- var regexp = /( ?([-|\+])?(\d*)n)? ?((\+|-)? ?(\d+))? ?/
- var matches = nth.match(regexp)
- if (matches) {
- var growth = 0;
- if (matches[1]) {
- var positiveGrowth = (matches[2] != '-')
- growth = parseInt(matches[3] == '' ? 1 : matches[3])
- growth = growth * (positiveGrowth ? 1 : -1)
- }
- var offset = 0
- if (matches[4]) {
- offset = parseInt(matches[6])
- var positiveOffset = (matches[5] != '-')
- offset = offset * (positiveOffset ? 1 : -1)
- }
- if (growth == 0) {
- if (offset != 0) {
- test = function(children, node) {
- return children[offset - 1] === node
- }
- }
- } else {
- test = function(children, node) {
- var validPositions = []
- var len = children.length
- for (var position=1; position <= len; position++) {
- var divisible = ((position - offset) % growth) == 0;
- if (divisible) {
- if (growth > 0) {
- validPositions.push(position);
- } else {
- if ((position - offset) / growth >= 0) {
- validPositions.push(position);
- }
- }
- }
- }
- for(var i=0; i < validPositions.length; i++) {
- if (children[validPositions[i] - 1] === node) {
- return true
- }
- }
- return false
- }
- }
- }
- return function(node) {
- var children = options.children(options.parent(node))
- return test(children, node)
- }
- }
- var checkattr = {
- '$': check_end
- , '^': check_beg
- , '*': check_any
- , '~': check_spc
- , '|': check_dsh
- }
- function check_end(l, r) {
- return l.slice(l.length - r.length) === r
- }
- function check_beg(l, r) {
- return l.slice(0, r.length) === r
- }
- function check_any(l, r) {
- return l.indexOf(r) > -1
- }
- function check_spc(l, r) {
- return l.split(/\s+/).indexOf(r) > -1
- }
- function check_dsh(l, r) {
- return l.split('-').indexOf(r) > -1
- }
- function caseSensitiveComparison(type, pattern, data) {
- return pattern === data;
- }
|