carto
Version:
Mapnik Stylesheet Compiler
291 lines (259 loc) • 11.3 kB
JavaScript
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;
}
}
};