1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | var rxTest = /^[ \t]*\[[ \t]*([a-z_][a-z_0-9]*)[ \t]*(=[^\]]+)?[ \t]*\]/i;
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | var TreeWalker = function( actions ) {
|
50 | this._actions = compile.call( this, actions );
|
51 | };
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | TreeWalker.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 |
|
67 | function 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 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 | function 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 |
|
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 |
|
115 | itm = itm.substr( m[0].length );
|
116 | }
|
117 | return buildTst( tst );
|
118 | });
|
119 | }
|
120 |
|
121 | function 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 |
|
133 | function 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 |
|
145 | function 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 |
|
175 | function 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 |
|
199 | function buildAny() {
|
200 | return actionAny;
|
201 | }
|
202 |
|
203 | function 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 |
|
230 | function buildAll() {
|
231 | return actionAll;
|
232 | }
|
233 |
|
234 | function 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 |
|
245 | TreeWalker.create = function( actions ) {
|
246 | return new TreeWalker( actions );
|
247 | };
|
248 | module.exports = TreeWalker;
|