carto
Version:
Mapnik Stylesheet Compiler
179 lines (162 loc) • 6.5 kB
JavaScript
(function(tree) {
var util = require('../util');
tree.Ruleset = function Ruleset(selectors, rules) {
this.selectors = selectors;
this.rules = rules;
// static cache of find() function
this._lookups = {};
};
tree.Ruleset.prototype = {
is: 'ruleset',
'ev': function(env) {
var i,
rule,
ruleset = new tree.Ruleset(this.selectors, this.rules.slice(0));
ruleset.root = this.root;
// push the current ruleset to the frames stack
env.frames.unshift(ruleset);
// Evaluate everything else
for (i = 0; i < ruleset.rules.length; i++) {
rule = ruleset.rules[i];
ruleset.rules[i] = rule.ev ? rule.ev(env) : rule;
}
// Pop the stack
env.frames.shift();
return ruleset;
},
match: function(args) {
return !args || args.length === 0;
},
variables: function() {
if (this._variables) { return this._variables; }
else {
return this._variables = this.rules.reduce(function(hash, r) {
if (r instanceof tree.Rule && r.variable === true) {
hash[r.name] = r;
}
return hash;
}, {});
}
},
variable: function(name) {
return this.variables()[name];
},
rulesets: function() {
if (this._rulesets) { return this._rulesets; }
else {
return this._rulesets = this.rules.filter(function(r) {
return (r instanceof tree.Ruleset);
});
}
},
find: function(selector, self) {
self = self || this;
var rules = [], match,
key = selector.toString();
if (key in this._lookups) { return this._lookups[key]; }
this.rulesets().forEach(function(rule) {
if (rule !== self) {
for (var j = 0; j < rule.selectors.length; j++) {
match = selector.match(rule.selectors[j]);
if (match) {
if (selector.elements.length > 1) {
Array.prototype.push.apply(rules, rule.find(
new tree.Selector(null, null, selector.elements.slice(1)), self));
} else {
rules.push(rule);
}
break;
}
}
}
});
return this._lookups[key] = rules;
},
// Zooms can use variables. This replaces tree.Zoom objects on selectors
// with simple bit-arrays that we can compare easily.
evZooms: function(env) {
for (var i = 0; i < this.selectors.length; i++) {
var zval = tree.Zoom.all;
for (var z = 0; z < this.selectors[i].zoom.length; z++) {
zval = zval & this.selectors[i].zoom[z].ev(env).zoom;
}
this.selectors[i].zoom = zval;
}
},
flatten: function(result, parents, env) {
var selectors = [], i, j;
if (this.selectors.length === 0) {
env.frames = env.frames.concat(this.rules);
}
// evaluate zoom variables on this object.
this.evZooms(env);
for (i = 0; i < this.selectors.length; i++) {
var child = this.selectors[i];
if (!child.filters) {
// TODO: is this internal inconsistency?
// This is an invalid filterset.
continue;
}
if (parents.length) {
for (j = 0; j < parents.length; j++) {
var parent = parents[j];
var mergedFilters = parent.filters.cloneWith(child.filters);
if (mergedFilters === null) {
// Filters could be added, but they didn't change the
// filters. This means that we only have to clone when
// the zoom levels or the attachment is different too.
if (parent.zoom === (parent.zoom & child.zoom) &&
parent.attachment === child.attachment &&
parent.elements.join() === child.elements.join()) {
selectors.push(parent);
continue;
} else {
mergedFilters = parent.filters;
}
} else if (!mergedFilters) {
// The merged filters are invalid, that means we don't
// have to clone.
continue;
}
var clone = Object.create(tree.Selector.prototype);
clone.filters = mergedFilters;
clone.zoom = parent.zoom & child.zoom;
clone.elements = parent.elements.concat(child.elements);
if (parent.attachment && child.attachment) {
clone.attachment = parent.attachment + '/' + child.attachment;
}
else clone.attachment = child.attachment || parent.attachment;
clone.conditions = parent.conditions + child.conditions;
clone.index = child.index;
selectors.push(clone);
}
} else {
selectors.push(child);
}
}
var rules = [];
for (i = 0; i < this.rules.length; i++) {
var rule = this.rules[i];
// Recursively flatten any nested rulesets
if (rule instanceof tree.Ruleset) {
rule.flatten(result, selectors, env);
} else if (rule instanceof tree.Rule) {
rules.push(rule);
} else if (rule instanceof tree.Invalid) {
util.error(env, rule);
}
}
var index = rules.length ? rules[0].index : false;
for (i = 0; i < selectors.length; i++) {
// For specificity sort, use the position of the first rule to allow
// defining attachments that are under current element as a descendant
// selector.
if (index !== false) {
selectors[i].index = index;
}
result.push(new tree.Definition(env, selectors[i], rules.slice()));
}
return result;
}
};
})(require('../tree'));