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 @@ + + + + + + + + world + + + + + + +