123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- var assert = require('assert');
- var dict = require('./dict');
- var Parser = require('./parser');
- var Handlers = require('./handlers');
- var JSONPath = function() {
- this.initialize.apply(this, arguments);
- };
- JSONPath.prototype.initialize = function() {
- this.parser = new Parser();
- this.handlers = new Handlers();
- };
- JSONPath.prototype.parse = function(string) {
- assert.ok(_is_string(string), "we need a path");
- return this.parser.parse(string);
- };
- JSONPath.prototype.parent = function(obj, string) {
- assert.ok(obj instanceof Object, "obj needs to be an object");
- assert.ok(string, "we need a path");
- var node = this.nodes(obj, string)[0];
- var key = node.path.pop(); /* jshint unused:false */
- return this.value(obj, node.path);
- }
- JSONPath.prototype.apply = function(obj, string, fn) {
- assert.ok(obj instanceof Object, "obj needs to be an object");
- assert.ok(string, "we need a path");
- assert.equal(typeof fn, "function", "fn needs to be function")
- var nodes = this.nodes(obj, string).sort(function(a, b) {
- // sort nodes so we apply from the bottom up
- return b.path.length - a.path.length;
- });
- nodes.forEach(function(node) {
- var key = node.path.pop();
- var parent = this.value(obj, this.stringify(node.path));
- var val = node.value = fn.call(obj, parent[key]);
- parent[key] = val;
- }, this);
- return nodes;
- }
- JSONPath.prototype.value = function(obj, path, value) {
- assert.ok(obj instanceof Object, "obj needs to be an object");
- assert.ok(path, "we need a path");
- if (arguments.length >= 3) {
- var node = this.nodes(obj, path).shift();
- if (!node) return this._vivify(obj, path, value);
- var key = node.path.slice(-1).shift();
- var parent = this.parent(obj, this.stringify(node.path));
- parent[key] = value;
- }
- return this.query(obj, this.stringify(path), 1).shift();
- }
- JSONPath.prototype._vivify = function(obj, string, value) {
- var self = this;
- assert.ok(obj instanceof Object, "obj needs to be an object");
- assert.ok(string, "we need a path");
- var path = this.parser.parse(string)
- .map(function(component) { return component.expression.value });
- var setValue = function(path, value) {
- var key = path.pop();
- var node = self.value(obj, path);
- if (!node) {
- setValue(path.concat(), typeof key === 'string' ? {} : []);
- node = self.value(obj, path);
- }
- node[key] = value;
- }
- setValue(path, value);
- return this.query(obj, string)[0];
- }
- JSONPath.prototype.query = function(obj, string, count) {
- assert.ok(obj instanceof Object, "obj needs to be an object");
- assert.ok(_is_string(string), "we need a path");
- var results = this.nodes(obj, string, count)
- .map(function(r) { return r.value });
- return results;
- };
- JSONPath.prototype.paths = function(obj, string, count) {
- assert.ok(obj instanceof Object, "obj needs to be an object");
- assert.ok(string, "we need a path");
- var results = this.nodes(obj, string, count)
- .map(function(r) { return r.path });
- return results;
- };
- JSONPath.prototype.nodes = function(obj, string, count) {
- assert.ok(obj instanceof Object, "obj needs to be an object");
- assert.ok(string, "we need a path");
- if (count === 0) return [];
- var path = this.parser.parse(string);
- var handlers = this.handlers;
- var partials = [ { path: ['$'], value: obj } ];
- var matches = [];
- if (path.length && path[0].expression.type == 'root') path.shift();
- if (!path.length) return partials;
- path.forEach(function(component, index) {
- if (matches.length >= count) return;
- var handler = handlers.resolve(component);
- var _partials = [];
- partials.forEach(function(p) {
- if (matches.length >= count) return;
- var results = handler(component, p, count);
- if (index == path.length - 1) {
- // if we're through the components we're done
- matches = matches.concat(results || []);
- } else {
- // otherwise accumulate and carry on through
- _partials = _partials.concat(results || []);
- }
- });
- partials = _partials;
- });
- return count ? matches.slice(0, count) : matches;
- };
- JSONPath.prototype.stringify = function(path) {
- assert.ok(path, "we need a path");
- var string = '$';
- var templates = {
- 'descendant-member': '..{{value}}',
- 'child-member': '.{{value}}',
- 'descendant-subscript': '..[{{value}}]',
- 'child-subscript': '[{{value}}]'
- };
- path = this._normalize(path);
- path.forEach(function(component) {
- if (component.expression.type == 'root') return;
- var key = [component.scope, component.operation].join('-');
- var template = templates[key];
- var value;
- if (component.expression.type == 'string_literal') {
- value = JSON.stringify(component.expression.value)
- } else {
- value = component.expression.value;
- }
- if (!template) throw new Error("couldn't find template " + key);
- string += template.replace(/{{value}}/, value);
- });
- return string;
- }
- JSONPath.prototype._normalize = function(path) {
- assert.ok(path, "we need a path");
- if (typeof path == "string") {
- return this.parser.parse(path);
- } else if (Array.isArray(path) && typeof path[0] == "string") {
- var _path = [ { expression: { type: "root", value: "$" } } ];
- path.forEach(function(component, index) {
- if (component == '$' && index === 0) return;
- if (typeof component == "string" && component.match("^" + dict.identifier + "$")) {
- _path.push({
- operation: 'member',
- scope: 'child',
- expression: { value: component, type: 'identifier' }
- });
- } else {
- var type = typeof component == "number" ?
- 'numeric_literal' : 'string_literal';
- _path.push({
- operation: 'subscript',
- scope: 'child',
- expression: { value: component, type: type }
- });
- }
- });
- return _path;
- } else if (Array.isArray(path) && typeof path[0] == "object") {
- return path
- }
- throw new Error("couldn't understand path " + path);
- }
- function _is_string(obj) {
- return Object.prototype.toString.call(obj) == '[object String]';
- }
- JSONPath.Handlers = Handlers;
- JSONPath.Parser = Parser;
- var instance = new JSONPath;
- instance.JSONPath = JSONPath;
- module.exports = instance;
|