UNPKG

6.41 kBJavaScriptView Raw
1/**
2 * @module tree-walker
3 *
4 * @description
5 * Add hooks on an object's tree.
6 *
7 * @example
8 * var TW = require('tree-walker');
9 * var data = {
10 * students: [
11 * { gender: "M", name: "John" },
12 * { gender: "F", name: "Arya" },
13 * { gender: "F", name: "Shae" },
14 * { gender: "M", name: "Tyron" },
15 * { gender: "M", name: "Jammy" }
16 * ]
17 * };
18 *
19 * var a = new TW({
20 * "students/2": display
21 * });
22 * a.walk( data );
23 *
24 * var b = new TW({
25 * "students/[gender=M]": display
26 * });
27 * b.walk( data );
28 */
29
30
31
32// Matches this kind of strings:
33// * `[Var]`
34// * ` [Toto]`
35// * ` [Toto] `
36// * ` [Toto ] `
37// * ` [ Toto] `
38// * `[Var=bob]`
39// * `[titi= Fla ga da Johnes ]`
40var rxTest = /^[ \t]*\[[ \t]*([a-z_][a-z_0-9]*)[ \t]*(=[^\]]+)?[ \t]*\]/i;
41
42
43/**
44 * @example
45 * var TreeWalker = require("tree-walker");
46 * var instance = new TreeWalker(opts);
47 * @class TreeWalker
48 */
49var TreeWalker = function( actions ) {
50 this._actions = compile.call( this, actions );
51};
52
53/**
54 * @return void
55 */
56TreeWalker.prototype.walk = function( data ) {
57 this._actions.forEach(function ( item ) {
58 var action = item.action;
59 var path = item.path;
60 var tester = path[0];
61 tester( data, action, path, 1 );
62 });
63
64};
65
66
67function compile( actions ) {
68 var out = [];
69 var path, action;
70 var compiledActions;
71 var pathItems;
72 for( path in actions ) {
73 compiledActions = [];
74 action = actions[path];
75 pathItems = explodePath( path );
76 out.push({ path: pathItems, action: action });
77 }
78
79 return out;
80}
81
82/**
83 * Transform `"a/*"` into `[["att", "a"], ["any"]]`.
84 * Transform `"bob/** /[youp]"` into `[["att", "bob"], ["all"], ["tst", {youp: undefined}]]`.
85 * Transform `"a/b"` into `[["att", "a"], ["att", "b"]]`.
86 * Transform `"a/4"` into `[[att: "a"], [idx: 4]]`.
87 * Transform `"[x=toto]"` into `[["tst", {x: 'toto'}]]`.
88 * Transform `"[x=toto][y=foo]"` into `[["tst", {x: 'toto', y: 'foo'}]]`.
89 */
90function explodePath( path ) {
91 var items = path.split( '/' );
92 return items.map(function( itm ) {
93 itm = itm.trim();
94 if( itm == '*' ) return buildAny();
95 if( itm == '**' ) return buildAll();
96 if( itm.charAt(0) != '[' ) {
97 // Not a test.
98 var idx = parseInt( itm );
99 if( isNaN( idx ) ) {
100 return buildAtt( itm );
101 }
102 return buildIdx( idx );
103 }
104 var tst = {};
105 while( itm.length > 0 ) {
106 var m = itm.match(rxTest);
107 if (!m) break;
108 var key = m[1];
109 var val = m[2];
110 if (val) {
111 val = val.substr(1);
112 }
113 tst[key] = val;
114 // Go to next test, if any.
115 itm = itm.substr( m[0].length );
116 }
117 return buildTst( tst );
118 });
119}
120
121function buildAtt( att ) {
122 return function( node, action, path, idx ) {
123 node = node[att];
124 if( typeof node === 'undefined' ) return false;
125 if( idx < path.length ) {
126 return path[idx]( node, action, path, idx + 1 );
127 }
128 action( node );
129 return false;
130 };
131}
132
133function buildIdx( index ) {
134 return function( node, action, path, idx ) {
135 node = node[index];
136 if( typeof node === 'undefined' ) return false;
137 if( idx < path.length ) {
138 return path[idx]( node, action, path, idx + 1 );
139 }
140 action( node );
141 return false;
142 };
143}
144
145function buildTst( tst ) {
146 return function( node, action, path, idx ) {
147 var arr = node;
148 if( !Array.isArray( arr ) ) arr = [arr];
149 arr.forEach(function ( child ) {
150 var success = true;
151 var key, val;
152 for( key in tst ) {
153 val = tst[key];
154 if( typeof child[key] === 'undefined' ) {
155 success = false;
156 break;
157 }
158 if( typeof val !== 'undefined' ) {
159 if( child[key] != val ) {
160 success = false;
161 break;
162 }
163 }
164 }
165 if( success ) {
166 if( idx < path.length ) {
167 return path[idx]( child, action, path, idx + 1 );
168 }
169 action( child );
170 }
171 });
172 };
173}
174
175function actionAny( node, action, path, idx ) {
176 if( typeof node === 'undefined' ) return;
177
178 if( Array.isArray( node ) ) {
179 node.forEach(function ( child ) {
180 if( idx < path.length ) {
181 path[idx]( child, action, path, idx + 1 );
182 } else {
183 action( child );
184 }
185 });
186 } else {
187 var key, child;
188 for( key in node ) {
189 child = node[key];
190 if( idx < path.length ) {
191 path[idx]( child, action, path, idx + 1 );
192 } else {
193 action( child );
194 }
195 }
196 }
197}
198
199function buildAny() {
200 return actionAny;
201}
202
203function actionAll( node, action, path, idx ) {
204 if( typeof node === 'undefined' ) return;
205
206 var test = path[idx];
207 var noMoreTests = (typeof test !== 'function');
208
209 if( isLeaf( node ) ) {
210 if( noMoreTests ) action( node );
211 return;
212 }
213
214 if( Array.isArray( node ) ) {
215 node.forEach(function ( child ) {
216 if( !noMoreTests ) test( child, action, path, idx + 1 );
217 actionAll( child, action, path, idx );
218 });
219
220 } else {
221 var key, child;
222 for( key in node ) {
223 child = node[key];
224 if( !noMoreTests ) test( child, action, path, idx + 1 );
225 actionAll( child, action, path, idx );
226 }
227 }
228}
229
230function buildAll() {
231 return actionAll;
232}
233
234function isLeaf( node ) {
235 switch( typeof node ) {
236 case 'function':
237 case 'string':
238 case 'number':
239 case 'boolean':
240 return true;
241 }
242 return false;
243}
244
245TreeWalker.create = function( actions ) {
246 return new TreeWalker( actions );
247};
248module.exports = TreeWalker;