1 /*global module, exports, require*/
2 /*jslint vars:true, evil:true*/
3 /* JSONPath 0.8.0 - XPath for JSON
5 * Copyright (c) 2007 Stefan Goessner (goessner.net)
6 * Licensed under the MIT (MIT-LICENSE.txt) licence.
11 var vm = require('vm');
12 var _ = require("lodash");
14 var arrayset = require('../arrayset');
16 var append = arrayset.append;
18 var properties = function (obj) {
21 } else if (Array.isArray(obj)) {
22 return _.range(obj.length);
23 } else if (typeof obj === 'object') {
24 return Object.keys(obj);
30 var asPath = function (path) {
33 for (var i = 1; i < n; ++i) {
35 if (p[p.length - 1] === ')') {
38 r += /^[0-9*]+$/.test(p) ? ('[' + p + ']') : ("['" + p + "']");
44 var normalize = exports.normalize = function (expr) {
47 var length = expr.length;
49 while (index < length) {
50 var c = expr.charAt(index);
52 var cp1 = expr.charAt(index + 1);
53 if (lastIndex !== index) {
54 var subExpr = expr.substring(lastIndex, index);
55 exprList.push(subExpr);
66 if (lastIndex !== index) {
67 var subExprLB = expr.substring(lastIndex, index);
68 exprList.push(subExprLB);
73 while (index < length) {
74 var cinside = expr.charAt(index);
75 if (cinside === '[') {
80 if (cinside === ']') {
82 if (openBrackets === 0) {
83 var subExprInside = expr.substring(lastIndex, index);
84 exprList.push(subExprInside);
94 if ((c === '^') || (c === '$')) {
95 if (lastIndex !== index) {
96 throw new Error('Invalid use of \'^\' or \'$\' in the expression.');
105 if (lastIndex < index) {
106 var subExprFinal = expr.substring(lastIndex, index);
107 exprList.push(subExprFinal);
112 var processOptions = function (opts) {
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) || {}
123 init: function (lookbackNeeded, pathNeeded) {
126 if (lookbackNeeded) {
127 this.objHistory = [];
134 var r = Object.create(Accumulator);
135 r.result = this.result;
137 r.location = this.location;
139 if (this.objHistory) {
140 r.objHistory = this.objHistory.slice();
143 r.path = this.path.slice();
147 add: function (obj, location) {
148 if (this.objHistory && (this.obj !== undefined)) {
149 this.objHistory.push(this.obj);
151 if (this.path && (this.location !== undefined)) {
152 this.path.push(this.location);
155 this.location = location;
158 if (this.objHistory.length > 0) {
159 this.obj = this.objHistory.splice(-1, 1)[0];
162 if (this.path && this.path.length > 0) {
163 this.location = this.path.splice(-1, 1)[0];
166 currentObject: function () {
169 currentPath: function () {
171 var r = this.path.slice();
172 r.push(this.location);
178 addToResult: function () {
180 path: this.currentPath(),
181 value: this.currentObject()
184 incrementStep: function () {
188 toResult: function (options) {
189 var result = this.result.slice();
190 if (!result.length) {
191 return options.wrap ? options.emptyValue : null;
193 if (result.length === 1 && !options.wrap && !Array.isArray(result[0].value)) {
194 return result[0][options.resultType];
196 return result.reduce(function (result, ea) {
197 var valOrPath = ea[options.resultType];
198 if (options.resultType === 'path') {
199 valOrPath = asPath(valOrPath);
201 if (options.flatten && Array.isArray(valOrPath)) {
202 result = result.concat(valOrPath);
204 result.push(valOrPath);
211 var PATH_VAR = '_$_path';
212 var VALUE_VAR = '_$_v';
215 init: function (obj, traceSteps, sandbox, context) {
217 this.traceSteps = traceSteps;
218 this.sandbox = sandbox;
219 this.context = context || {};
221 traceStart: function (accumulator) {
222 accumulator.add(this._obj, '$');
223 this.traceNext(accumulator);
225 traceAllProperties: function (accumulator) {
226 var obj = accumulator.currentObject();
227 var ps = properties(obj);
228 this.traceProperties(ps, accumulator);
230 traceProperties: function (properties, accumulator) {
232 properties.forEach(function (p) {
233 var newAccumulator = accumulator.clone();
234 this.traceProperty(p, newAccumulator);
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);
245 traceAll: function (accumulator) {
246 var rootJspResult = accumulator.clone();
247 this.traceNext(rootJspResult);
248 var obj = accumulator.currentObject();
249 var ps = properties(obj);
251 ps = ps.filter(function (p) {
252 return typeof obj[p] === 'object';
256 ps.forEach(function (p) {
257 var newAccumulator = accumulator.clone();
258 newAccumulator.add(obj[p], p);
259 this.traceAll(newAccumulator);
263 traceBack: function (accumulator) {
265 this.traceNext(accumulator);
267 traceEnd: function (accumulator) {
268 accumulator.addToResult();
270 traceNext: function (accumulator) {
271 var step = accumulator.incrementStep();
272 var action = this.traceSteps[step];
273 action.call(this, accumulator);
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);
282 eval: function (code, obj, addlLocation, path) {
283 if (code.indexOf(PATH_VAR) > -1) {
285 path.push(addlLocation);
286 this.sandbox[PATH_VAR] = asPath(path);
288 if (code.indexOf(VALUE_VAR) > -1) {
289 this.sandbox[VALUE_VAR] = obj;
292 return vm.runInNewContext(code, this.sandbox);
294 throw new Error('jsonPath: ' + e.message + ': ' + code);
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);
305 traceFilteredPropertiesGen: function (expr) {
306 var jsExpr = expr.replace(/^\?\((.*?)\)$/, '$1');
307 return function (accumulator) {
308 var obj = accumulator.currentObject();
309 var ps = properties(obj);
311 var path = accumulator.currentPath();
312 ps = ps.filter(function (p) {
313 return this.eval(jsExpr, obj[p], p, path);
316 this.traceProperties(ps, accumulator);
319 traceCommaDelimitedPropertiesGen: function (expr) {
320 var properties = expr.split(',');
321 return function (accumulator) {
322 this.traceProperties(properties, accumulator);
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);
341 tracePropertyGen: function (property) {
342 return function (accumulator) {
343 this.traceProperty(property, accumulator);
346 tracePredefinedFunctionGen: function (fn, expr) {
347 return function (accumulator) {
348 var obj = accumulator.currentObject();
349 accumulator.add(fn(obj), expr);
350 this.traceNext(accumulator);
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];
359 throw new Error('No function named ' + fnName + '.');
361 accumulator.add(fn(obj), expr);
362 this.traceNext(accumulator);
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);
375 this.traceProperty(property, accumulator);
382 var tracerAdapter = (function () {
385 adapter.directMap = {
387 method: Tracer.traceAllProperties,
391 method: Tracer.traceAll,
395 method: Tracer.traceStart
398 method: Tracer.traceBack,
403 adapter.handleJSExpression = function (expr) {
404 expr = expr.replace(/@path/g, PATH_VAR);
405 expr = expr.replace(/@/g, VALUE_VAR);
411 obj: function (expr) {
412 var modifiedExpr = adapter.handleJSExpression(expr);
414 method: Tracer.traceJsExprPropertyGen(modifiedExpr),
415 pathNeeded: expr.indexOf('@path') > -1
420 obj: function (expr) {
421 var modifiedExpr = adapter.handleJSExpression(expr);
423 method: Tracer.traceFilteredPropertiesGen(modifiedExpr),
424 pathNeeded: expr.indexOf('@path') > -1,
430 obj: function (expr) {
432 method: Tracer.traceCommaDelimitedPropertiesGen(expr),
437 re: /^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/,
438 obj: function (expr) {
440 method: Tracer.traceArrayRangeGen(expr),
446 obj: function (expr, opts) {
447 var fnName = expr.substring(0, expr.length - 2);
448 var fn = opts.functions[fnName];
451 method: Tracer.tracePredefinedFunctionGen(fn, expr)
455 method: Tracer.tracePostdefinedFunctionGen(expr)
461 obj: function (expr, opts) {
462 var internalOpts = processOptions({});
463 var propertySteps = adapter.run(expr, internalOpts);
465 method: Tracer.traceRootPathPropertyGen(propertySteps, internalOpts)
470 adapter.exprToMethod = function (expr, opts) {
471 var r = this.directMap[expr];
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);
482 method: Tracer.tracePropertyGen(expr)
486 adapter.exprToMethodUpdateOptions = function (expr, opts) {
487 var r = this.exprToMethod(expr, opts);
488 if (r.branches && (opts.wrap === null)) {
491 if (r.lookbackNeeded) {
492 opts.lookbackNeeded = true;
495 opts.pathNeeded = true;
500 adapter.run = function (inputExpr, opts) {
501 var normalizedExprList = normalize(inputExpr);
502 var steps = normalizedExprList.map(function (expr) {
503 return adapter.exprToMethodUpdateOptions(expr, opts);
505 steps.push(Tracer.traceEnd);
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);
518 opts.pathNeeded = (opts.resultType === 'path');
519 opts.lookbackNeeded = false;
520 opts.emptyValue = (opts.wrap === null) ? null : [];
523 throw new Error('An input expression is required.');
526 var traceSteps = tracerAdapter(inputExpr, opts);
527 if (traceSteps[0] !== Tracer.traceStart) {
528 traceSteps.unshift(Tracer.traceStart);
531 return function (obj, context) {
532 var tracer = Object.create(Tracer);
533 tracer.init(obj, traceSteps, opts.sandbox, context);
535 return tracer.run(opts);