UNPKG

3.18 kBJavaScriptView Raw
1'use strict'
2
3/**
4 * # API
5 *
6 * @author Ivan Voischev (@voischev),
7 * Anton Winogradov (@awinogradov),
8 * Alexej Yaroshevich (@zxqfox),
9 * Vasiliy (@Yeti-or)
10 *
11 * @namespace tree
12 */
13function Api () {
14 this.walk = walk
15 this.match = match
16}
17
18/**
19 * Walks the tree and passes all nodes via a callback
20 *
21 * @memberof tree
22 *
23 * @param {Function} cb Callback
24 * @return {Function} Callback(node)
25 *
26 *@example
27 * ```js
28 * export const walk = (tree) => {
29 * tree.walk((node) => {
30 * let classes = node.attrs && node.attrs.class.split(' ') || []
31 *
32 * if (classes.includes(className)) return cb(node)
33 * return node
34 * })
35 * }
36 * ```
37 */
38function walk (cb) {
39 return traverse(this, cb)
40}
41
42/**
43 * Matches an expression to search for nodes in the tree
44 *
45 * @memberof tree
46 *
47 * @param {String|RegExp|Object|Array} expression - Matcher(s) to search
48 * @param {Function} cb Callback
49 *
50 * @return {Function} Callback(node)
51 *
52 * @example
53 * ```js
54 * export const match = (tree) => {
55 * // Single matcher
56 * tree.match({ tag: 'custom-tag' }, (node) => {
57 * let tag = node.tag
58 *
59 * Object.assign(node, { tag: 'div', attrs: {class: tag} })
60 *
61 * return node
62 * })
63 * // Multiple matchers
64 * tree.match([{ tag: 'b' }, { tag: 'strong' }], (node) => {
65 * let style = 'font-weight: bold;'
66 *
67 * node.tag = 'span'
68 *
69 * node.attrs
70 * ? ( node.attrs.style
71 * ? ( node.attrs.style += style )
72 * : node.attrs.style = style
73 * )
74 * : node.attrs = { style: style }
75 *
76 * return node
77 * })
78 * }
79 * ```
80 */
81function match (expression, cb) {
82 return Array.isArray(expression)
83 ? traverse(this, node => {
84 for (let i = 0; i < expression.length; i++) {
85 if (compare(expression[i], node)) return cb(node)
86 }
87
88 return node
89 })
90 : traverse(this, node => {
91 if (compare(expression, node)) return cb(node)
92
93 return node
94 })
95}
96
97module.exports = Api
98module.exports.match = match
99module.exports.walk = walk
100
101/** @private */
102function traverse (tree, cb) {
103 if (Array.isArray(tree)) {
104 for (let i = 0; i < tree.length; i++) {
105 tree[i] = traverse(cb(tree[i]), cb)
106 }
107 } else if (
108 tree &&
109 typeof tree === 'object' &&
110 Object.prototype.hasOwnProperty.call(tree, 'content')
111 ) traverse(tree.content, cb)
112
113 return tree
114}
115
116/** @private */
117function compare (expected, actual) {
118 if (expected instanceof RegExp) {
119 if (typeof actual === 'object') return false
120 if (typeof actual === 'string') return expected.test(actual)
121 }
122
123 if (typeof expected !== typeof actual) return false
124 if (typeof expected !== 'object' || expected === null) {
125 return expected === actual
126 }
127
128 if (Array.isArray(expected)) {
129 return expected.every(exp => [].some.call(actual, act => compare(exp, act)))
130 }
131
132 return Object.keys(expected).every(key => {
133 const ao = actual[key]
134 const eo = expected[key]
135
136 if (typeof eo === 'object' && eo !== null && ao !== null) {
137 return compare(eo, ao)
138 }
139 if (typeof eo === 'boolean') {
140 return eo !== (ao == null)
141 }
142
143 return ao === eo
144 })
145}