146 lines
3.1 KiB
JavaScript
146 lines
3.1 KiB
JavaScript
|
'use strict'
|
||
|
|
||
|
/**
|
||
|
* # API
|
||
|
*
|
||
|
* @author Ivan Voischev (@voischev),
|
||
|
* Anton Winogradov (@awinogradov),
|
||
|
* Alexej Yaroshevich (@zxqfox),
|
||
|
* Vasiliy (@Yeti-or)
|
||
|
*
|
||
|
* @namespace tree
|
||
|
*/
|
||
|
function Api () {
|
||
|
this.walk = walk
|
||
|
this.match = match
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Walks the tree and passes all nodes via a callback
|
||
|
*
|
||
|
* @memberof tree
|
||
|
*
|
||
|
* @param {Function} cb Callback
|
||
|
* @return {Function} Callback(node)
|
||
|
*
|
||
|
*@example
|
||
|
* ```js
|
||
|
* export const walk = (tree) => {
|
||
|
* tree.walk((node) => {
|
||
|
* let classes = node.attrs && node.attrs.class.split(' ') || []
|
||
|
*
|
||
|
* if (classes.includes(className)) return cb(node)
|
||
|
* return node
|
||
|
* })
|
||
|
* }
|
||
|
* ```
|
||
|
*/
|
||
|
function walk (cb) {
|
||
|
return traverse(this, cb)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Matches an expression to search for nodes in the tree
|
||
|
*
|
||
|
* @memberof tree
|
||
|
*
|
||
|
* @param {String|RegExp|Object|Array} expression - Matcher(s) to search
|
||
|
* @param {Function} cb Callback
|
||
|
*
|
||
|
* @return {Function} Callback(node)
|
||
|
*
|
||
|
* @example
|
||
|
* ```js
|
||
|
* export const match = (tree) => {
|
||
|
* // Single matcher
|
||
|
* tree.match({ tag: 'custom-tag' }, (node) => {
|
||
|
* let tag = node.tag
|
||
|
*
|
||
|
* Object.assign(node, { tag: 'div', attrs: {class: tag} })
|
||
|
*
|
||
|
* return node
|
||
|
* })
|
||
|
* // Multiple matchers
|
||
|
* tree.match([{ tag: 'b' }, { tag: 'strong' }], (node) => {
|
||
|
* let style = 'font-weight: bold;'
|
||
|
*
|
||
|
* node.tag = 'span'
|
||
|
*
|
||
|
* node.attrs
|
||
|
* ? ( node.attrs.style
|
||
|
* ? ( node.attrs.style += style )
|
||
|
* : node.attrs.style = style
|
||
|
* )
|
||
|
* : node.attrs = { style: style }
|
||
|
*
|
||
|
* return node
|
||
|
* })
|
||
|
* }
|
||
|
* ```
|
||
|
*/
|
||
|
function match (expression, cb) {
|
||
|
return Array.isArray(expression)
|
||
|
? traverse(this, node => {
|
||
|
for (let i = 0; i < expression.length; i++) {
|
||
|
if (compare(expression[i], node)) return cb(node)
|
||
|
}
|
||
|
|
||
|
return node
|
||
|
})
|
||
|
: traverse(this, node => {
|
||
|
if (compare(expression, node)) return cb(node)
|
||
|
|
||
|
return node
|
||
|
})
|
||
|
}
|
||
|
|
||
|
module.exports = Api
|
||
|
module.exports.match = match
|
||
|
module.exports.walk = walk
|
||
|
|
||
|
/** @private */
|
||
|
function traverse (tree, cb) {
|
||
|
if (Array.isArray(tree)) {
|
||
|
for (let i = 0; i < tree.length; i++) {
|
||
|
tree[i] = traverse(cb(tree[i]), cb)
|
||
|
}
|
||
|
} else if (
|
||
|
tree &&
|
||
|
typeof tree === 'object' &&
|
||
|
Object.prototype.hasOwnProperty.call(tree, 'content')
|
||
|
) traverse(tree.content, cb)
|
||
|
|
||
|
return tree
|
||
|
}
|
||
|
|
||
|
/** @private */
|
||
|
function compare (expected, actual) {
|
||
|
if (expected instanceof RegExp) {
|
||
|
if (typeof actual === 'object') return false
|
||
|
if (typeof actual === 'string') return expected.test(actual)
|
||
|
}
|
||
|
|
||
|
if (typeof expected !== typeof actual) return false
|
||
|
if (typeof expected !== 'object' || expected === null) {
|
||
|
return expected === actual
|
||
|
}
|
||
|
|
||
|
if (Array.isArray(expected)) {
|
||
|
return expected.every(exp => [].some.call(actual, act => compare(exp, act)))
|
||
|
}
|
||
|
|
||
|
return Object.keys(expected).every(key => {
|
||
|
const ao = actual[key]
|
||
|
const eo = expected[key]
|
||
|
|
||
|
if (typeof eo === 'object' && eo !== null && ao !== null) {
|
||
|
return compare(eo, ao)
|
||
|
}
|
||
|
if (typeof eo === 'boolean') {
|
||
|
return eo !== (ao == null)
|
||
|
}
|
||
|
|
||
|
return ao === eo
|
||
|
})
|
||
|
}
|