UNPKG

carto

Version:

Mapnik Stylesheet Compiler

315 lines (286 loc) 10.5 kB
// Carto needs a reference to validate input. // The reference can be either `mapnik-reference` or customly set. // This file builds indexes from that file for its various // options, and provides validation methods for property: value // combinations. (function(tree) { var _ = require('lodash'), semver = require('semver'); tree.Reference = function Reference(ref) { this.data = null; this.selector_cache = []; this.functions = []; this.required_cache = []; if (!_.isNil(ref)) { if (this.checkCompliance(ref)) { this.compliant = true; this.ref = ref; } // fall back to Mapnik reference if non-compliant reference was given else { this.compliant = false; this.ref = require('mapnik-reference'); } } // use Mapnik reference if no reference given else { this.compliant = true; this.ref = require('mapnik-reference'); } }; tree.Reference.prototype.checkCompliance = function (ref) { if (_.has(ref, 'latest') && _.has(ref, 'versions') && _.isArray(ref.versions) && _.has(ref, 'load') && _.isFunction(ref.load)) { var data = ref.load(ref.latest); if (_.has(data, 'version') && _.isString(data.version) && _.has(data, 'style') && _.isObject(data.style) && _.has(data, 'layer') && _.isObject(data.layer) && _.has(data, 'symbolizers') && _.isObject(data.symbolizers) && _.has(data, 'colors') && _.isObject(data.colors) && _.has(data, 'datasources') && _.isObject(data.datasources)) { return true; } } return false; }; tree.Reference.prototype.getLatest = function () { return this.ref.latest; }; tree.Reference.prototype.setData = function (data) { this.data = data; this.selector_cache = generateSelectorCache(data); this.functions = generateFunctions(data); this.functions.matrix = [6]; this.functions.translate = [1, 2]; this.functions.scale = [1, 2]; this.functions.rotate = [1, 3]; this.functions.skewX = [1]; this.functions.skewY = [1]; this.required_cache = generateRequiredProperties(data); }; tree.Reference.prototype.setVersion = function (version) { if (semver.valid(version)) { try { this.setData(this.ref.load(version)); } catch (err) { var e = new Error('Version ' + version + ' is not supported'); e.stack = null; // do not show stack trace throw e; } } else { var apiErr = new Error('Invalid API version. A valid version is e.g. 3.0.0 or 3.0.10'); apiErr.stack = null; // do not show stack trace throw apiErr; } }; tree.Reference.prototype.selectorData = function (selector, i) { if (this.selector_cache[selector]) return this.selector_cache[selector][i]; }; tree.Reference.prototype.validSelector = function (selector) { return !!this.selector_cache[selector]; }; tree.Reference.prototype.selectorName = function (selector) { return this.selectorData(selector, 3); }; tree.Reference.prototype.selector = function (selector) { return this.selectorData(selector, 1); }; tree.Reference.prototype.symbolizer = function (selector) { return this.selectorData(selector, 2); }; function generateSelectorCache(data) { var index = {}; _.forEach(data.style, function (rule, i) { if (_.has(rule, 'css')) { index[rule.css] = ['style', rule, '*', i]; } }); _.forEach(data.layer, function (rule, i) { if (_.has(rule, 'css')) { index[rule.css] = ['layer', rule, '*', i]; } }); _.forEach(data.symbolizers, function (symbolizer, i) { _.forEach(symbolizer, function (rule, j) { if (_.has(rule, 'css')) { index[rule.css] = ['symbolizer', rule, i, j]; } }); }); return index; } function generateFunctions(data) { var functions = {}; _.forEach(data.style, function (rule) { if (rule.type === 'functions') { _.forEach(rule.functions, function (func) { functions[func[0]] = func[1]; }); } }); _.forEach(data.layer, function (rule) { if (rule.type === 'functions') { _.forEach(rule.functions, function (func) { functions[func[0]] = func[1]; }); } }); _.forEach(data.symbolizers, function (symbolizer) { _.forEach(symbolizer, function (rule) { if (rule.type === 'functions') { _.forEach(rule.functions, function (func) { functions[func[0]] = func[1]; }); } }); }); return functions; } function generateRequiredProperties(data) { var cache = {}; for (var symbolizer_name in data.symbolizers) { cache[symbolizer_name] = []; for (var j in data.symbolizers[symbolizer_name]) { if (data.symbolizers[symbolizer_name][j].required) { cache[symbolizer_name].push(data.symbolizers[symbolizer_name][j].css); } } } return cache; } tree.Reference.prototype.requiredProperties = function (symbolizer_name, rules) { var that = this, doNotSerialize = false; _.forEach(rules, function (rule) { if (that.selectorName(rule.name) === 'default') { doNotSerialize = true; } }); if (doNotSerialize) { return null; } var req = this.required_cache[symbolizer_name]; for (var i in req) { if (!(req[i] in rules)) { return 'Property ' + req[i] + ' required for defining ' + symbolizer_name + ' styles.'; } } }; // TODO: finish implementation - this is dead code tree.Reference.prototype._validateValue = { 'font': function(env, value) { if (env.validation_data && env.validation_data.fonts) { return env.validation_data.fonts.indexOf(value) != -1; } else { return true; } } }; tree.Reference.prototype.isFont = function (selector) { return this.selector(selector).validate == 'font'; }; // https://gist.github.com/982927 tree.Reference.prototype.editDistance = function (a, b){ if (a.length === 0) return b.length; if (b.length === 0) return a.length; var matrix = []; for (var i = 0; i <= b.length; i++) { matrix[i] = [i]; } for (var j = 0; j <= a.length; j++) { matrix[0][j] = j; } for (i = 1; i <= b.length; i++) { for (j = 1; j <= a.length; j++) { if (b.charAt(i-1) == a.charAt(j-1)) { matrix[i][j] = matrix[i-1][j-1]; } else { matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution Math.min(matrix[i][j-1] + 1, // insertion matrix[i-1][j] + 1)); // deletion } } } return matrix[b.length][a.length]; }; function validateFunctions(ref, value, selector) { if (value.value[0].is === 'string') return true; for (var i in value.value) { for (var j in value.value[i].value) { if (value.value[i].value[j].is !== 'call') return false; var f = _.find(ref .selector(selector).functions, function(x) { return x[0] == value.value[i].value[j].name; }); if (!(f && f[1] == -1)) { // This filter is unknown or given an incorrect number of arguments if (!f || f[1] !== value.value[i].value[j].args.length) return false; } } } return true; } function validateKeyword(ref, value, selector) { if (typeof ref.selector(selector).type === 'object') { return ref.selector(selector).type .indexOf(value.value[0].value) !== -1; } else { // allow unquoted keywords as strings return ref.selector(selector).type === 'string' || ref.selector(selector)['default-value'] === value.value[0].value; } } tree.Reference.prototype.selectorStatus = function (selector) { var selectorRef = this.selector(selector); if (_.has(selectorRef, 'status')) { return selectorRef.status; } return 'stable'; }; tree.Reference.prototype.validValue = function (env, selector, value) { var i; // TODO: handle in reusable way if (!this.selector(selector)) { return false; } else if (value.value[0].is == 'keyword') { return validateKeyword(this, value, selector); } else if (value.value[0].is == 'undefined') { // caught earlier in the chain - ignore here so that // error is not overridden return true; } else if (this.selector(selector).type == 'numbers') { for (i in value.value) { if (value.value[i].is !== 'float') { return false; } } return true; } else if (this.selector(selector).type == 'tags') { if (!value.value) return false; if (!value.value[0].value) { return value.value[0].is === 'tag'; } for (i = 0; i < value.value[0].value.length; i++) { if (value.value[0].value[i].is !== 'tag') return false; } return true; } else if (this.selector(selector).type == 'functions') { // For backwards compatibility, you can specify a string for `functions`-compatible // values, though they will not be validated. return validateFunctions(this.ref, value, selector); } else if (this.selector(selector).type === 'unsigned') { if (value.value[0].is === 'float') { value.value[0].round(); return true; } else { return false; } } else if ((this.selector(selector).expression)) { return true; } else { if (this.selector(selector).validate) { for (i = 0; i < value.value.length; i++) { if (this.selector(selector).type == value.value[i].is && this._validateValue[this.selector(selector).validate](env, value.value[i].value)) { return true; } } return false; } else { return this.selector(selector).type == value.value[0].is; } } }; })(require('../tree'));