UNPKG

carto

Version:

Mapnik Stylesheet Compiler

291 lines (259 loc) 11.3 kB
var tree = require('../tree'), util = require('../util'); tree.Filterset = function Filterset() { this.filters = {}; }; tree.Filterset.prototype.toObject = function(env) { var filters = []; for (var id in this.filters) { filters.push('(' + this.filters[id].toObject(env).trim() + ')'); } if (filters.length) { return { '_name': 'Filter', '_content': filters.join(' and ') }; } else { return {}; } }; tree.Filterset.prototype.toString = function() { var arr = []; for (var id in this.filters) arr.push(this.filters[id].id); return arr.sort().join('\t'); }; tree.Filterset.prototype.ev = function(env) { for (var i in this.filters) { this.filters[i].ev(env); } // Rebuild the filters dict after var subst. var result = new tree.Filterset(); for (var i2 in this.filters) { var err = result.add(this.filters[i2]); if (err) { util.error(env, { message: err, filename: this.filters[i2].filename, index: this.filters[i2].index }); throw new Error('N/A'); } } this.filters = result.filters; return this; }; tree.Filterset.prototype.clone = function() { var clone = new tree.Filterset(); for (var id in this.filters) { clone.filters[id] = this.filters[id]; } return clone; }; // Note: other has to be a tree.Filterset. tree.Filterset.prototype.cloneWith = function(other) { var additions = []; for (var id in other.filters) { var status = this.addable(other.filters[id]); // status is true, false or null. if it's null we don't fail this // clone nor do we add the filter. if (status === false) { return false; } if (status === true) { // Adding the filter will override another value. additions.push(other.filters[id]); } } // Adding the other filters doesn't make this filterset invalid, but it // doesn't add anything to it either. if (!additions.length) { return null; } // We can successfully add all filters. Now clone the filterset and add the // new rules. var clone = new tree.Filterset(); // 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 (id in this.filters) { clone.filters[id] = this.filters[id]; } // Only add new filters that actually change the filter. do { id = additions.shift(); if (id) { clone.add(id); } } while (id); return clone; }; // Returns true when the new filter can be added, false otherwise. // It can also return null, and on the other side we test for === true or // false tree.Filterset.prototype.addable = function(filter) { var key = filter.key.toString(), value = filter.val.toString(), valueIsNull = filter.val.is == 'keyword' && value == 'null'; if (value.match(/^[+-]?[0-9]+(\.[0-9]*)?([e|E][+-]?[0-9]+)?$/)) { value = parseFloat(value); } switch (filter.op) { case '=': // if there is already foo= and we're adding foo= if (this.filters[key + '='] !== undefined) { if (this.filters[key + '='].val.toString() != value) { return false; } else { return null; } } if (this.filters[key + '!=' + value] !== undefined) return false; if (this.filters[key + '>'] !== undefined && (this.filters[key + '>'].val >= value || valueIsNull)) return false; if (this.filters[key + '<'] !== undefined && (this.filters[key + '<'].val <= value || valueIsNull)) return false; if (this.filters[key + '>='] !== undefined && (this.filters[key + '>='].val > value || valueIsNull)) return false; if (this.filters[key + '<='] !== undefined && (this.filters[key + '<='].val < value || valueIsNull)) return false; return true; case '=~': return true; case '!=': if (this.filters[key + '='] !== undefined) return (this.filters[key + '='].val == value) ? false : null; if (this.filters[key + '!=' + value] !== undefined) return null; if (this.filters[key + '>'] !== undefined && (this.filters[key + '>'].val >= value || valueIsNull)) return null; if (this.filters[key + '<'] !== undefined && (this.filters[key + '<'].val <= value || valueIsNull)) return null; if (this.filters[key + '>='] !== undefined && (this.filters[key + '>='].val > value || valueIsNull)) return null; if (this.filters[key + '<='] !== undefined && (this.filters[key + '<='].val < value || valueIsNull)) return null; return true; case '>': if (this.filters[key + '='] !== undefined) { if (this.filters[key + '='].val <= value || (this.filters[key + '='].val.is == 'keyword' && this.filters[key + '='].val.toString() == 'null')) { return false; } else { return null; } } if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return false; if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val <= value) return false; if (this.filters[key + '>'] !== undefined && this.filters[key + '>'].val >= value) return null; if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return null; return true; case '>=': if (this.filters[key + '=' ] !== undefined) { if (this.filters[key + '='].val < value || (this.filters[key + '='].val.is == 'keyword' && this.filters[key + '='].val.toString() == 'null')) { return false; } else { return null; } } if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return false; if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return false; if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return null; if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val >= value) return null; return true; case '<': if (this.filters[key + '=' ] !== undefined) { if (this.filters[key + '='].val >= value || (this.filters[key + '='].val.is == 'keyword' && this.filters[key + '='].val.toString() == 'null')) { return false; } else { return null; } } if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return false; if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val >= value) return false; if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return null; if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return null; return true; case '<=': if (this.filters[key + '=' ] !== undefined) { if (this.filters[key + '='].val > value || (this.filters[key + '='].val.is == 'keyword' && this.filters[key + '='].val.toString() == 'null')) { return false; } else { return null; } } if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return false; if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return false; if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return null; if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val <= value) return null; return true; } }; // Does the new filter constitute a conflict? tree.Filterset.prototype.conflict = function(filter) { var key = filter.key.toString(), value = filter.val.toString(); if (!isNaN(parseFloat(value))) value = parseFloat(value); // if (a=b) && (a=c) // if (a=b) && (a!=b) // or (a!=b) && (a=b) if ((filter.op === '=' && this.filters[key + '='] !== undefined && value != this.filters[key + '='].val.toString()) || (filter.op === '!=' && this.filters[key + '='] !== undefined && value == this.filters[key + '='].val.toString()) || (filter.op === '=' && this.filters[key + '!='] !== undefined && value == this.filters[key + '!='].val.toString())) { return filter.toString() + ' added to ' + this.toString() + ' produces an invalid filter'; } return false; }; // Only call this function for filters that have been cleared by .addable(). tree.Filterset.prototype.add = function(filter) { var key = filter.key.toString(), op = filter.op, conflict = this.conflict(filter), numval; if (conflict) return conflict; if (op === '=') { for (var i in this.filters) { if (this.filters[i].key == key) delete this.filters[i]; } this.filters[key + '='] = filter; } else if (op === '!=') { this.filters[key + '!=' + filter.val] = filter; } else if (op === '=~') { this.filters[key + '=~' + filter.val] = filter; } else if (op === '>') { this.filters[key + '>'] = filter; } else if (op === '>=') { for (var k in this.filters) { numval = (+this.filters[k].val.toString()); if (this.filters[k].key == key && numval < filter.val) { delete this.filters[k]; } } if (this.filters[key + '!=' + filter.val] !== undefined) { delete this.filters[key + '!=' + filter.val]; filter.op = '>'; this.filters[key + '>'] = filter; } else { this.filters[key + '>='] = filter; } } else if (op === '<') { for (var l in this.filters) { numval = (+this.filters[l].val.toString()); if (this.filters[l].key == key && numval >= filter.val) { delete this.filters[l]; } } this.filters[key + '<'] = filter; } else if (op === '<=') { for (var m in this.filters) { numval = (+this.filters[m].val.toString()); if (this.filters[m].key == key && numval > filter.val) { delete this.filters[m]; } } if (this.filters[key + '!=' + filter.val] !== undefined) { delete this.filters[key + '!=' + filter.val]; filter.op = '<'; this.filters[key + '<'] = filter; } else { this.filters[key + '<='] = filter; } } };