From def6de429d607d488099652e82bdc3f05d848574 Mon Sep 17 00:00:00 2001 From: nebulon42 Date: Fri, 2 Jun 2017 20:21:28 +0200 Subject: [PATCH] add ability to define variables within selectors and filters, ref #461 --- lib/carto/parser.js | 19 +--- lib/carto/tree.js | 28 +++++ lib/carto/tree/call.js | 9 ++ lib/carto/tree/color.js | 5 + lib/carto/tree/definition.js | 38 ++++++- lib/carto/tree/dimension.js | 4 + lib/carto/tree/expression.js | 19 ++++ lib/carto/tree/field.js | 7 ++ lib/carto/tree/filter.js | 14 +++ lib/carto/tree/filterset.js | 14 ++- lib/carto/tree/fontset.js | 12 +++ lib/carto/tree/imagefilter.js | 15 +++ lib/carto/tree/invalid.js | 5 + lib/carto/tree/keyword.js | 7 ++ lib/carto/tree/literal.js | 8 ++ lib/carto/tree/operation.js | 16 ++- lib/carto/tree/quoted.js | 8 ++ lib/carto/tree/rule.js | 22 +++- lib/carto/tree/ruleset.js | 22 +++- lib/carto/tree/selector.js | 48 ++++++++- lib/carto/tree/url.js | 7 ++ lib/carto/tree/value.js | 18 ++++ lib/carto/tree/variable.js | 134 ++++++++++++++++++++---- lib/carto/tree/zoom.js | 11 ++ test/rendering/issue_461.mml | 10 ++ test/rendering/issue_461.mss | 10 ++ test/rendering/issue_461.result | 18 ++++ test/specificity/classes.result | 14 +-- test/specificity/demo.result | 16 +-- test/specificity/filters_and_ids.result | 12 +-- test/specificity/issue60.result | 8 +- 31 files changed, 501 insertions(+), 77 deletions(-) create mode 100644 test/rendering/issue_461.mml create mode 100644 test/rendering/issue_461.mss create mode 100644 test/rendering/issue_461.result diff --git a/lib/carto/parser.js b/lib/carto/parser.js index 22b4d9624..33076a306 100644 --- a/lib/carto/parser.js +++ b/lib/carto/parser.js @@ -213,28 +213,11 @@ carto.Parser = function Parser(env) { // call populates Invalid-caused errors var definitions = this.flatten([], [], env); - definitions.sort(specificitySort); + definitions.sort(tree.specificitySort); return definitions; }; })(); - // Sort rules by specificity: this function expects selectors to be - // split already. - // - // Written to be used as a .sort(Function); - // argument. - // - // [1, 0, 0, 467] > [0, 0, 1, 520] - var specificitySort = function(a, b) { - var as = a.specificity; - var bs = b.specificity; - - if (as[0] != bs[0]) return bs[0] - as[0]; - if (as[1] != bs[1]) return bs[1] - as[1]; - if (as[2] != bs[2]) return bs[2] - as[2]; - return bs[3] - as[3]; - }; - return root; }, diff --git a/lib/carto/tree.js b/lib/carto/tree.js index a8d6db986..02db67535 100644 --- a/lib/carto/tree.js +++ b/lib/carto/tree.js @@ -8,3 +8,31 @@ module.exports.find = function (obj, fun) { } return null; }; + +// Sort rules by specificity: this function expects selectors to be +// split already. +// +// Specificity: [ID, Class, Filters, Zoom, Position in document] +// +// Written to be used as a .sort(Function); +// argument. +// +// [1, 0, 0, 467] > [0, 0, 1, 520] +module.exports.specificitySort = function (a, b) { + var as = a.specificity; + var bs = b.specificity; + + if (as[0] != bs[0]) { // ID + return bs[0] - as[0]; + } + if (as[1] != bs[1]) { // Class + return bs[1] - as[1]; + } + if (as[2] != bs[2]) { // Filters + return bs[2] - as[2]; + } + if (as[3] != -1 && bs[3] != -1 && as[3] != bs[3]) { // Zoom + return bs[4] - as[4]; + } + return bs[4] - as[4]; // Position in document +}; diff --git a/lib/carto/tree/call.js b/lib/carto/tree/call.js index 2b917edf0..e24129b94 100644 --- a/lib/carto/tree/call.js +++ b/lib/carto/tree/call.js @@ -7,6 +7,7 @@ tree.Call = function Call(name, args, filename, index) { this.args = args; this.filename = filename; this.index = index; + this.rule = null; }; tree.Call.prototype = { @@ -105,6 +106,14 @@ tree.Call.prototype = { } else { return this.name; } + }, + setRule: function (rule) { + this.rule = rule; + _.forEach(this.args, function (a) { + if (typeof a.setRule === 'function') { + a.setRule(rule); + } + }); } }; diff --git a/lib/carto/tree/color.js b/lib/carto/tree/color.js index 8165a3557..1c59b8610 100644 --- a/lib/carto/tree/color.js +++ b/lib/carto/tree/color.js @@ -29,6 +29,7 @@ tree.Color = function Color(hsl, a, perceptual) { } else { this.perceptual = false; } + this.rule = null; }; tree.Color.prototype = { @@ -149,6 +150,10 @@ tree.Color.prototype = { round: function(value, decimals) { return Number(Math.round(value+'e'+decimals)+'e-'+decimals); + }, + + setRule: function (rule) { + this.rule = rule; } }; diff --git a/lib/carto/tree/definition.js b/lib/carto/tree/definition.js index 78be008d5..50d2aca63 100644 --- a/lib/carto/tree/definition.js +++ b/lib/carto/tree/definition.js @@ -9,7 +9,9 @@ var assert = require('assert'), // } // // The selector can have filters -tree.Definition = function Definition(selector, rules) { +tree.Definition = function Definition(selector, rules, env) { + var that = this; + this.elements = selector.elements; assert.ok(selector.filters instanceof tree.Filterset); this.rules = rules; @@ -19,10 +21,42 @@ tree.Definition = function Definition(selector, rules) { this.rules[i].zoom = selector.zoom; this.ruleIndex[this.rules[i].updateID()] = true; } + this.filters = selector.filters; + if (this.filters) { + if (_.isArray(this.filters)) { + _.forEach(this.filters, function (f) { + if (typeof f.setRule === 'function') { + f.setRule(that); + } + }); + } + else { + if (typeof this.filters.setRule === 'function') { + this.filters.setRule(that); + } + } + } + this.zoom = selector.zoom; + + if (this.zoom) { + if (_.isArray(this.zoom)) { + _.forEach(this.zoom, function (f) { + if (typeof f.setRule === 'function') { + f.setRule(that); + } + }); + } + else { + if (typeof this.zoom.setRule === 'function') { + this.zoom.setRule(that); + } + } + } + this.attachment = selector.attachment || '__default__'; - this.specificity = selector.specificity(); + this.specificity = selector.specificity(env); this.matchCount = 0; // special handling for Map selector diff --git a/lib/carto/tree/dimension.js b/lib/carto/tree/dimension.js index 4d1e70707..4aee932cf 100644 --- a/lib/carto/tree/dimension.js +++ b/lib/carto/tree/dimension.js @@ -10,6 +10,7 @@ tree.Dimension = function Dimension(value, unit, index, filename) { this.unit = unit || null; this.filename = filename; this.index = index; + this.rule = null; }; tree.Dimension.prototype = { @@ -96,6 +97,9 @@ tree.Dimension.prototype = { //here the operands are either the same (% or undefined or px), or one is undefined and the other is px return new tree.Dimension(tree.operate(op, this.value, other.value), this.unit || other.unit, this.index, this.filename); + }, + setRule: function (rule) { + this.rule = rule; } }; diff --git a/lib/carto/tree/expression.js b/lib/carto/tree/expression.js index 6060914a7..affd315c7 100644 --- a/lib/carto/tree/expression.js +++ b/lib/carto/tree/expression.js @@ -1,7 +1,10 @@ (function(tree) { +var _ = require('lodash'); + tree.Expression = function Expression(value) { this.value = value; + this.rule = null; }; tree.Expression.prototype = { @@ -20,6 +23,22 @@ tree.Expression.prototype = { return this.value.map(function(e) { return e.toString(env); }).join(' '); + }, + + setRule: function (rule) { + this.rule = rule; + if (_.isArray(this.value)) { + _.forEach(this.value, function (v) { + if (typeof v.setRule === 'function') { + v.setRule(rule); + } + }); + } + else { + if (typeof this.value.setRule === 'function') { + this.value.setRule(rule); + } + } } }; diff --git a/lib/carto/tree/field.js b/lib/carto/tree/field.js index a98b904e1..7cedf414a 100644 --- a/lib/carto/tree/field.js +++ b/lib/carto/tree/field.js @@ -2,6 +2,7 @@ tree.Field = function Field(content) { this.value = content || ''; + this.rule = null; }; tree.Field.prototype = { @@ -11,6 +12,12 @@ tree.Field.prototype = { }, 'ev': function() { return this; + }, + setRule: function (rule) { + this.rule = rule; + if (typeof this.value.setRule === 'function') { + this.value.setRule(rule); + } } }; diff --git a/lib/carto/tree/filter.js b/lib/carto/tree/filter.js index 8e24f9b64..007407030 100644 --- a/lib/carto/tree/filter.js +++ b/lib/carto/tree/filter.js @@ -8,6 +8,7 @@ tree.Filter = function Filter(key, op, val, index, filename) { this.val = val; this.index = index; this.filename = filename; + this.rule = null; this.id = this.key + this.op + this.val; }; @@ -67,4 +68,17 @@ tree.Filter.prototype.toString = function() { return '[' + this.id + ']'; }; +tree.Filter.prototype.setRule = function (rule) { + this.rule = rule; + if (typeof this.key.setRule === 'function') { + this.key.setRule(rule); + } + if (typeof this.op.setRule === 'function') { + this.op.setRule(rule); + } + if (typeof this.val.setRule === 'function') { + this.val.setRule(rule); + } +} + })(require('../tree')); diff --git a/lib/carto/tree/filterset.js b/lib/carto/tree/filterset.js index 0c3f4e593..489227c3f 100644 --- a/lib/carto/tree/filterset.js +++ b/lib/carto/tree/filterset.js @@ -1,8 +1,10 @@ var tree = require('../tree'), - util = require('../util'); + util = require('../util'), + _ = require('lodash'); tree.Filterset = function Filterset() { this.filters = {}; + this.rule = null; }; tree.Filterset.prototype.toXML = function(env) { @@ -46,6 +48,7 @@ tree.Filterset.prototype.ev = function(env) { tree.Filterset.prototype.clone = function() { var clone = new tree.Filterset(); + clone.rule = this.rule; for (var id in this.filters) { clone.filters[id] = this.filters[id]; } @@ -285,3 +288,12 @@ tree.Filterset.prototype.add = function(filter, env) { // eslint-disable-line } } }; + +tree.Filterset.prototype.setRule = function (rule) { + this.rule = rule; + _.forEach(this.filters, function (f) { + if (typeof f.setRule === 'function') { + f.setRule(rule); + } + }); +} diff --git a/lib/carto/tree/fontset.js b/lib/carto/tree/fontset.js index abbd9e54c..575a60d05 100644 --- a/lib/carto/tree/fontset.js +++ b/lib/carto/tree/fontset.js @@ -1,5 +1,7 @@ (function(tree) { +var _ = require('lodash'); + tree._flattenFontArray = function (fonts) { var result = []; @@ -29,6 +31,7 @@ tree._getFontSet = function(env, fonts) { tree.FontSet = function FontSet(env, fonts) { this.fonts = fonts; this.name = 'fontset-' + env.effects.length; + this.rule = null; }; tree.FontSet.prototype.toXML = function(env) { // eslint-disable-line @@ -41,4 +44,13 @@ tree.FontSet.prototype.toXML = function(env) { // eslint-disable-line '\n'; }; +tree.FontSet.prototype.setRule = function (rule) { + this.rule = rule; + _.forEach(this.fonts, function (f) { + if (typeof f.setRule === 'function') { + f.setRule(rule); + } + }); +} + })(require('../tree')); diff --git a/lib/carto/tree/imagefilter.js b/lib/carto/tree/imagefilter.js index 99a1fe5ac..130cd1354 100644 --- a/lib/carto/tree/imagefilter.js +++ b/lib/carto/tree/imagefilter.js @@ -1,8 +1,11 @@ (function(tree) { +var _ = require('lodash'); + tree.ImageFilter = function ImageFilter(filter, args) { this.filter = filter; this.args = args || null; + this.rule = null; }; tree.ImageFilter.prototype = { @@ -18,5 +21,17 @@ tree.ImageFilter.prototype = { } }; +tree.ImageFilter.prototype.setRule = function (rule) { + this.rule = rule; + if (typeof this.filter.setRule === 'function') { + this.filter.setRule(rule); + } + _.forEach(this.args, function (a) { + if (typeof a.setRule === 'function') { + a.setRule(rule); + } + }); +} + })(require('../tree')); diff --git a/lib/carto/tree/invalid.js b/lib/carto/tree/invalid.js index ad37a7f61..9fb97cc5c 100644 --- a/lib/carto/tree/invalid.js +++ b/lib/carto/tree/invalid.js @@ -7,6 +7,7 @@ tree.Invalid = function Invalid(chunk, index, message, filename) { this.index = index; this.message = message || "Invalid code: " + this.chunk; this.filename = filename; + this.rule = null; }; tree.Invalid.prototype.is = 'invalid'; @@ -22,4 +23,8 @@ tree.Invalid.prototype.ev = function(env) { is: 'undefined' }; }; + +tree.Invalid.prototype.setRule = function (rule) { + this.rule = rule; +}; })(require('../tree')); diff --git a/lib/carto/tree/keyword.js b/lib/carto/tree/keyword.js index ed317717d..be6559536 100644 --- a/lib/carto/tree/keyword.js +++ b/lib/carto/tree/keyword.js @@ -8,10 +8,17 @@ tree.Keyword = function Keyword(value) { 'false': 'boolean' }; this.is = special[value] ? special[value] : 'keyword'; + this.rule = null; }; tree.Keyword.prototype = { ev: function() { return this; }, toString: function() { return this.value; } }; +tree.Keyword.prototype.setRule = function (rule) { + this.rule = rule; + if (typeof this.value.setRule === 'function') { + this.value.setRule(rule); + } +} })(require('../tree')); diff --git a/lib/carto/tree/literal.js b/lib/carto/tree/literal.js index 5fb6968e4..3a29f61b5 100644 --- a/lib/carto/tree/literal.js +++ b/lib/carto/tree/literal.js @@ -6,6 +6,7 @@ tree.Literal = function Field(content) { this.value = content || ''; this.is = 'field'; + this.rule = null; }; tree.Literal.prototype = { @@ -17,4 +18,11 @@ tree.Literal.prototype = { } }; +tree.Literal.prototype.setRule = function (rule) { + this.rule = rule; + if (typeof this.value.setRule === 'function') { + this.value.setRule(rule); + } +} + })(require('../tree')); diff --git a/lib/carto/tree/operation.js b/lib/carto/tree/operation.js index 473d785e7..9e2617eae 100644 --- a/lib/carto/tree/operation.js +++ b/lib/carto/tree/operation.js @@ -2,13 +2,15 @@ // like 2 + 1. (function(tree) { -var util = require('../util'); +var util = require('../util'), + _ = require('lodash'); tree.Operation = function Operation(op, operands, index, filename) { this.op = op.trim(); this.operands = operands; this.index = index; this.filename = filename; + this.rule = null; }; tree.Operation.prototype.is = 'operation'; @@ -89,6 +91,18 @@ tree.Operation.prototype.toString = function () { return this.operands[0].toString() + this.op + this.operands[1].toString(); }; +tree.Operation.prototype.setRule = function (rule) { + this.rule = rule; + if (typeof this.op.setRule === 'function') { + this.op.setRule(rule); + } + _.forEach(this.operands, function (o) { + if (typeof o.setRule === 'function') { + o.setRule(rule); + } + }); +} + tree.operate = function(op, a, b) { switch (op) { case '+': return a + b; diff --git a/lib/carto/tree/quoted.js b/lib/carto/tree/quoted.js index 0d3045e1e..956da9679 100644 --- a/lib/carto/tree/quoted.js +++ b/lib/carto/tree/quoted.js @@ -2,6 +2,7 @@ tree.Quoted = function Quoted(content) { this.value = content || ''; + this.rule = null; }; tree.Quoted.prototype = { @@ -24,6 +25,13 @@ tree.Quoted.prototype = { operate: function(env, op, other) { return new tree.Quoted(tree.operate(op, this.toString(), other.toString(this.contains_field))); + }, + + setRule: function (rule) { + this.rule = rule; + if (typeof this.value.setRule === 'function') { + this.value.setRule(rule); + } } }; diff --git a/lib/carto/tree/rule.js b/lib/carto/tree/rule.js index c2d9a0719..75117c446 100644 --- a/lib/carto/tree/rule.js +++ b/lib/carto/tree/rule.js @@ -11,10 +11,23 @@ tree.Rule = function Rule(name, value, index, filename) { this.instance = parts.length ? parts[0] : '__default__'; this.value = (value instanceof tree.Value) ? value : new tree.Value([value]); + + if (typeof this.value.setRule === 'function') { + this.value.setRule(this); + } + this.index = index; - this.symbolizer = tree.Reference.symbolizer(this.name); this.filename = filename; this.variable = (name.charAt(0) === '@'); + + if (this.variable) { + this.symbolizer = '*'; + } + else { + this.symbolizer = tree.Reference.symbolizer(this.name); + } + + this.parent = null; }; tree.Rule.prototype.is = 'rule'; @@ -28,6 +41,7 @@ tree.Rule.prototype.clone = function() { clone.symbolizer = this.symbolizer; clone.filename = this.filename; clone.variable = this.variable; + clone.selectors = this.selectors; return clone; }; @@ -42,7 +56,7 @@ tree.Rule.prototype.toString = function() { tree.Rule.prototype.validate = function (env) { var valid = true; - if (!tree.Reference.validSelector(this.name)) { + if (!tree.Reference.validSelector(this.name) && !this.variable) { var mean = getMean(this.name); var mean_message = ''; if (mean[0][1] < 3) { @@ -83,7 +97,7 @@ tree.Rule.prototype.validate = function (env) { if ((this.value instanceof tree.Value) && !tree.Reference.validValue(env, this.name, this.value)) { - if (!tree.Reference.selector(this.name)) { + if (!tree.Reference.selector(this.name) && !this.variable) { valid = false; util.error(env, { message: 'Unrecognized property: ' + @@ -91,7 +105,7 @@ tree.Rule.prototype.validate = function (env) { index: this.index, filename: this.filename }); - } else { + } else if (!this.variable) { var typename; if (tree.Reference.selector(this.name).validate) { typename = tree.Reference.selector(this.name).validate; diff --git a/lib/carto/tree/ruleset.js b/lib/carto/tree/ruleset.js index 33f4f4316..b0f4a4bc4 100644 --- a/lib/carto/tree/ruleset.js +++ b/lib/carto/tree/ruleset.js @@ -3,8 +3,19 @@ var util = require('../util'); tree.Ruleset = function Ruleset(selectors, rules) { + var that = this; + this.selectors = selectors; this.rules = rules; + this.rules.forEach(function (r) { + if (r instanceof tree.Ruleset) { + r.parent = that; + } + if (r instanceof tree.Rule) { + r.parent = that; + } + }); + this.parent = null; // static cache of find() function this._lookups = {}; }; @@ -93,9 +104,12 @@ tree.Ruleset.prototype = { }, flatten: function(result, parents, env) { var selectors = [], i, j; - if (this.selectors.length === 0) { - env.frames = env.frames.concat(this.rules); - } + // add variables to env.frames + this.rules.forEach(function (r) { + if (r.variable) { + env.frames.push(r); + } + }); // evaluate zoom variables on this object. this.evZooms(env); for (i = 0; i < this.selectors.length; i++) { @@ -169,7 +183,7 @@ tree.Ruleset.prototype = { if (index !== false) { selectors[i].index = index; } - result.push(new tree.Definition(selectors[i], rules.slice())); + result.push(new tree.Definition(selectors[i], rules.slice(), env)); } return result; diff --git a/lib/carto/tree/selector.js b/lib/carto/tree/selector.js index 76fb5daa3..843bd5508 100644 --- a/lib/carto/tree/selector.js +++ b/lib/carto/tree/selector.js @@ -1,26 +1,66 @@ (function(tree) { +var _ = require('lodash'); + tree.Selector = function Selector(filters, zoom, elements, attachment, conditions, index) { + var that = this; + this.elements = elements || []; this.attachment = attachment; this.filters = filters || {}; this.zoom = typeof zoom !== 'undefined' ? zoom : tree.Zoom.all; this.conditions = conditions; this.index = index; + + if (this.filters) { + if (_.isArray(this.filters)) { + _.forEach(this.filters, function (f) { + if (typeof f.setRule === 'function') { + f.setRule(that); + } + }); + } + else { + if (typeof this.filters.setRule === 'function') { + this.filters.setRule(that); + } + } + } + + if (this.zoom) { + if (_.isArray(this.zoom)) { + _.forEach(this.zoom, function (f) { + if (typeof f.setRule === 'function') { + f.setRule(that); + } + }); + } + else { + if (typeof this.zoom.setRule === 'function') { + this.zoom.setRule(that); + } + } + } }; // Determine the specificity of this selector // based on the specificity of its elements - calling // Element.specificity() in order to do so // -// [ID, Class, Filters, Position in document] -tree.Selector.prototype.specificity = function() { +// [ID, Class, Filters, Zoom, Position in document] +tree.Selector.prototype.specificity = function(env) { + var zoomVal = -1; + // if we would evaluate zooms here we would enter an infinite loop + if (!_.isArray(this.zoom)) { + zoomVal = this.zoom; + } + return this.elements.reduce(function(memo, e) { - var spec = e.specificity(); + var spec = e.specificity(env); memo[0] += spec[0]; memo[1] += spec[1]; return memo; - }, [0, 0, this.conditions, this.index]); + }, [0, 0, this.conditions, zoomVal, this.index]); }; })(require('../tree')); diff --git a/lib/carto/tree/url.js b/lib/carto/tree/url.js index fc8fdb740..76abf13d0 100644 --- a/lib/carto/tree/url.js +++ b/lib/carto/tree/url.js @@ -3,6 +3,7 @@ tree.URL = function URL(val, paths) { this.value = val; this.paths = paths; + this.rule = null; }; tree.URL.prototype = { @@ -12,6 +13,12 @@ tree.URL.prototype = { }, ev: function(ctx) { return new tree.URL(this.value.ev(ctx), this.paths); + }, + setRule: function (rule) { + this.rule = rule; + if (typeof this.value.setRule === 'function') { + this.value.setRule(rule); + } } }; diff --git a/lib/carto/tree/value.js b/lib/carto/tree/value.js index 5fb0a39ff..793281c92 100644 --- a/lib/carto/tree/value.js +++ b/lib/carto/tree/value.js @@ -1,7 +1,10 @@ (function(tree) { +var _ = require('lodash'); + tree.Value = function Value(value) { this.value = value; + this.rule = null; }; tree.Value.prototype = { @@ -26,6 +29,21 @@ tree.Value.prototype = { else obj.value = this.value; obj.is = this.is; return obj; + }, + setRule: function (rule) { + this.rule = rule; + if (_.isArray(this.value)) { + _.forEach(this.value, function (v) { + if (typeof v.setRule === 'function') { + v.setRule(rule); + } + }); + } + else { + if (typeof this.value.setRule === 'function') { + this.value.setRule(rule); + } + } } }; diff --git a/lib/carto/tree/variable.js b/lib/carto/tree/variable.js index abacb820e..6dc5bbc40 100644 --- a/lib/carto/tree/variable.js +++ b/lib/carto/tree/variable.js @@ -1,26 +1,121 @@ (function(tree) { -var util = require('../util'); + var util = require('../util'), + _ = require('lodash'); -tree.Variable = function Variable(name, index, filename) { - this.name = name; - this.index = index; - this.filename = filename; -}; + var findSelector = function (ruleset) { + if (ruleset.selectors.length && ruleset.selectors[0].elements.length) { + return ruleset.selectors[0]; + } + else { + if (ruleset.parent) { + return findSelector(ruleset.parent); + } + return null; + } + }; + + var findFilter = function (ruleset) { + if (ruleset.selectors.length && ruleset.selectors[0].filters) { + return ruleset.selectors[0].filters; + } + else { + if (ruleset.parent) { + return findFilter(ruleset.parent); + } + return null; + } + }; -tree.Variable.prototype = { - is: 'variable', - toString: function() { + tree.Variable = function Variable(name, index, filename) { + this.name = name; + this.index = index; + this.filename = filename; + this.rule = null; + }; + + tree.Variable.prototype.is = 'variable'; + + tree.Variable.prototype.toString = function() { return this.name; - }, - ev: function(env) { + }; + + tree.Variable.prototype.ev = function (env) { + var that = this, + variableDefs = [], + ruleSpecificity = null; + if (this._css) return this._css; - var thisframe = env.frames.filter(function(f) { - return f.name == this.name; - }.bind(this)); - if (thisframe.length) { - return thisframe[thisframe.length - 1].value.ev(env); + _.forEach(_.filter(env.frames, function (f) { + return f.name == that.name; + }), function (f) { + var selector = findSelector(f.parent), + specificity = [0, 0, 0, tree.Zoom.all, f.index], + parentSelectorSpecificity = null; + + if (f.parent.selectors.length) { + parentSelectorSpecificity = f.parent.selectors[0].specificity(env); + } + + if (selector) { + selector = selector.elements; + specificity = selector[0].specificity(env); + if (specificity.length == 2 && parentSelectorSpecificity) { + specificity = [ + specificity[0], + specificity[1], + parentSelectorSpecificity[2], + parentSelectorSpecificity[3], + parentSelectorSpecificity[4] + ]; + } + } + + variableDefs.push({ + selector: selector, + filter: findFilter(f.parent), + specificity: specificity, + rule: f + }); + }); + if (variableDefs.length) { + variableDefs.sort(tree.specificitySort); + var rSpec = null; + + if (this.rule instanceof tree.Selector || + this.rule instanceof tree.Definition) { + if (typeof this.rule.specificity === 'function') { + rSpec = this.rule.specificity(env); + } + else { + rSpec = this.rule.specificity; + } + + ruleSpecificity = { + specificity: rSpec + }; + } + else { + if (this.rule.parent.selectors.length) { + var pSpec = findSelector(this.rule.parent).specificity(env); + + rSpec = this.rule.parent.selectors[0].specificity(env); + ruleSpecificity = { + specificity: [pSpec[0], pSpec[1], rSpec[2], rSpec[3], rSpec[4]] + }; + } + else { + ruleSpecificity = { + specificity: [0, 0, 0, tree.Zoom.all, this.rule.index] + }; + } + } + + for (var i = 0; tree.specificitySort(variableDefs[i], + ruleSpecificity) < 0 && i < variableDefs.length; i++); + + return variableDefs[i].rule.value.ev(env); } else { util.error(env, { message: 'variable ' + this.name + ' is undefined', @@ -32,7 +127,10 @@ tree.Variable.prototype = { value: 'undefined' }; } - } -}; + }; + + tree.Variable.prototype.setRule = function (rule) { + this.rule = rule; + }; })(require('../tree')); diff --git a/lib/carto/tree/zoom.js b/lib/carto/tree/zoom.js index 2034387b0..9197dd6a4 100644 --- a/lib/carto/tree/zoom.js +++ b/lib/carto/tree/zoom.js @@ -9,6 +9,7 @@ tree.Zoom = function(op, value, index, filename) { this.value = value; this.index = index; this.filename = filename; + this.rule = null; }; tree.Zoom.prototype.setZoom = function(zoom) { @@ -118,3 +119,13 @@ tree.Zoom.prototype.toString = function() { } return str; }; + +tree.Zoom.prototype.setRule = function (rule) { + this.rule = rule; + if (typeof this.op.setRule === 'function') { + this.op.setRule(rule); + } + if (typeof this.value.setRule === 'function') { + this.value.setRule(rule); + } +}; diff --git a/test/rendering/issue_461.mml b/test/rendering/issue_461.mml new file mode 100644 index 000000000..2496fc4b5 --- /dev/null +++ b/test/rendering/issue_461.mml @@ -0,0 +1,10 @@ +{ + "Stylesheet": [ + "issue_461.mss" + ], + "Layer": [ + { + "id": "world" + } + ] +} diff --git a/test/rendering/issue_461.mss b/test/rendering/issue_461.mss new file mode 100644 index 000000000..60652294a --- /dev/null +++ b/test/rendering/issue_461.mss @@ -0,0 +1,10 @@ +@var: 'foo'; + +#world { + @var: 'oof'; + [zoom >= 5] { + @var: 'roof'; + } + text-face-name: 'Arial'; + text-name: @var + 'bar'; +} diff --git a/test/rendering/issue_461.result b/test/rendering/issue_461.result new file mode 100644 index 000000000..a7e51da4a --- /dev/null +++ b/test/rendering/issue_461.result @@ -0,0 +1,18 @@ + + + + + + world + + + diff --git a/test/specificity/classes.result b/test/specificity/classes.result index 0e41a46bc..7dcf85f1a 100644 --- a/test/specificity/classes.result +++ b/test/specificity/classes.result @@ -1,9 +1,9 @@ [ - {"elements":[".foo",".bar",".baz"],"specificity":[0,3,0,29],"matchCount": 0}, - {"elements":[".foo",".baz"],"specificity":[0,2,0,68],"matchCount": 0}, - {"elements":[".baz",".foo"],"specificity":[0,2,0,55],"matchCount": 0}, - {"elements":[".baz",".bar"],"specificity":[0,2,0,0],"matchCount": 0}, - {"elements":[".baz"],"specificity":[0,1,0,47],"matchCount": 0}, - {"elements":[".bar"],"specificity":[0,1,0,21],"matchCount": 0}, - {"elements":[".foo"],"specificity":[0,1,0,13],"matchCount": 0} + {"elements":[".foo",".bar",".baz"],"specificity":[0,3,0,67108863,29],"matchCount": 0}, + {"elements":[".foo",".baz"],"specificity":[0,2,0,67108863,68],"matchCount": 0}, + {"elements":[".baz",".foo"],"specificity":[0,2,0,67108863,55],"matchCount": 0}, + {"elements":[".baz",".bar"],"specificity":[0,2,0,67108863,0],"matchCount": 0}, + {"elements":[".baz"],"specificity":[0,1,0,67108863,47],"matchCount": 0}, + {"elements":[".bar"],"specificity":[0,1,0,67108863,21],"matchCount": 0}, + {"elements":[".foo"],"specificity":[0,1,0,67108863,13],"matchCount": 0} ] diff --git a/test/specificity/demo.result b/test/specificity/demo.result index a36e3ed3b..5c755286b 100644 --- a/test/specificity/demo.result +++ b/test/specificity/demo.result @@ -1,10 +1,10 @@ [ - {"elements":["#countries",".foo",".bar",".baz"],"specificity":[1,3,0,241],"matchCount": 0}, - {"elements":["#countries",".countries",".two"],"specificity":[1,2,0,149],"matchCount": 0}, - {"elements":["#world"],"filters":["[NAME]=United States","[BLUE]=red"],"specificity":[1,0,2,90],"matchCount": 0}, - {"elements":["#world"],"filters":["[NAME]=United States"],"specificity":[1,0,1,66],"matchCount": 0}, - {"elements":["#countries"],"specificity":[1,0,0,241],"matchCount": 0}, - {"elements":["#countries"],"specificity":[1,0,0,194],"matchCount": 0}, - {"elements":["#world"],"specificity":[1,0,0,194],"matchCount": 0}, - {"elements":["#world"],"specificity":[1,0,0,11],"matchCount": 0} + {"elements":["#countries",".foo",".bar",".baz"],"specificity":[1,3,0,67108863,241],"matchCount": 0}, + {"elements":["#countries",".countries",".two"],"specificity":[1,2,0,67108863,149],"matchCount": 0}, + {"elements":["#world"],"filters":["[NAME]=United States","[BLUE]=red"],"specificity":[1,0,2,67108863,90],"matchCount": 0}, + {"elements":["#world"],"filters":["[NAME]=United States"],"specificity":[1,0,1,67108863,66],"matchCount": 0}, + {"elements":["#countries"],"specificity":[1,0,0,67108863,241],"matchCount": 0}, + {"elements":["#countries"],"specificity":[1,0,0,67108863,194],"matchCount": 0}, + {"elements":["#world"],"specificity":[1,0,0,67108863,194],"matchCount": 0}, + {"elements":["#world"],"specificity":[1,0,0,67108863,11],"matchCount": 0} ] diff --git a/test/specificity/filters_and_ids.result b/test/specificity/filters_and_ids.result index abe3dcebf..d8568c960 100644 --- a/test/specificity/filters_and_ids.result +++ b/test/specificity/filters_and_ids.result @@ -1,8 +1,8 @@ [ - {"elements":["#world","#countries"],"filters":["[NAME]=United States"],"specificity":[2,0,1,94],"matchCount": 0}, - {"elements":["#world"],"filters":["[NAME]=United States"],"zoom":"......XXXXXXXXXXXXXXXXXXXX","specificity":[1,0,2,60],"matchCount": 0}, - {"elements":["#world"],"filters":["[NAME]=United States"],"specificity":[1,0,1,33],"matchCount": 0}, - {"elements":["#world"],"filters":["[NAME]=Canada"],"specificity":[1,0,1,7],"matchCount": 0}, - {"elements":[],"zoom":"......XXXXXXXXXXXXXXXXXXXX","specificity":[0,0,1,146],"matchCount": 0}, - {"elements":[],"filters":["[NAME]=United States"],"specificity":[0,0,1,120],"matchCount": 0} + {"elements":["#world","#countries"],"filters":["[NAME]=United States"],"specificity":[2,0,1,67108863,94],"matchCount": 0}, + {"elements":["#world"],"filters":["[NAME]=United States"],"zoom":"......XXXXXXXXXXXXXXXXXXXX","specificity":[1,0,2,67108800,60],"matchCount": 0}, + {"elements":["#world"],"filters":["[NAME]=United States"],"specificity":[1,0,1,67108863,33],"matchCount": 0}, + {"elements":["#world"],"filters":["[NAME]=Canada"],"specificity":[1,0,1,67108863,7],"matchCount": 0}, + {"elements":[],"zoom":"......XXXXXXXXXXXXXXXXXXXX","specificity":[0,0,1,67108800,146],"matchCount": 0}, + {"elements":[],"filters":["[NAME]=United States"],"specificity":[0,0,1,67108863,120],"matchCount": 0} ] diff --git a/test/specificity/issue60.result b/test/specificity/issue60.result index f24f92a94..01b8a46ba 100644 --- a/test/specificity/issue60.result +++ b/test/specificity/issue60.result @@ -1,6 +1,6 @@ [ - {"elements":["#world"],"filters":["[OBJECTID]=12"],"specificity":[1,0,1,131],"matchCount": 0}, - {"elements":["#world"],"filters":["[NET_INFLOW]>-10000"],"specificity":[1,0,1,83],"matchCount": 0}, - {"elements":["#world"],"filters":["[NET_INFLOW]>-30000"],"specificity":[1,0,1,35],"matchCount": 0}, - {"elements":["#world"],"specificity":[1,0,0,0],"matchCount": 0} + {"elements":["#world"],"filters":["[OBJECTID]=12"],"specificity":[1,0,1,67108863,131],"matchCount": 0}, + {"elements":["#world"],"filters":["[NET_INFLOW]>-10000"],"specificity":[1,0,1,67108863,83],"matchCount": 0}, + {"elements":["#world"],"filters":["[NET_INFLOW]>-30000"],"specificity":[1,0,1,67108863,35],"matchCount": 0}, + {"elements":["#world"],"specificity":[1,0,0,67108863,0],"matchCount": 0} ]