chore(eslint): ESLint setup (#6708)
[openemr.git] / ccdaservice / oe-blue-button-util / lib / jsonpath / index.js
blobea72a506284d329c6dc191587c2d62bf69996bfe
1 /*global module, exports, require*/
2 /*jslint vars:true, evil:true*/
3 /* JSONPath 0.8.0 - XPath for JSON
4  *
5  * Copyright (c) 2007 Stefan Goessner (goessner.net)
6  * Licensed under the MIT (MIT-LICENSE.txt) licence.
7  */
9 'use strict';
11 var vm = require('vm');
12 var _ = require("lodash");
14 var arrayset = require('../arrayset');
16 var append = arrayset.append;
18 var properties = function (obj) {
19     if (!obj) {
20         return null;
21     } else if (Array.isArray(obj)) {
22         return _.range(obj.length);
23     } else if (typeof obj === 'object') {
24         return Object.keys(obj);
25     } else {
26         return null;
27     }
30 var asPath = function (path) {
31     var n = path.length;
32     var r = '$';
33     for (var i = 1; i < n; ++i) {
34         var p = path[i];
35         if (p[p.length - 1] === ')') {
36             r += '.' + p;
37         } else {
38             r += /^[0-9*]+$/.test(p) ? ('[' + p + ']') : ("['" + p + "']");
39         }
40     }
41     return r;
44 var normalize = exports.normalize = function (expr) {
45     var exprList = [];
46     var index = 0;
47     var length = expr.length;
48     var lastIndex = 0;
49     while (index < length) {
50         var c = expr.charAt(index);
51         if (c === '.') {
52             var cp1 = expr.charAt(index + 1);
53             if (lastIndex !== index) {
54                 var subExpr = expr.substring(lastIndex, index);
55                 exprList.push(subExpr);
56             }
57             if (cp1 === '.') {
58                 exprList.push('..');
59                 ++index;
60             }
61             ++index;
62             lastIndex = index;
63             continue;
64         }
65         if (c === '[') {
66             if (lastIndex !== index) {
67                 var subExprLB = expr.substring(lastIndex, index);
68                 exprList.push(subExprLB);
69             }
70             var openBrackets = 1;
71             ++index;
72             lastIndex = index;
73             while (index < length) {
74                 var cinside = expr.charAt(index);
75                 if (cinside === '[') {
76                     ++openBrackets;
77                     ++index;
78                     continue;
79                 }
80                 if (cinside === ']') {
81                     --openBrackets;
82                     if (openBrackets === 0) {
83                         var subExprInside = expr.substring(lastIndex, index);
84                         exprList.push(subExprInside);
85                         ++index;
86                         lastIndex = index;
87                         break;
88                     }
89                 }
90                 ++index;
91             }
92             continue;
93         }
94         if ((c === '^') || (c === '$')) {
95             if (lastIndex !== index) {
96                 throw new Error('Invalid use of \'^\' or \'$\' in the expression.');
97             }
98             exprList.push(c);
99             ++index;
100             lastIndex = index;
101             continue;
102         }
103         ++index;
104     }
105     if (lastIndex < index) {
106         var subExprFinal = expr.substring(lastIndex, index);
107         exprList.push(subExprFinal);
108     }
109     return exprList;
112 var processOptions = function (opts) {
113     return {
114         resultType: (opts && opts.resultType && opts.resultType.toLowerCase()) || 'value',
115         flatten: (opts && opts.flatten) || false,
116         wrap: (opts && Object.prototype.hasOwnProperty.call(opts, 'wrap')) ? opts.wrap : null,
117         sandbox: (opts && opts.sandbox) || {},
118         functions: (opts && opts.functions) || {}
119     };
122 var Accumulator = {
123     init: function (lookbackNeeded, pathNeeded) {
124         this.result = [];
125         this.step = 0;
126         if (lookbackNeeded) {
127             this.objHistory = [];
128         }
129         if (pathNeeded) {
130             this.path = [];
131         }
132     },
133     clone: function () {
134         var r = Object.create(Accumulator);
135         r.result = this.result;
136         r.obj = this.obj;
137         r.location = this.location;
138         r.step = this.step;
139         if (this.objHistory) {
140             r.objHistory = this.objHistory.slice();
141         }
142         if (this.path) {
143             r.path = this.path.slice();
144         }
145         return r;
146     },
147     add: function (obj, location) {
148         if (this.objHistory && (this.obj !== undefined)) {
149             this.objHistory.push(this.obj);
150         }
151         if (this.path && (this.location !== undefined)) {
152             this.path.push(this.location);
153         }
154         this.obj = obj;
155         this.location = location;
156     },
157     back: function () {
158         if (this.objHistory.length > 0) {
159             this.obj = this.objHistory.splice(-1, 1)[0];
160         }
162         if (this.path && this.path.length > 0) {
163             this.location = this.path.splice(-1, 1)[0];
164         }
165     },
166     currentObject: function () {
167         return this.obj;
168     },
169     currentPath: function () {
170         if (this.path) {
171             var r = this.path.slice();
172             r.push(this.location);
173             return r;
174         } else {
175             return null;
176         }
177     },
178     addToResult: function () {
179         this.result.push({
180             path: this.currentPath(),
181             value: this.currentObject()
182         });
183     },
184     incrementStep: function () {
185         ++this.step;
186         return this.step;
187     },
188     toResult: function (options) {
189         var result = this.result.slice();
190         if (!result.length) {
191             return options.wrap ? options.emptyValue : null;
192         }
193         if (result.length === 1 && !options.wrap && !Array.isArray(result[0].value)) {
194             return result[0][options.resultType];
195         }
196         return result.reduce(function (result, ea) {
197             var valOrPath = ea[options.resultType];
198             if (options.resultType === 'path') {
199                 valOrPath = asPath(valOrPath);
200             }
201             if (options.flatten && Array.isArray(valOrPath)) {
202                 result = result.concat(valOrPath);
203             } else {
204                 result.push(valOrPath);
205             }
206             return result;
207         }, []);
208     }
211 var PATH_VAR = '_$_path';
212 var VALUE_VAR = '_$_v';
214 var Tracer = {
215     init: function (obj, traceSteps, sandbox, context) {
216         this._obj = obj;
217         this.traceSteps = traceSteps;
218         this.sandbox = sandbox;
219         this.context = context || {};
220     },
221     traceStart: function (accumulator) {
222         accumulator.add(this._obj, '$');
223         this.traceNext(accumulator);
224     },
225     traceAllProperties: function (accumulator) {
226         var obj = accumulator.currentObject();
227         var ps = properties(obj);
228         this.traceProperties(ps, accumulator);
229     },
230     traceProperties: function (properties, accumulator) {
231         if (properties) {
232             properties.forEach(function (p) {
233                 var newAccumulator = accumulator.clone();
234                 this.traceProperty(p, newAccumulator);
235             }, this);
236         }
237     },
238     traceProperty: function (property, accumulator) {
239         var obj = accumulator.currentObject();
240         if (obj && Object.prototype.hasOwnProperty.call(obj, property)) {
241             accumulator.add(obj[property], property);
242             this.traceNext(accumulator);
243         }
244     },
245     traceAll: function (accumulator) {
246         var rootJspResult = accumulator.clone();
247         this.traceNext(rootJspResult);
248         var obj = accumulator.currentObject();
249         var ps = properties(obj);
250         if (ps) {
251             ps = ps.filter(function (p) {
252                 return typeof obj[p] === 'object';
253             });
254         }
255         if (ps) {
256             ps.forEach(function (p) {
257                 var newAccumulator = accumulator.clone();
258                 newAccumulator.add(obj[p], p);
259                 this.traceAll(newAccumulator);
260             }, this);
261         }
262     },
263     traceBack: function (accumulator) {
264         accumulator.back();
265         this.traceNext(accumulator);
266     },
267     traceEnd: function (accumulator) {
268         accumulator.addToResult();
269     },
270     traceNext: function (accumulator) {
271         var step = accumulator.incrementStep();
272         var action = this.traceSteps[step];
273         action.call(this, accumulator);
274     },
275     run: function (opts) {
276         var accumulator = Object.create(Accumulator);
277         accumulator.init(opts.lookbackNeeded, opts.pathNeeded);
278         var action = this.traceSteps[0];
279         action.call(this, accumulator);
280         return accumulator.toResult(opts);
281     },
282     eval: function (code, obj, addlLocation, path) {
283         if (code.indexOf(PATH_VAR) > -1) {
284             path = path.slice();
285             path.push(addlLocation);
286             this.sandbox[PATH_VAR] = asPath(path);
287         }
288         if (code.indexOf(VALUE_VAR) > -1) {
289             this.sandbox[VALUE_VAR] = obj;
290         }
291         try {
292             return vm.runInNewContext(code, this.sandbox);
293         } catch (e) {
294             throw new Error('jsonPath: ' + e.message + ': ' + code);
295         }
296     },
297     traceJsExprPropertyGen: function (expr) {
298         return function (accumulator) {
299             var path = accumulator.currentPath();
300             var obj = accumulator.currentObject();
301             var property = this.eval(expr, obj, null, path);
302             this.traceProperty(property, accumulator);
303         };
304     },
305     traceFilteredPropertiesGen: function (expr) {
306         var jsExpr = expr.replace(/^\?\((.*?)\)$/, '$1');
307         return function (accumulator) {
308             var obj = accumulator.currentObject();
309             var ps = properties(obj);
310             if (ps) {
311                 var path = accumulator.currentPath();
312                 ps = ps.filter(function (p) {
313                     return this.eval(jsExpr, obj[p], p, path);
314                 }, this);
315             }
316             this.traceProperties(ps, accumulator);
317         };
318     },
319     traceCommaDelimitedPropertiesGen: function (expr) {
320         var properties = expr.split(',');
321         return function (accumulator) {
322             this.traceProperties(properties, accumulator);
323         };
324     },
325     traceArrayRangeGen: function (expr) {
326         var indices = expr.split(':');
327         var start = (indices[0] && parseInt(indices[0], 10)) || 0;
328         var end = (indices[1] && parseInt(indices[1], 10)) || null;
329         var step = (indices[2] && parseInt(indices[2], 10)) || 1;
331         return function (accumulator) {
332             var obj = accumulator.currentObject();
333             var length = obj.length;
334             var localStart = (start < 0) ? Math.max(0, start + length) : Math.min(length, start);
335             var localEnd = (end === null) ? length : end;
336             localEnd = (localEnd < 0) ? Math.max(0, localEnd + length) : Math.min(length, localEnd);
337             var range = _.range(localStart, localEnd, step);
338             this.traceProperties(range, accumulator);
339         };
340     },
341     tracePropertyGen: function (property) {
342         return function (accumulator) {
343             this.traceProperty(property, accumulator);
344         };
345     },
346     tracePredefinedFunctionGen: function (fn, expr) {
347         return function (accumulator) {
348             var obj = accumulator.currentObject();
349             accumulator.add(fn(obj), expr);
350             this.traceNext(accumulator);
351         };
352     },
353     tracePostdefinedFunctionGen: function (expr) {
354         return function (accumulator) {
355             var obj = accumulator.currentObject();
356             var fnName = expr.substring(0, expr.length - 2);
357             var fn = this.context[fnName];
358             if (!fn) {
359                 throw new Error('No function named ' + fnName + '.');
360             }
361             accumulator.add(fn(obj), expr);
362             this.traceNext(accumulator);
363         };
364     },
365     traceRootPathPropertyGen: function (propertySteps, opts) {
366         return function (accumulator) {
367             var tracer = Object.create(Tracer);
368             tracer.init(this._obj, propertySteps, this.sandbox, this.context);
370             var property = tracer.run(opts);
371             if (property !== null) {
372                 if (Array.isArray(property)) {
373                     this.traceProperties(property, accumulator);
374                 } else {
375                     this.traceProperty(property, accumulator);
376                 }
377             }
378         };
379     }
382 var tracerAdapter = (function () {
383     var adapter = {};
385     adapter.directMap = {
386         "*": {
387             method: Tracer.traceAllProperties,
388             branches: true
389         },
390         "..": {
391             method: Tracer.traceAll,
392             branches: true
393         },
394         "$": {
395             method: Tracer.traceStart
396         },
397         "^": {
398             method: Tracer.traceBack,
399             lookbackNeeded: true
400         }
401     };
403     adapter.handleJSExpression = function (expr) {
404         expr = expr.replace(/@path/g, PATH_VAR);
405         expr = expr.replace(/@/g, VALUE_VAR);
406         return expr;
407     };
409     adapter.reMap = [{
410         re: /^\(.+\)$/,
411         obj: function (expr) {
412             var modifiedExpr = adapter.handleJSExpression(expr);
413             return {
414                 method: Tracer.traceJsExprPropertyGen(modifiedExpr),
415                 pathNeeded: expr.indexOf('@path') > -1
416             };
417         }
418     }, {
419         re: /^\?\(.+\)$/,
420         obj: function (expr) {
421             var modifiedExpr = adapter.handleJSExpression(expr);
422             return {
423                 method: Tracer.traceFilteredPropertiesGen(modifiedExpr),
424                 pathNeeded: expr.indexOf('@path') > -1,
425                 branches: true
426             };
427         }
428     }, {
429         re: /,/,
430         obj: function (expr) {
431             return {
432                 method: Tracer.traceCommaDelimitedPropertiesGen(expr),
433                 branches: true
434             };
435         }
436     }, {
437         re: /^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/,
438         obj: function (expr) {
439             return {
440                 method: Tracer.traceArrayRangeGen(expr),
441                 branches: true
442             };
443         }
444     }, {
445         re: /\(\)$/,
446         obj: function (expr, opts) {
447             var fnName = expr.substring(0, expr.length - 2);
448             var fn = opts.functions[fnName];
449             if (fn) {
450                 return {
451                     method: Tracer.tracePredefinedFunctionGen(fn, expr)
452                 };
453             } else {
454                 return {
455                     method: Tracer.tracePostdefinedFunctionGen(expr)
456                 };
457             }
458         }
459     }, {
460         re: /^\$/,
461         obj: function (expr, opts) {
462             var internalOpts = processOptions({});
463             var propertySteps = adapter.run(expr, internalOpts);
464             return {
465                 method: Tracer.traceRootPathPropertyGen(propertySteps, internalOpts)
466             };
467         }
468     }];
470     adapter.exprToMethod = function (expr, opts) {
471         var r = this.directMap[expr];
472         if (r) {
473             return r;
474         }
475         for (var i = 0; i < this.reMap.length; ++i) {
476             var reItem = this.reMap[i];
477             if (reItem.re.test(expr)) {
478                 return reItem.obj(expr, opts);
479             }
480         }
481         return {
482             method: Tracer.tracePropertyGen(expr)
483         };
484     };
486     adapter.exprToMethodUpdateOptions = function (expr, opts) {
487         var r = this.exprToMethod(expr, opts);
488         if (r.branches && (opts.wrap === null)) {
489             opts.wrap = true;
490         }
491         if (r.lookbackNeeded) {
492             opts.lookbackNeeded = true;
493         }
494         if (r.pathNeeded) {
495             opts.pathNeeded = true;
496         }
497         return r.method;
498     };
500     adapter.run = function (inputExpr, opts) {
501         var normalizedExprList = normalize(inputExpr);
502         var steps = normalizedExprList.map(function (expr) {
503             return adapter.exprToMethodUpdateOptions(expr, opts);
504         });
505         steps.push(Tracer.traceEnd);
506         return steps;
507     };
509     return adapter.run;
510 })();
512 exports.instance = function (inputExpr, opts) {
514     opts = processOptions(opts);
515     if ((opts.resultType !== 'value') && (opts.resultType !== 'path')) {
516         throw new Error('Invalid option resultType: ' + opts.resultType);
517     }
518     opts.pathNeeded = (opts.resultType === 'path');
519     opts.lookbackNeeded = false;
520     opts.emptyValue = (opts.wrap === null) ? null : [];
522     if (!inputExpr) {
523         throw new Error('An input expression is required.');
524     }
526     var traceSteps = tracerAdapter(inputExpr, opts);
527     if (traceSteps[0] !== Tracer.traceStart) {
528         traceSteps.unshift(Tracer.traceStart);
529     }
531     return function (obj, context) {
532         var tracer = Object.create(Tracer);
533         tracer.init(obj, traceSteps, opts.sandbox, context);
535         return tracer.run(opts);
536     };