handlers.js 7.4 KB

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