UNPKG

@mpxjs/core

Version:

mpx runtime core

446 lines (361 loc) 9.96 kB
import tokenizer from './tokenizer' export default function language (lookups, matchComparison) { return function (selector, moduleId) { return parse( selector, remap(lookups), moduleId, matchComparison || caseSensitiveComparison ) } } function remap (opts) { // 对于字符串类型的 value 转化为函数 for (const key in opts) { if (opt_okay(opts, key)) { /* eslint-disable-next-line */ opts[key] = Function( 'return function(node, attr) { return node.' + opts[key] + ' }' ) opts[key] = opts[key]() } } return opts } function opt_okay (opts, key) { return Object.prototype.hasOwnProperty.call(opts, key) && typeof opts[key] === 'string' } function parse (selector, options, moduleId, matchComparison) { const stream = tokenizer() // const default_subj = true const selectors = [[]] let bits = selectors[0] // 逆向关系 const traversal = { '': any_parents, '>': direct_parent, '+': direct_sibling, '~': any_sibling } stream.on('data', group) stream.end(selector) function group (token) { 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() const 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) { if (node.data?.moduleId !== moduleId) { return } let current, length, subj const orig = node const set = [] for (let i = 0, len = selectors.length; i < len; ++i) { bits = selectors[i] current = entry // 当前 bits 检测规则 length = bits.length node = orig // 引用赋值 subj = [] let j = 0 // 步长为2,因为2个节点之间的关系中间会有一个 OP 操作符 for (j = 0; j < length; j += 2) { node = current(node, bits[j], subj) if (!node) { break } // todo 这里的规则和步长设计的很巧妙 current = bits[j + 1] // 改变当前的 bits 检测规则 } 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) { let 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 (let 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) { let 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) { const parent = options.parent(node) let idx = 0 const children = options.children(parent) for (let 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) { const parent = options.parent(node) const children = options.children(parent) for (let 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) { const fn = parse(selector, options, matchComparison) return not_function function not_function (node) { return !fn(node, true) } } function valid_any_match (options, selector, matchComparison) { const fn = parse(selector, options, matchComparison) return fn } function valid_attr (fn, lhs, cmp, rhs) { return function (node) { const attr = fn(node, lhs) if (!cmp) { return !!attr } if (cmp.length === 1) { return attr === rhs } if (attr === undefined || 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) { const 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) { let test = function () { return false } if (nth === 'odd') { nth = '2n+1' } else if (nth === 'even') { nth = '2n' } const regexp = /( ?([-|+])?(\d*)n)? ?((\+|-)? ?(\d+))? ?/ const matches = nth.match(regexp) if (matches) { let growth = 0 if (matches[1]) { const positiveGrowth = matches[2] !== '-' growth = parseInt(matches[3] === '' ? 1 : matches[3]) growth = growth * (positiveGrowth ? 1 : -1) } let offset = 0 if (matches[4]) { offset = parseInt(matches[6]) const 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) { const validPositions = [] const len = children.length for (let position = 1; position <= len; position++) { const divisible = (position - offset) % growth === 0 if (divisible) { if (growth > 0) { validPositions.push(position) } else { if ((position - offset) / growth >= 0) { validPositions.push(position) } } } } for (let i = 0; i < validPositions.length; i++) { if (children[validPositions[i] - 1] === node) { return true } } return false } } } return function (node) { const children = options.children(options.parent(node)) return test(children, node) } } const 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 }