JAL-1759 merge from develop
[jalview.git] / examples / biojson-doc / lib / traverse.js
1 /*
2  Copyright 2010 James Halliday (mail@substack.net)
3
4  This project is free software released under the MIT/X11 license:
5  http://www.opensource.org/licenses/mit-license.php
6
7  Copyright 2010 James Halliday (mail@substack.net)
8
9  Permission is hereby granted, free of charge, to any person obtaining a copy
10  of this software and associated documentation files (the "Software"), to deal
11  in the Software without restriction, including without limitation the rights
12  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13  copies of the Software, and to permit persons to whom the Software is
14  furnished to do so, subject to the following conditions:
15
16  The above copyright notice and this permission notice shall be included in
17  all copies or substantial portions of the Software.
18
19  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25  THE SOFTWARE.
26  */
27
28 var traverse;
29
30 (function(){
31     traverse = function (obj) {
32         return new Traverse(obj);
33     };
34
35     function Traverse (obj) {
36         this.value = obj;
37     }
38
39     Traverse.prototype.get = function (ps) {
40         var node = this.value;
41         for (var i = 0; i < ps.length; i ++) {
42             var key = ps[i];
43             if (!node || !hasOwnProperty.call(node, key)) {
44                 node = undefined;
45                 break;
46             }
47             node = node[key];
48         }
49         return node;
50     };
51
52     Traverse.prototype.has = function (ps) {
53         var node = this.value;
54         for (var i = 0; i < ps.length; i ++) {
55             var key = ps[i];
56             if (!node || !hasOwnProperty.call(node, key)) {
57                 return false;
58             }
59             node = node[key];
60         }
61         return true;
62     };
63
64     Traverse.prototype.set = function (ps, value) {
65         var node = this.value;
66         for (var i = 0; i < ps.length - 1; i ++) {
67             var key = ps[i];
68             if (!hasOwnProperty.call(node, key)) node[key] = {};
69             node = node[key];
70         }
71         node[ps[i]] = value;
72         return value;
73     };
74
75     Traverse.prototype.map = function (cb) {
76         return walk(this.value, cb, true);
77     };
78
79     Traverse.prototype.forEach = function (cb) {
80         this.value = walk(this.value, cb, false);
81         return this.value;
82     };
83
84     Traverse.prototype.reduce = function (cb, init) {
85         var skip = arguments.length === 1;
86         var acc = skip ? this.value : init;
87         this.forEach(function (x) {
88             if (!this.isRoot || !skip) {
89                 acc = cb.call(this, acc, x);
90             }
91         });
92         return acc;
93     };
94
95     Traverse.prototype.paths = function () {
96         var acc = [];
97         this.forEach(function (x) {
98             acc.push(this.path);
99         });
100         return acc;
101     };
102
103     Traverse.prototype.nodes = function () {
104         var acc = [];
105         this.forEach(function (x) {
106             acc.push(this.node);
107         });
108         return acc;
109     };
110
111     Traverse.prototype.clone = function () {
112         var parents = [], nodes = [];
113
114         return (function clone (src) {
115             for (var i = 0; i < parents.length; i++) {
116                 if (parents[i] === src) {
117                     return nodes[i];
118                 }
119             }
120
121             if (typeof src === 'object' && src !== null) {
122                 var dst = copy(src);
123
124                 parents.push(src);
125                 nodes.push(dst);
126
127                 forEach(objectKeys(src), function (key) {
128                     dst[key] = clone(src[key]);
129                 });
130
131                 parents.pop();
132                 nodes.pop();
133                 return dst;
134             }
135             else {
136                 return src;
137             }
138         })(this.value);
139     };
140
141     function walk (root, cb, immutable) {
142         var path = [];
143         var parents = [];
144         var alive = true;
145
146         return (function walker (node_) {
147             var node = immutable ? copy(node_) : node_;
148             var modifiers = {};
149
150             var keepGoing = true;
151
152             var state = {
153                 node : node,
154                 node_ : node_,
155                 path : [].concat(path),
156                 parent : parents[parents.length - 1],
157                 parents : parents,
158                 key : path.slice(-1)[0],
159                 isRoot : path.length === 0,
160                 level : path.length,
161                 circular : null,
162                 update : function (x, stopHere) {
163                     if (!state.isRoot) {
164                         state.parent.node[state.key] = x;
165                     }
166                     state.node = x;
167                     if (stopHere) keepGoing = false;
168                 },
169                 'delete' : function (stopHere) {
170                     delete state.parent.node[state.key];
171                     if (stopHere) keepGoing = false;
172                 },
173                 remove : function (stopHere) {
174                     if (isArray(state.parent.node)) {
175                         state.parent.node.splice(state.key, 1);
176                     }
177                     else {
178                         delete state.parent.node[state.key];
179                     }
180                     if (stopHere) keepGoing = false;
181                 },
182                 keys : null,
183                 before : function (f) { modifiers.before = f },
184                 after : function (f) { modifiers.after = f },
185                 pre : function (f) { modifiers.pre = f },
186                 post : function (f) { modifiers.post = f },
187                 stop : function () { alive = false },
188                 block : function () { keepGoing = false }
189             };
190
191             if (!alive) return state;
192
193             function updateState() {
194                 if (typeof state.node === 'object' && state.node !== null) {
195                     if (!state.keys || state.node_ !== state.node) {
196                         state.keys = objectKeys(state.node)
197                     }
198
199                     state.isLeaf = state.keys.length == 0;
200
201                     for (var i = 0; i < parents.length; i++) {
202                         if (parents[i].node_ === node_) {
203                             state.circular = parents[i];
204                             break;
205                         }
206                     }
207                 }
208                 else {
209                     state.isLeaf = true;
210                     state.keys = null;
211                 }
212
213                 state.notLeaf = !state.isLeaf;
214                 state.notRoot = !state.isRoot;
215             }
216
217             updateState();
218
219             // use return values to update if defined
220             var ret = cb.call(state, state.node);
221             if (ret !== undefined && state.update) state.update(ret);
222
223             if (modifiers.before) modifiers.before.call(state, state.node);
224
225             if (!keepGoing) return state;
226
227             if (typeof state.node == 'object'
228                 && state.node !== null && !state.circular) {
229                 parents.push(state);
230
231                 updateState();
232
233                 forEach(state.keys, function (key, i) {
234                     path.push(key);
235
236                     if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
237
238                     var child = walker(state.node[key]);
239                     if (immutable && hasOwnProperty.call(state.node, key)) {
240                         state.node[key] = child.node;
241                     }
242
243                     child.isLast = i == state.keys.length - 1;
244                     child.isFirst = i == 0;
245
246                     if (modifiers.post) modifiers.post.call(state, child);
247
248                     path.pop();
249                 });
250                 parents.pop();
251             }
252
253             if (modifiers.after) modifiers.after.call(state, state.node);
254
255             return state;
256         })(root).node;
257     }
258
259     function copy (src) {
260         if (typeof src === 'object' && src !== null) {
261             var dst;
262
263             if (isArray(src)) {
264                 dst = [];
265             }
266             else if (isDate(src)) {
267                 dst = new Date(src.getTime ? src.getTime() : src);
268             }
269             else if (isRegExp(src)) {
270                 dst = new RegExp(src);
271             }
272             else if (isError(src)) {
273                 dst = { message: src.message };
274             }
275             else if (isBoolean(src)) {
276                 dst = new Boolean(src);
277             }
278             else if (isNumber(src)) {
279                 dst = new Number(src);
280             }
281             else if (isString(src)) {
282                 dst = new String(src);
283             }
284             else if (Object.create && Object.getPrototypeOf) {
285                 dst = Object.create(Object.getPrototypeOf(src));
286             }
287             else if (src.constructor === Object) {
288                 dst = {};
289             }
290             else {
291                 var proto =
292                         (src.constructor && src.constructor.prototype)
293                             || src.__proto__
294                             || {}
295                     ;
296                 var T = function () {};
297                 T.prototype = proto;
298                 dst = new T;
299             }
300
301             forEach(objectKeys(src), function (key) {
302                 dst[key] = src[key];
303             });
304             return dst;
305         }
306         else return src;
307     }
308
309     var objectKeys = Object.keys || function keys (obj) {
310         var res = [];
311         for (var key in obj) res.push(key)
312         return res;
313     };
314
315     function toS (obj) { return Object.prototype.toString.call(obj) }
316     function isDate (obj) { return toS(obj) === '[object Date]' }
317     function isRegExp (obj) { return toS(obj) === '[object RegExp]' }
318     function isError (obj) { return toS(obj) === '[object Error]' }
319     function isBoolean (obj) { return toS(obj) === '[object Boolean]' }
320     function isNumber (obj) { return toS(obj) === '[object Number]' }
321     function isString (obj) { return toS(obj) === '[object String]' }
322
323     var isArray = Array.isArray || function isArray (xs) {
324         return Object.prototype.toString.call(xs) === '[object Array]';
325     };
326
327     var forEach = function (xs, fn) {
328         if (xs.forEach) return xs.forEach(fn)
329         else for (var i = 0; i < xs.length; i++) {
330             fn(xs[i], i, xs);
331         }
332     };
333
334     forEach(objectKeys(Traverse.prototype), function (key) {
335         traverse[key] = function (obj) {
336             var args = [].slice.call(arguments, 1);
337             var t = new Traverse(obj);
338             return t[key].apply(t, args);
339         };
340     });
341
342     var hasOwnProperty = Object.hasOwnProperty || function (obj, key) {
343         return key in obj;
344     };
345 })();