UNPKG

7.57 kBJavaScriptView Raw
1var aesprim = require('./aesprim');
2var slice = require('./slice');
3var _evaluate = require('static-eval');
4var _uniq = require('underscore').uniq;
5
6var Handlers = function() {
7 return this.initialize.apply(this, arguments);
8}
9
10Handlers.prototype.initialize = function() {
11 this.traverse = traverser(true);
12 this.descend = traverser();
13}
14
15Handlers.prototype.keys = Object.keys;
16
17Handlers.prototype.resolve = function(component) {
18
19 var key = [ component.operation, component.scope, component.expression.type ].join('-');
20 var method = this._fns[key];
21
22 if (!method) throw new Error("couldn't resolve key: " + key);
23 return method.bind(this);
24};
25
26Handlers.prototype.register = function(key, handler) {
27
28 if (!handler instanceof Function) {
29 throw new Error("handler must be a function");
30 }
31
32 this._fns[key] = handler;
33};
34
35Handlers.prototype._fns = {
36
37 'member-child-identifier': function(component, partial) {
38 var key = component.expression.value;
39 var value = partial.value;
40 if (value instanceof Object && key in value) {
41 return [ { value: value[key], path: partial.path.concat(key) } ]
42 }
43 },
44
45 'member-descendant-identifier':
46 _traverse(function(key, value, ref) { return key == ref }),
47
48 'subscript-child-numeric_literal':
49 _descend(function(key, value, ref) { return key === ref }),
50
51 'member-child-numeric_literal':
52 _descend(function(key, value, ref) { return String(key) === String(ref) }),
53
54 'subscript-descendant-numeric_literal':
55 _traverse(function(key, value, ref) { return key === ref }),
56
57 'member-child-wildcard':
58 _descend(function() { return true }),
59
60 'member-descendant-wildcard':
61 _traverse(function() { return true }),
62
63 'subscript-descendant-wildcard':
64 _traverse(function() { return true }),
65
66 'subscript-child-wildcard':
67 _descend(function() { return true }),
68
69 'subscript-child-slice': function(component, partial) {
70 if (is_array(partial.value)) {
71 var args = component.expression.value.split(':').map(_parse_nullable_int);
72 var values = partial.value.map(function(v, i) { return { value: v, path: partial.path.concat(i) } });
73 return slice.apply(null, [values].concat(args));
74 }
75 },
76
77 'subscript-child-union': function(component, partial) {
78 var results = [];
79 component.expression.value.forEach(function(component) {
80 var _component = { operation: 'subscript', scope: 'child', expression: component.expression };
81 var handler = this.resolve(_component);
82 var _results = handler(_component, partial);
83 if (_results) {
84 results = results.concat(_results);
85 }
86 }, this);
87
88 return unique(results);
89 },
90
91 'subscript-descendant-union': function(component, partial, count) {
92
93 var jp = require('..');
94 var self = this;
95
96 var results = [];
97 var nodes = jp.nodes(partial, '$..*').slice(1);
98
99 nodes.forEach(function(node) {
100 if (results.length >= count) return;
101 component.expression.value.forEach(function(component) {
102 var _component = { operation: 'subscript', scope: 'child', expression: component.expression };
103 var handler = self.resolve(_component);
104 var _results = handler(_component, node);
105 results = results.concat(_results);
106 });
107 });
108
109 return unique(results);
110 },
111
112 'subscript-child-filter_expression': function(component, partial, count) {
113
114 // slice out the expression from ?(expression)
115 var src = component.expression.value.slice(2, -1);
116 var ast = aesprim.parse(src).body[0].expression;
117
118 var passable = function(key, value) {
119 return evaluate(ast, { '@': value });
120 }
121
122 return this.descend(partial, null, passable, count);
123
124 },
125
126 'subscript-descendant-filter_expression': function(component, partial, count) {
127
128 // slice out the expression from ?(expression)
129 var src = component.expression.value.slice(2, -1);
130 var ast = aesprim.parse(src).body[0].expression;
131
132 var passable = function(key, value) {
133 return evaluate(ast, { '@': value });
134 }
135
136 return this.traverse(partial, null, passable, count);
137 },
138
139 'subscript-child-script_expression': function(component, partial) {
140 var exp = component.expression.value.slice(1, -1);
141 return eval_recurse(partial, exp, '$[{{value}}]');
142 },
143
144 'member-child-script_expression': function(component, partial) {
145 var exp = component.expression.value.slice(1, -1);
146 return eval_recurse(partial, exp, '$.{{value}}');
147 },
148
149 'member-descendant-script_expression': function(component, partial) {
150 var exp = component.expression.value.slice(1, -1);
151 return eval_recurse(partial, exp, '$..value');
152 }
153};
154
155Handlers.prototype._fns['subscript-child-string_literal'] =
156 Handlers.prototype._fns['member-child-identifier'];
157
158Handlers.prototype._fns['member-descendant-numeric_literal'] =
159 Handlers.prototype._fns['subscript-descendant-string_literal'] =
160 Handlers.prototype._fns['member-descendant-identifier'];
161
162function eval_recurse(partial, src, template) {
163
164 var jp = require('./index');
165 var ast = aesprim.parse(src).body[0].expression;
166 var value = evaluate(ast, { '@': partial.value });
167 var path = template.replace(/\{\{\s*value\s*\}\}/g, value);
168
169 var results = jp.nodes(partial.value, path);
170 results.forEach(function(r) {
171 r.path = partial.path.concat(r.path.slice(1));
172 });
173
174 return results;
175}
176
177function is_array(val) {
178 return Array.isArray(val);
179}
180
181function is_object(val) {
182 // is this a non-array, non-null object?
183 return val && !(val instanceof Array) && val instanceof Object;
184}
185
186function traverser(recurse) {
187
188 return function(partial, ref, passable, count) {
189
190 var value = partial.value;
191 var path = partial.path;
192
193 var results = [];
194
195 var descend = function(value, path) {
196
197 if (is_array(value)) {
198 value.forEach(function(element, index) {
199 if (results.length >= count) { return }
200 if (passable(index, element, ref)) {
201 results.push({ path: path.concat(index), value: element });
202 }
203 });
204 value.forEach(function(element, index) {
205 if (results.length >= count) { return }
206 if (recurse) {
207 descend(element, path.concat(index));
208 }
209 });
210 } else if (is_object(value)) {
211 this.keys(value).forEach(function(k) {
212 if (results.length >= count) { return }
213 if (passable(k, value[k], ref)) {
214 results.push({ path: path.concat(k), value: value[k] });
215 }
216 })
217 this.keys(value).forEach(function(k) {
218 if (results.length >= count) { return }
219 if (recurse) {
220 descend(value[k], path.concat(k));
221 }
222 });
223 }
224 }.bind(this);
225 descend(value, path);
226 return results;
227 }
228}
229
230function _descend(passable) {
231 return function(component, partial, count) {
232 return this.descend(partial, component.expression.value, passable, count);
233 }
234}
235
236function _traverse(passable) {
237 return function(component, partial, count) {
238 return this.traverse(partial, component.expression.value, passable, count);
239 }
240}
241
242function evaluate() {
243 try { return _evaluate.apply(this, arguments) }
244 catch (e) { }
245}
246
247function unique(results) {
248 results = results.filter(function(d) { return d })
249 return _uniq(
250 results,
251 function(r) { return r.path.map(function(c) { return String(c).replace('-', '--') }).join('-') }
252 );
253}
254
255function _parse_nullable_int(val) {
256 var sval = String(val);
257 return sval.match(/^-?[0-9]+$/) ? parseInt(sval) : null;
258}
259
260module.exports = Handlers;