diff --git a/lib/carto/parser.js b/lib/carto/parser.js
index 0a269c6..81ea1f0 100644
--- a/lib/carto/parser.js
+++ b/lib/carto/parser.js
@@ -438,16 +438,15 @@ carto.Parser = function Parser(env) {
// "milky way" 'he\'s the one!'
//
quoted: function() {
- var str;
if (input.charAt(i) !== '"' && input.charAt(i) !== "'") return;
-
- if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) {
+ var str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/);
+ if (str) {
return new tree.Quoted(str[0], str[1] || str[2]);
}
},
comparison: function() {
- var str = $(/^=|!=|<=|>=|<|>/);
+ var str = $(/^=~|=|!=|<=|>=|<|>/);
if (str) {
return str;
}
diff --git a/lib/carto/tree/directive.js b/lib/carto/tree/directive.js
index 4702bec..9fb4aba 100644
--- a/lib/carto/tree/directive.js
+++ b/lib/carto/tree/directive.js
@@ -25,9 +25,15 @@ tree.Directive.prototype = {
env.frames.shift();
return this;
},
- variable: function(name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) },
- find: function() { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) },
- rulesets: function() { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }
+ variable: function(name) {
+ return tree.Ruleset.prototype.variable.call(this.ruleset, name);
+ },
+ find: function() {
+ return tree.Ruleset.prototype.find.apply(this.ruleset, arguments);
+ },
+ rulesets: function() {
+ return tree.Ruleset.prototype.rulesets.apply(this.ruleset);
+ }
};
})(require('../tree'));
diff --git a/lib/carto/tree/expression.js b/lib/carto/tree/expression.js
index 9d20b3e..775c16f 100644
--- a/lib/carto/tree/expression.js
+++ b/lib/carto/tree/expression.js
@@ -1,6 +1,8 @@
(function(tree) {
-tree.Expression = function Expression(value) { this.value = value };
+tree.Expression = function Expression(value) {
+ this.value = value;
+};
tree.Expression.prototype = {
eval: function(env) {
if (this.value.length > 1) {
diff --git a/lib/carto/tree/filter.js b/lib/carto/tree/filter.js
index 87c84f0..17e6102 100644
--- a/lib/carto/tree/filter.js
+++ b/lib/carto/tree/filter.js
@@ -19,7 +19,7 @@ tree.Filter = function Filter(key, op, val, index, filename) {
this.val = val;
}
- if (this.op !== '=' && this.op !== '!=') {
+ if (ops[this.op][1]) {
this.val = 1 * this.val;
}
@@ -27,18 +27,19 @@ tree.Filter = function Filter(key, op, val, index, filename) {
};
-// XML-safe versions of comparators
-var opXML = {
- '<': '<',
- '>': '>',
- '=': '=',
- '!=': '!=',
- '<=': '<=',
- '>=': '>='
+// xmlsafe, numeric, suffix
+var ops = {
+ '<': [' < ', true],
+ '>': [' > ', true],
+ '=': [' = ', false],
+ '!=': [' != ', false],
+ '<=': [' <= ', true],
+ '>=': [' >= ', true],
+ '=~': ['.match(', false, ')']
};
tree.Filter.prototype.toXML = function(env) {
- if (this.op !== '=' && this.op !== '!=' && isNaN(this.val)) {
+ if (ops[this.op][1] && isNaN(this.val)) {
env.error({
message: 'Cannot use operator "' + this.op + '" with value ' + this.val,
index: this.index,
@@ -50,7 +51,7 @@ tree.Filter.prototype.toXML = function(env) {
if (this._key) var key = this._key.toString(false);
if (this._val) var val = this._val.toString(this._val.is == 'string');
- return '[' + (key || this.key) + '] ' + opXML[this.op] + ' ' + (val || this.val);
+ return '[' + (key || this.key) + ']' + ops[this.op][0] + '' + (val || this.val) + (ops[this.op][2] || '');
};
tree.Filter.prototype.toString = function() {
diff --git a/lib/carto/tree/filterset.js b/lib/carto/tree/filterset.js
index 9ba2dfe..ad73938 100644
--- a/lib/carto/tree/filterset.js
+++ b/lib/carto/tree/filterset.js
@@ -66,12 +66,14 @@ Object.defineProperty(tree.Filterset.prototype, 'cloneWith', {
// We can add the rules that are already present without going through the
// add function as a Filterset is always in it's simplest canonical form.
- for (var id in this)
+ for (var id in this) {
clone[id] = this[id];
+ }
// Only add new filters that actually change the filter.
- while (id = additions.shift())
+ while (id = additions.shift()) {
clone.add(id);
+ }
return clone;
}
@@ -98,12 +100,10 @@ Object.defineProperty(tree.Filterset.prototype, 'addable', {
case '!=':
if (key + '=' in this) return (this[key + '='].val == value) ? false : null;
if (key + '!=' + value in this) return null;
-
if (key + '>' in this && this[key + '>'].val >= value) return null;
if (key + '<' in this && this[key + '<'].val <= value) return null;
if (key + '>=' in this && this[key + '>='].val > value) return null;
if (key + '<=' in this && this[key + '<='].val < value) return null;
-
return true;
case '>':
@@ -151,9 +151,11 @@ Object.defineProperty(tree.Filterset.prototype, 'add', {
switch (filter.op) {
case '=':
- for (var id in this)
- if (this[id].key == key)
+ for (var id in this) {
+ if (this[id].key == key) {
delete this[id];
+ }
+ }
this[key + '='] = filter;
break;
@@ -161,17 +163,28 @@ Object.defineProperty(tree.Filterset.prototype, 'add', {
this[key + '!=' + filter.val] = filter;
break;
+ case '=~':
+ this[key + '=~' + filter.val] = filter;
+ break;
+
case '>':
- for (var id in this)
- if (this[id].key == key && this[id].val <= filter.val)
+ // If there are other filters that are also >
+ // but are less than this one, they don't matter, so
+ // remove them.
+ for (var id in this) {
+ if (this[id].key == key && this[id].val <= filter.val) {
delete this[id];
+ }
+ }
this[key + '>'] = filter;
break;
case '>=':
- for (var id in this)
- if (this[id].key == key && this[id].val < filter.val)
+ for (var id in this) {
+ if (this[id].key == key && this[id].val < filter.val) {
delete this[id];
+ }
+ }
if (key + '!=' + filter.val in this) {
delete this[key + '!=' + filter.val];
filter.op = '>';
@@ -183,16 +196,20 @@ Object.defineProperty(tree.Filterset.prototype, 'add', {
break;
case '<':
- for (var id in this)
- if (this[id].key == key && this[id].val >= filter.val)
+ for (var id in this) {
+ if (this[id].key == key && this[id].val >= filter.val) {
delete this[id];
+ }
+ }
this[key + '<'] = filter;
break;
case '<=':
- for (var id in this)
- if (this[id].key == key && this[id].val > filter.val)
+ for (var id in this) {
+ if (this[id].key == key && this[id].val > filter.val) {
delete this[id];
+ }
+ }
if (key + '!=' + filter.val in this) {
delete this[key + '!=' + filter.val];
filter.op = '<';
diff --git a/lib/carto/tree/fontset.js b/lib/carto/tree/fontset.js
index 5f2c60b..458cd38 100644
--- a/lib/carto/tree/fontset.js
+++ b/lib/carto/tree/fontset.js
@@ -26,13 +26,13 @@ tree.FontSet = function FontSet(env, fonts) {
};
tree.FontSet.prototype.toXML = function(env) {
- return '\n'
- + this.fonts.map(function(f) {
+ return '\n' +
+ this.fonts.map(function(f) {
return ' ';
- }).join('\n')
- + '\n'
+ }).join('\n') +
+ '\n';
};
})(require('../tree'));
diff --git a/lib/carto/tree/keyword.js b/lib/carto/tree/keyword.js
index 7897f48..9501ae7 100644
--- a/lib/carto/tree/keyword.js
+++ b/lib/carto/tree/keyword.js
@@ -10,8 +10,8 @@ tree.Keyword = function Keyword(value) {
this.is = special[value] ? special[value] : 'keyword';
};
tree.Keyword.prototype = {
- eval: function() { return this },
- toString: function() { return this.value }
+ eval: function() { return this; },
+ toString: function() { return this.value; }
};
})(require('../tree'));
diff --git a/lib/carto/tree/zoom.js b/lib/carto/tree/zoom.js
index 62b5097..ffdac56 100644
--- a/lib/carto/tree/zoom.js
+++ b/lib/carto/tree/zoom.js
@@ -4,11 +4,11 @@ var tree = require('../tree');
// and stores them as bit-sequences so that they can be combined,
// inverted, and compared quickly.
tree.Zoom = function(op, value, index) {
- value = parseInt(value);
+ value = parseInt(value, 10);
if (value > tree.Zoom.maxZoom || value < 0) {
throw {
- message: 'Only zoom levels between 0 and '
- + tree.Zoom.maxZoom + ' supported.',
+ message: 'Only zoom levels between 0 and ' +
+ tree.Zoom.maxZoom + ' supported.',
index: index
};
}
@@ -81,14 +81,14 @@ tree.Zoom.toXML = function(zoom) {
var start = null, end = null;
for (var i = 0; i <= tree.Zoom.maxZoom; i++) {
if (zoom & (1 << i)) {
- if (start == null) start = i;
+ if (start === null) start = i;
end = i;
}
}
- if (start > 0) conditions.push(' '
- + tree.Zoom.ranges[start] + '\n');
- if (end < 22) conditions.push(' '
- + tree.Zoom.ranges[end + 1] + '\n');
+ if (start > 0) conditions.push(' ' +
+ tree.Zoom.ranges[start] + '\n');
+ if (end < 22) conditions.push(' ' +
+ tree.Zoom.ranges[end + 1] + '\n');
}
return conditions;
};
diff --git a/test/rendering/regex.mml b/test/rendering/regex.mml
new file mode 100644
index 0000000..4133c0b
--- /dev/null
+++ b/test/rendering/regex.mml
@@ -0,0 +1,14 @@
+{
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Stylesheet": [
+ "regex.mss"
+ ],
+ "Layer": [{
+ "name": "world",
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Datasource": {
+ "file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
+ "type": "shape"
+ }
+ }]
+}
diff --git a/test/rendering/regex.mss b/test/rendering/regex.mss
new file mode 100644
index 0000000..0ebdb96
--- /dev/null
+++ b/test/rendering/regex.mss
@@ -0,0 +1,5 @@
+#world[ISO =~ "U*"] {
+ polygon-fill: #FFF;
+ line-color:#F00;
+ line-width: 0.5;
+}
diff --git a/test/rendering/regex.result b/test/rendering/regex.result
new file mode 100644
index 0000000..a9d8bab
--- /dev/null
+++ b/test/rendering/regex.result
@@ -0,0 +1,22 @@
+
+
+