index.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. var assert = require('assert');
  2. var dict = require('./dict');
  3. var Parser = require('./parser');
  4. var Handlers = require('./handlers');
  5. var JSONPath = function() {
  6. this.initialize.apply(this, arguments);
  7. };
  8. JSONPath.prototype.initialize = function() {
  9. this.parser = new Parser();
  10. this.handlers = new Handlers();
  11. };
  12. JSONPath.prototype.parse = function(string) {
  13. assert.ok(_is_string(string), "we need a path");
  14. return this.parser.parse(string);
  15. };
  16. JSONPath.prototype.parent = function(obj, string) {
  17. assert.ok(obj instanceof Object, "obj needs to be an object");
  18. assert.ok(string, "we need a path");
  19. var node = this.nodes(obj, string)[0];
  20. var key = node.path.pop(); /* jshint unused:false */
  21. return this.value(obj, node.path);
  22. }
  23. JSONPath.prototype.apply = function(obj, string, fn) {
  24. assert.ok(obj instanceof Object, "obj needs to be an object");
  25. assert.ok(string, "we need a path");
  26. assert.equal(typeof fn, "function", "fn needs to be function")
  27. var nodes = this.nodes(obj, string).sort(function(a, b) {
  28. // sort nodes so we apply from the bottom up
  29. return b.path.length - a.path.length;
  30. });
  31. nodes.forEach(function(node) {
  32. var key = node.path.pop();
  33. var parent = this.value(obj, this.stringify(node.path));
  34. var val = node.value = fn.call(obj, parent[key]);
  35. parent[key] = val;
  36. }, this);
  37. return nodes;
  38. }
  39. JSONPath.prototype.value = function(obj, path, value) {
  40. assert.ok(obj instanceof Object, "obj needs to be an object");
  41. assert.ok(path, "we need a path");
  42. if (arguments.length >= 3) {
  43. var node = this.nodes(obj, path).shift();
  44. if (!node) return this._vivify(obj, path, value);
  45. var key = node.path.slice(-1).shift();
  46. var parent = this.parent(obj, this.stringify(node.path));
  47. parent[key] = value;
  48. }
  49. return this.query(obj, this.stringify(path), 1).shift();
  50. }
  51. JSONPath.prototype._vivify = function(obj, string, value) {
  52. var self = this;
  53. assert.ok(obj instanceof Object, "obj needs to be an object");
  54. assert.ok(string, "we need a path");
  55. var path = this.parser.parse(string)
  56. .map(function(component) { return component.expression.value });
  57. var setValue = function(path, value) {
  58. var key = path.pop();
  59. var node = self.value(obj, path);
  60. if (!node) {
  61. setValue(path.concat(), typeof key === 'string' ? {} : []);
  62. node = self.value(obj, path);
  63. }
  64. node[key] = value;
  65. }
  66. setValue(path, value);
  67. return this.query(obj, string)[0];
  68. }
  69. JSONPath.prototype.query = function(obj, string, count) {
  70. assert.ok(obj instanceof Object, "obj needs to be an object");
  71. assert.ok(_is_string(string), "we need a path");
  72. var results = this.nodes(obj, string, count)
  73. .map(function(r) { return r.value });
  74. return results;
  75. };
  76. JSONPath.prototype.paths = function(obj, string, count) {
  77. assert.ok(obj instanceof Object, "obj needs to be an object");
  78. assert.ok(string, "we need a path");
  79. var results = this.nodes(obj, string, count)
  80. .map(function(r) { return r.path });
  81. return results;
  82. };
  83. JSONPath.prototype.nodes = function(obj, string, count) {
  84. assert.ok(obj instanceof Object, "obj needs to be an object");
  85. assert.ok(string, "we need a path");
  86. if (count === 0) return [];
  87. var path = this.parser.parse(string);
  88. var handlers = this.handlers;
  89. var partials = [ { path: ['$'], value: obj } ];
  90. var matches = [];
  91. if (path.length && path[0].expression.type == 'root') path.shift();
  92. if (!path.length) return partials;
  93. path.forEach(function(component, index) {
  94. if (matches.length >= count) return;
  95. var handler = handlers.resolve(component);
  96. var _partials = [];
  97. partials.forEach(function(p) {
  98. if (matches.length >= count) return;
  99. var results = handler(component, p, count);
  100. if (index == path.length - 1) {
  101. // if we're through the components we're done
  102. matches = matches.concat(results || []);
  103. } else {
  104. // otherwise accumulate and carry on through
  105. _partials = _partials.concat(results || []);
  106. }
  107. });
  108. partials = _partials;
  109. });
  110. return count ? matches.slice(0, count) : matches;
  111. };
  112. JSONPath.prototype.stringify = function(path) {
  113. assert.ok(path, "we need a path");
  114. var string = '$';
  115. var templates = {
  116. 'descendant-member': '..{{value}}',
  117. 'child-member': '.{{value}}',
  118. 'descendant-subscript': '..[{{value}}]',
  119. 'child-subscript': '[{{value}}]'
  120. };
  121. path = this._normalize(path);
  122. path.forEach(function(component) {
  123. if (component.expression.type == 'root') return;
  124. var key = [component.scope, component.operation].join('-');
  125. var template = templates[key];
  126. var value;
  127. if (component.expression.type == 'string_literal') {
  128. value = JSON.stringify(component.expression.value)
  129. } else {
  130. value = component.expression.value;
  131. }
  132. if (!template) throw new Error("couldn't find template " + key);
  133. string += template.replace(/{{value}}/, value);
  134. });
  135. return string;
  136. }
  137. JSONPath.prototype._normalize = function(path) {
  138. assert.ok(path, "we need a path");
  139. if (typeof path == "string") {
  140. return this.parser.parse(path);
  141. } else if (Array.isArray(path) && typeof path[0] == "string") {
  142. var _path = [ { expression: { type: "root", value: "$" } } ];
  143. path.forEach(function(component, index) {
  144. if (component == '$' && index === 0) return;
  145. if (typeof component == "string" && component.match("^" + dict.identifier + "$")) {
  146. _path.push({
  147. operation: 'member',
  148. scope: 'child',
  149. expression: { value: component, type: 'identifier' }
  150. });
  151. } else {
  152. var type = typeof component == "number" ?
  153. 'numeric_literal' : 'string_literal';
  154. _path.push({
  155. operation: 'subscript',
  156. scope: 'child',
  157. expression: { value: component, type: type }
  158. });
  159. }
  160. });
  161. return _path;
  162. } else if (Array.isArray(path) && typeof path[0] == "object") {
  163. return path
  164. }
  165. throw new Error("couldn't understand path " + path);
  166. }
  167. function _is_string(obj) {
  168. return Object.prototype.toString.call(obj) == '[object String]';
  169. }
  170. JSONPath.Handlers = Handlers;
  171. JSONPath.Parser = Parser;
  172. var instance = new JSONPath;
  173. instance.JSONPath = JSONPath;
  174. module.exports = instance;