1 | var assert = require('assert');
|
2 | var dict = require('./dict');
|
3 | var Parser = require('./parser');
|
4 | var Handlers = require('./handlers');
|
5 |
|
6 | var JSONPath = function() {
|
7 | this.initialize.apply(this, arguments);
|
8 | };
|
9 |
|
10 | JSONPath.prototype.initialize = function() {
|
11 | this.parser = new Parser();
|
12 | this.handlers = new Handlers();
|
13 | };
|
14 |
|
15 | JSONPath.prototype.parse = function(string) {
|
16 | assert.ok(_is_string(string), "we need a path");
|
17 | return this.parser.parse(string);
|
18 | };
|
19 |
|
20 | JSONPath.prototype.parent = function(obj, string) {
|
21 |
|
22 | assert.ok(obj instanceof Object, "obj needs to be an object");
|
23 | assert.ok(string, "we need a path");
|
24 |
|
25 | var node = this.nodes(obj, string)[0];
|
26 | var key = node.path.pop();
|
27 | return this.value(obj, node.path);
|
28 | }
|
29 |
|
30 | JSONPath.prototype.apply = function(obj, string, fn) {
|
31 |
|
32 | assert.ok(obj instanceof Object, "obj needs to be an object");
|
33 | assert.ok(string, "we need a path");
|
34 | assert.equal(typeof fn, "function", "fn needs to be function")
|
35 |
|
36 | var nodes = this.nodes(obj, string).sort(function(a, b) {
|
37 |
|
38 | return b.path.length - a.path.length;
|
39 | });
|
40 |
|
41 | nodes.forEach(function(node) {
|
42 | var key = node.path.pop();
|
43 | var parent = this.value(obj, this.stringify(node.path));
|
44 | var val = node.value = fn.call(obj, parent[key]);
|
45 | parent[key] = val;
|
46 | }, this);
|
47 |
|
48 | return nodes;
|
49 | }
|
50 |
|
51 | JSONPath.prototype.value = function(obj, path, value) {
|
52 |
|
53 | assert.ok(obj instanceof Object, "obj needs to be an object");
|
54 | assert.ok(path, "we need a path");
|
55 |
|
56 | if (arguments.length >= 3) {
|
57 | var node = this.nodes(obj, path).shift();
|
58 | if (!node) return this._vivify(obj, path, value);
|
59 | var key = node.path.slice(-1).shift();
|
60 | var parent = this.parent(obj, this.stringify(node.path));
|
61 | parent[key] = value;
|
62 | }
|
63 | return this.query(obj, this.stringify(path), 1).shift();
|
64 | }
|
65 |
|
66 | JSONPath.prototype._vivify = function(obj, string, value) {
|
67 |
|
68 | var self = this;
|
69 |
|
70 | assert.ok(obj instanceof Object, "obj needs to be an object");
|
71 | assert.ok(string, "we need a path");
|
72 |
|
73 | var path = this.parser.parse(string)
|
74 | .map(function(component) { return component.expression.value });
|
75 |
|
76 | var setValue = function(path, value) {
|
77 | var key = path.pop();
|
78 | var node = self.value(obj, path);
|
79 | if (!node) {
|
80 | setValue(path.concat(), typeof key === 'string' ? {} : []);
|
81 | node = self.value(obj, path);
|
82 | }
|
83 | node[key] = value;
|
84 | }
|
85 | setValue(path, value);
|
86 | return this.query(obj, string)[0];
|
87 | }
|
88 |
|
89 | JSONPath.prototype.query = function(obj, string, count) {
|
90 |
|
91 | assert.ok(obj instanceof Object, "obj needs to be an object");
|
92 | assert.ok(_is_string(string), "we need a path");
|
93 |
|
94 | var results = this.nodes(obj, string, count)
|
95 | .map(function(r) { return r.value });
|
96 |
|
97 | return results;
|
98 | };
|
99 |
|
100 | JSONPath.prototype.paths = function(obj, string, count) {
|
101 |
|
102 | assert.ok(obj instanceof Object, "obj needs to be an object");
|
103 | assert.ok(string, "we need a path");
|
104 |
|
105 | var results = this.nodes(obj, string, count)
|
106 | .map(function(r) { return r.path });
|
107 |
|
108 | return results;
|
109 | };
|
110 |
|
111 | JSONPath.prototype.nodes = function(obj, string, count) {
|
112 |
|
113 | assert.ok(obj instanceof Object, "obj needs to be an object");
|
114 | assert.ok(string, "we need a path");
|
115 |
|
116 | if (count === 0) return [];
|
117 |
|
118 | var path = this.parser.parse(string);
|
119 | var handlers = this.handlers;
|
120 |
|
121 | var partials = [ { path: ['$'], value: obj } ];
|
122 | var matches = [];
|
123 |
|
124 | if (path.length && path[0].expression.type == 'root') path.shift();
|
125 |
|
126 | if (!path.length) return partials;
|
127 |
|
128 | path.forEach(function(component, index) {
|
129 |
|
130 | if (matches.length >= count) return;
|
131 | var handler = handlers.resolve(component);
|
132 | var _partials = [];
|
133 |
|
134 | partials.forEach(function(p) {
|
135 |
|
136 | if (matches.length >= count) return;
|
137 | var results = handler(component, p, count);
|
138 |
|
139 | if (index == path.length - 1) {
|
140 |
|
141 | matches = matches.concat(results || []);
|
142 | } else {
|
143 |
|
144 | _partials = _partials.concat(results || []);
|
145 | }
|
146 | });
|
147 |
|
148 | partials = _partials;
|
149 |
|
150 | });
|
151 |
|
152 | return count ? matches.slice(0, count) : matches;
|
153 | };
|
154 |
|
155 | JSONPath.prototype.stringify = function(path) {
|
156 |
|
157 | assert.ok(path, "we need a path");
|
158 |
|
159 | var string = '$';
|
160 |
|
161 | var templates = {
|
162 | 'descendant-member': '..{{value}}',
|
163 | 'child-member': '.{{value}}',
|
164 | 'descendant-subscript': '..[{{value}}]',
|
165 | 'child-subscript': '[{{value}}]'
|
166 | };
|
167 |
|
168 | path = this._normalize(path);
|
169 |
|
170 | path.forEach(function(component) {
|
171 |
|
172 | if (component.expression.type == 'root') return;
|
173 |
|
174 | var key = [component.scope, component.operation].join('-');
|
175 | var template = templates[key];
|
176 | var value;
|
177 |
|
178 | if (component.expression.type == 'string_literal') {
|
179 | value = JSON.stringify(component.expression.value)
|
180 | } else {
|
181 | value = component.expression.value;
|
182 | }
|
183 |
|
184 | if (!template) throw new Error("couldn't find template " + key);
|
185 |
|
186 | string += template.replace(/{{value}}/, value);
|
187 | });
|
188 |
|
189 | return string;
|
190 | }
|
191 |
|
192 | JSONPath.prototype._normalize = function(path) {
|
193 |
|
194 | assert.ok(path, "we need a path");
|
195 |
|
196 | if (typeof path == "string") {
|
197 |
|
198 | return this.parser.parse(path);
|
199 |
|
200 | } else if (Array.isArray(path) && typeof path[0] == "string") {
|
201 |
|
202 | var _path = [ { expression: { type: "root", value: "$" } } ];
|
203 |
|
204 | path.forEach(function(component, index) {
|
205 |
|
206 | if (component == '$' && index === 0) return;
|
207 |
|
208 | if (typeof component == "string" && component.match("^" + dict.identifier + "$")) {
|
209 |
|
210 | _path.push({
|
211 | operation: 'member',
|
212 | scope: 'child',
|
213 | expression: { value: component, type: 'identifier' }
|
214 | });
|
215 |
|
216 | } else {
|
217 |
|
218 | var type = typeof component == "number" ?
|
219 | 'numeric_literal' : 'string_literal';
|
220 |
|
221 | _path.push({
|
222 | operation: 'subscript',
|
223 | scope: 'child',
|
224 | expression: { value: component, type: type }
|
225 | });
|
226 | }
|
227 | });
|
228 |
|
229 | return _path;
|
230 |
|
231 | } else if (Array.isArray(path) && typeof path[0] == "object") {
|
232 |
|
233 | return path
|
234 | }
|
235 |
|
236 | throw new Error("couldn't understand path " + path);
|
237 | }
|
238 |
|
239 | function _is_string(obj) {
|
240 | return Object.prototype.toString.call(obj) == '[object String]';
|
241 | }
|
242 |
|
243 | JSONPath.Handlers = Handlers;
|
244 | JSONPath.Parser = Parser;
|
245 |
|
246 | var instance = new JSONPath;
|
247 | instance.JSONPath = JSONPath;
|
248 |
|
249 | module.exports = instance;
|