Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Adding ol.Expression

This simple expression constructor will be used for symbolizer properties and the layer will generate symbolizer literals for rendering by evaluating any expressions with a feature as the this argument and feature attributes as the scope.  This allows generating labels that concatenate multiple attribute values together or displaying point symbols that are sized according to a population attribute divided by an area attribute, for example.

This implementation will not work in environments where the content security policy disallows the use of the Function constructor.  This is the case on browser extensions.  A more content-security-policy-friendly implementation would be to come up with a restricted grammar and write a lex/parser.  This is the road I started down, but this verison is far less code, and I think the security policy limitations are minor at this point.  This version will always be faster/lighter than a parser, so one is written in the future, it should only be pulled in where content security policy mandates it.
  • Loading branch information...
commit be255ed6c7b0499e9b6c2202d68bcaa0a6f01103 1 parent 59a203b
@tschaub tschaub authored
Showing with 121 additions and 0 deletions.
  1. +43 −0 src/ol/expression.js
  2. +78 −0 test/spec/ol/expression.test.js
View
43 src/ol/expression.js
@@ -0,0 +1,43 @@
+goog.provide('ol.Expression');
+
+
+
+/**
+ * @constructor
+ * @param {string} source Expression to be evaluated.
+ */
+ol.Expression = function(source) {
+
+ /**
+ * @type {string}
+ * @private
+ */
+ this.source_ = source;
+
+};
+
+
+/**
+ * Evaluate the expression and return the result.
+ *
+ * @param {Object=} opt_thisArg Object to use as this when evaluating the
+ * expression. If not provided, the global object will be used.
+ * @param {Object=} opt_scope Evaluation scope. All properties of this object
+ * will be available as variables when evaluating the expression. If not
+ * provided, the global object will be used.
+ * @return {*} Result of the expression.
+ */
+ol.Expression.prototype.evaluate = function(opt_thisArg, opt_scope) {
+ var thisArg = goog.isDef(opt_thisArg) ? opt_thisArg : goog.global,
+ scope = goog.isDef(opt_scope) ? opt_scope : goog.global,
+ names = [],
+ values = [];
+
+ for (var name in scope) {
+ names.push(name);
+ values.push(scope[name]);
+ }
+
+ var evaluator = new Function(names.join(','), 'return ' + this.source_);
+ return evaluator.apply(thisArg, values);
+};
View
78 test/spec/ol/expression.test.js
@@ -0,0 +1,78 @@
+goog.provide('ol.test.Expression');
+
+describe('ol.Expression', function() {
+
+ describe('constructor', function() {
+ it('creates an expression', function() {
+ var exp = new ol.Expression('foo');
+ expect(exp).toBeA(ol.Expression);
+ });
+ });
+
+ describe('#evaluate()', function() {
+
+ it('evaluates and returns the result', function() {
+ // test cases here with unique values only (lack of messages in expect)
+ var cases = [{
+ source: '42', result: 42
+ }, {
+ source: '10 + 10', result: 20
+ }, {
+ source: '"a" + "b"', result: 'ab'
+ }, {
+ source: 'Math.floor(Math.PI)', result: 3
+ }, {
+ source: 'ol', result: ol
+ }, {
+ source: 'this', result: goog.global
+ }];
+
+ var c, exp;
+ for (var i = 0, ii = cases.length; i < ii; ++i) {
+ c = cases[i];
+ exp = new ol.Expression(c.source);
+ expect(exp.evaluate()).toBe(c.result);
+ }
+ });
+
+ it('accepts an optional this argument', function() {
+ function Thing() {
+ this.works = true;
+ };
+
+ var exp = new ol.Expression('this.works ? "yes" : "no"');
+ expect(exp.evaluate(new Thing())).toBe('yes');
+ expect(exp.evaluate({})).toBe('no');
+ });
+
+ it('accepts an optional scope argument', function() {
+ var exp;
+ var scope = {
+ greeting: 'hello world',
+ punctuation: '!',
+ pick: function(array, index) {
+ return array[index];
+ }
+ };
+
+ // access two members in the scope
+ exp = new ol.Expression('greeting + punctuation');
+ expect(exp.evaluate({}, scope)).toBe('hello world!');
+
+ // call a function in the scope
+ exp = new ol.Expression(
+ 'pick([10, 42, "chicken"], 2) + Math.floor(Math.PI)');
+ expect(exp.evaluate({}, scope)).toBe('chicken3');
+
+ });
+
+ it('throws on error', function() {
+ var exp = new ol.Expression('@*)$(&');
+ expect(function() {exp.evaluate()}).toThrow();
+ });
+
+ });
+
+});
+
+goog.require('ol.Expression');
Please sign in to comment.
Something went wrong with that request. Please try again.