1 | var aesprim = require('./aesprim');
|
2 | var slice = require('./slice');
|
3 | var _evaluate = require('static-eval');
|
4 | var _uniq = require('underscore').uniq;
|
5 |
|
6 | var Handlers = function() {
|
7 | return this.initialize.apply(this, arguments);
|
8 | }
|
9 |
|
10 | Handlers.prototype.initialize = function() {
|
11 | this.traverse = traverser(true);
|
12 | this.descend = traverser();
|
13 | }
|
14 |
|
15 | Handlers.prototype.keys = Object.keys;
|
16 |
|
17 | Handlers.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 |
|
26 | Handlers.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 |
|
35 | Handlers.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 |
|
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 |
|
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 |
|
155 | Handlers.prototype._fns['subscript-child-string_literal'] =
|
156 | Handlers.prototype._fns['member-child-identifier'];
|
157 |
|
158 | Handlers.prototype._fns['member-descendant-numeric_literal'] =
|
159 | Handlers.prototype._fns['subscript-descendant-string_literal'] =
|
160 | Handlers.prototype._fns['member-descendant-identifier'];
|
161 |
|
162 | function 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 |
|
177 | function is_array(val) {
|
178 | return Array.isArray(val);
|
179 | }
|
180 |
|
181 | function is_object(val) {
|
182 |
|
183 | return val && !(val instanceof Array) && val instanceof Object;
|
184 | }
|
185 |
|
186 | function 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 |
|
230 | function _descend(passable) {
|
231 | return function(component, partial, count) {
|
232 | return this.descend(partial, component.expression.value, passable, count);
|
233 | }
|
234 | }
|
235 |
|
236 | function _traverse(passable) {
|
237 | return function(component, partial, count) {
|
238 | return this.traverse(partial, component.expression.value, passable, count);
|
239 | }
|
240 | }
|
241 |
|
242 | function evaluate() {
|
243 | try { return _evaluate.apply(this, arguments) }
|
244 | catch (e) { }
|
245 | }
|
246 |
|
247 | function 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 |
|
255 | function _parse_nullable_int(val) {
|
256 | var sval = String(val);
|
257 | return sval.match(/^-?[0-9]+$/) ? parseInt(sval) : null;
|
258 | }
|
259 |
|
260 | module.exports = Handlers;
|