Compare commits

..

No commits in common. "master" and "field" have entirely different histories.

365 changed files with 1955 additions and 28546 deletions

5
.gitignore vendored
View File

@ -1,6 +1 @@
/node_modules
.DS_Store
test/rendering/layers/
test/rendering/cache/
test/rendering-mss/npm-debug.log
.idea/

View File

@ -1,9 +1,3 @@
language: node_js
node_js:
- '6'
- '8'
- '10'
script:
- npm test
- 0.6

View File

@ -1,9 +0,0 @@
## CARTO's Changelog
## 0.15.1-cdb5
2018-11-20
* Support Node.js 6, 8 and, 10
* Drop support for Node.js 0.10 and 0.11
* Add package-lock.json
* Add CHANGELOG.carto.md

View File

@ -1,112 +1,16 @@
## Changelog
## 0.14.0
* Support for Mapnik 3.x
* Bump `mapnik-reference` dependency to ~6.0.1.
## 0.13.0
* Allows optional args in transforms.
* Bump `mapnik-reference` dependency to 5.1.x.
## 0.12.0
* Drop mml2json and xml2js dependency.
## 0.11.0
* Switch API to be synchronous. All errors should be caught using try/catch now.
## 0.10.0
* Remove automatic inclusion of `maximum-extent` on Map element to allow geometries that are buffered past extent bounds (e.g. dateline).
* Bump `mapnik-reference` dependency to ~5.0.9 (with `shield-halo-rasterizer`)
## 0.9.6
* Fixed support for `text-face-name` values with `&` like `El&Font Bubble`
* Fixed support for filtering on fields containing single quotes. Now `#layer[name="it's"] { ... }` is possible.
* Fixed support for filtering on fields containing `&`. Now `#layer["Hello&Goodbye"="yes"] { ... }` is possible.
* Added support for exponential notation in filters. Now `#layer[value = 1.2e3] { ... }` is possible.
* Bump `mapnik-reference` dependency to ~5.0.8 (with support for Mapnik v2.3.0 and 3.x)
## 0.9.5
* Various speed optimizations to help address #20 (#231)
* Fixed support for fields that contain the word `zoom` in them (previous clashed with `zoom` keyword)
* Fixed support for a space in front of `zoom` keyword (#288)
* Improved error messages when color functions encounter invalid color (#309)
* The `carto` command line tool now exits cleanly when millstone is used
* The `carto` command line tool now only localized with millstone if requested (#243)
* Added man page for `carto` (#257)
* Fix repeated comments in selectors. Fixes #260
* Fixed `image-filter` duplication (#270)
* Quote all needed XML chars. See #263.
* Added higher tolerance for various characters in field names (#230)
* Bump `mapnik-reference` dependency to ~5.0.7 (with support for Mapnik v2.2.0)
* Adds compatibility with screen units.
* Fixed ability to use carto as global module (#236)
* Now using 'console' instead of `util` for `stderr` (#217)
## 0.9.4
* Fixes nesting of regex calls
## 0.9.3
* Allows `text-face-name` properties to be unquoted
* Detects inline Format XML tags in `text-name` and passes such output
straight to XML for advanced text names.
* Fixes bugs around concatenation of strings in expressions
* Fixes parsing of comments in between selectors
* Fixes parsing of whitespace in calls
* Improved error messages for unknown properties - advises user on
the property name most closely matching the incorrect input.
* Improved errors for calls, advises user on number of arguments
* Fixes instance inheritance - thanks @gravitystorm!
## 0.9.2
Tagged Sept 6, 2012
* Bump `mapnik-reference` dependency to ~5.0.0
* Better support for unsigned types in certain Mapnik styling properties
## 0.9.1
Tagged Aug 15, 2012
* Improved error handling for different target `mapnik-reference` versions (strk)
* Bump `mapnik-reference` dependency to ~4.0.3
* Fixed handling of image-filter syntax as per [Mapnik changes](https://github.com/mapnik/mapnik/issues/1384)
## 0.9.0
* Bump `mapnik-reference` dependency to ~4.0.0 to pull in new properties.
* Adapted to `comp-op` rename upstream in `mapnik-reference`.
* Adapted to `transform` rename upstream in `mapnik-reference` and Mapnik.
## 0.8.1
* Bump `mapnik-reference` dependency to ~3.1.0 to pull in new properties.
## 0.8.0
* Adds the modulus operator `%` as an option
* Adds a new field-type like `[FIELD]` instead of "[FIELD]"
* Adds a new field-type like `[FIELD]` instead of "[FIELD"
### 0.8.0
* Supports function syntax for transforms, optionally with variables and arguments.
### 0.7.1
* Updated mapnik-reference to `~2.2.1`
* Added support for `status` parameter on layers.
* Command line `carto` program gained `--nosymlink` option to pass to millstone to use absolute paths instead of symlinking files.
* Removed unsupported mixin code.
### 0.7.0
* Updated mapnik-reference to `~2.1.0`
* Support an `opacity` property on any style that is a style-level property
### 0.6.0

View File

@ -1,34 +0,0 @@
## Developing
Installing:
git clone git@github.com:mapbox/carto.git
npm install
Test:
npm test
Running the head binary:
./bin/carto
## Documentation
This repository contains auto-generated documentation of the content of Carto
that's published on Mapbox.com.
git fetch origin gh-pages:gh-pages
Edit `_docs/package.json` to point to the head version of [mapnik-reference](https://github.com/mapnik/mapnik-reference).
cd _docs
npm install
node generate.js
Then run up a directory and run the testing server:
cd ../
jekyll serve -p 4000
Test the new site at `localhost:4000/carto` and if things look good then git add your changes and push.

View File

@ -3,15 +3,7 @@
#
expresso = ./node_modules/.bin/mocha
UGLIFYJS=./node_modules/.bin/uglifyjs
BROWSERIFY = ./node_modules/.bin/browserify
dist/carto.js: dist/carto.uncompressed.js $(shell $(BROWSERIFY) --list lib/carto/index.js)
$(UGLIFYJS) dist/carto.uncompressed.js > $@
dist/carto.uncompressed.js: dist $(shell $(BROWSERIFY) --list lib/carto/index.js)
$(BROWSERIFY) lib/carto/index.js --exclude node_modules/underscore/underscore.js --standalone carto > $@
docco = ./node_modules/.bin/docco
lint:
./node_modules/.bin/jshint lib/carto/*.js lib/carto/tree/*.js
@ -24,12 +16,7 @@ test:
@NODE_PATH=./lib:$NODE_PATH $(expresso) -R spec -I lib test/${only}.test.js
endif
check: test
dist:
mkdir -p dist
doc:
$(docco) lib/carto/*.js lib/carto/tree/*.js
.PHONY: test

375
README.md
View File

@ -1,83 +1,308 @@
# CartoCSS
[![Build Status](https://travis-ci.org/CartoDB/carto.png?branch=master)](https://travis-ci.org/CartoDB/carto)
Is as stylesheet renderer for javascript, It's an evolution of the Mapnik renderer from Mapbox.
Please, see original [Mapbox repo](http://github.com/mapbox/carto) for more information and credits
## Quick Start
```javascript
// shader is a CartoCSS object
var cartocss = [
'#layer {',
' marker-width: [property]',
' marker-fill: red',
'}'
].join('')
var shader = new carto.RendererJS().render(cartocss);
var layers = shader.getLayers()
for (var i = 0; i < layers.length; ++i) {
var layer = layers[i];
console.log("layer name: ", layer.fullName())
console.log("- frames: ", layer.frames())
console.log("- attachment: ", layer.attachment())
var layerShader = layer.getStyle({ property: 1 }, { zoom: 10 })
console.log(layerShader['marker-width']) // 1
console.log(layerShader['marker-fill']) // #FF0000
}
```
# API
## RendererJS
### render(cartocss)
## CartoCSS
compiled cartocss object
### getLayers
return the layers, an array of ``CartoCSS.Layer`` object
### getDefault
return the default layer (``CartoCSS.Layer``), usually the Map layer
### findLayer(where)
find a layer using where object.
```
shader.findLayer({ name: 'test' })
```
## CartoCSS.Layer
### getStyle(props, context)
return the evaluated style:
- props: object containing properties needed to render the style. If the cartocss style uses
some variables they should be passed in this object
- context: rendering context variables like ``zoom`` or animation ``frame``
# carto
[![Build Status](https://secure.travis-ci.org/mapbox/carto.png)](http://travis-ci.org/mapbox/carto)
Is a stylesheet renderer for Mapnik. It's an evolution of the
[Cascadenik](https://github.com/mapnik/Cascadenik) idea and language,
with an emphasis on speed and flexibility.
## Reference Documentation
* [mapbox.com/carto](http://mapbox.com/carto/)
## MML
_incompatibility_
* MML files are assumed to be JSON, not XML. The files are near-identical
to the XML files accepted by Cascadenik, just translated into JSON.
* Carto will not embed files or download URLs for you. Stylesheets should
be embedded directly into your MML JSON and any datasources should be
paths (relative or absolute) that would be acceptable in Mapnik XML.
The [millstone project](https://github.com/mapbox/millstone) aims to fill this need.
carto.js MML:
{
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
"Stylesheet": [{"id":"style.mss","data":"Map {\n background-color: #fff;\n}\n\n#world {\n line-color: #ccc;\n line-width: 0.5;\n polygon-fill: #eee;\n}"}],
"Layer": [{
"id": "world",
"name": "world",
"srs": "+proj=latlong +ellps=WGS84 +datum=WGS84 +no_defs",
"Datasource": {
"file": "world_borders",
"type": "shape"
}
}]
}
Cascadenik MML
<pre>&lt;Stylesheet&gt;&lt;![CDATA[
Map
{
map-bgcolor: #69f;
}
Layer
{
line-width: 1;
line-color: #696;
polygon-fill: #6f9;
}
]]&gt;&lt;/Stylesheet&gt;
&lt;Layer srs=&quot;+proj=latlong +ellps=WGS84 +datum=WGS84 +no_defs&quot;&gt;
&lt;Datasource&gt;
&lt;Parameter name=&quot;type&quot;&gt;shape&lt;/Parameter&gt;
&lt;Parameter name=&quot;file&quot;&gt;world_borders&lt;/Parameter&gt;
&lt;/Datasource&gt;
&lt;/Layer&gt;
&lt;/Map&gt;</pre>
## Attachments and Instances
_new_
In CSS, a certain object can only have one instance of a property. A `<div>` has a specific border width and color, rules that match better than others (#id instead of .class) override previous definitions. `carto.js` acts the same way normally for the sake of familiarity and organization, but Mapnik itself is more powerful.
Layers in Mapnik can have multiple [borders](http://trac.mapnik.org/wiki/LineSymbolizer) and multiple copies of other attributes. This ability is useful in drawing line outlines, like in the case of road borders or 'glow' effects around coasts. `carto.js` makes this accessible by allowing attachments to styles:
#world {
line-color: #fff;
line-width: 3;
}
#world::outline {
line-color: #000;
line-width: 6;
}
Attachments are optional: if you don't define them, carto.js does overriding of styles just like Cascadenik.
This brings us to another _incompatibility_: `line-inline` and `line-outline` have been removed from the language, because attachments are capable of the same trick.
While attachments allow creating implicit "layers" with the same data, using **instances** allows you to create multiple symbolizers in the same style/layer:
#roads {
casing/line-width: 6;
casing/line-color: #333;
line-width: 4;
line-color: #666;
}
This makes Mapnik first draw the line of color #333 with a width of 6, and then immediately afterwards, it draws the same line again with width 4 and color #666. Contrast that to attachments: Mapnik would first draw all casings before proceeding to the actual lines.
## text-name
_incompatibility_
Instead of the name attribute of the [TextSymbolizer](http://trac.mapnik.org/wiki/TextSymbolizer) and [ShieldSymbolizer](http://trac.mapnik.org/wiki/ShieldSymbolizer) being a part of the selector, it is a property of a rule. Thus the evaluation is less complex and one can use expressions in names.
<table>
<tr>
<th>cascadenik</th>
<th>carto.js</th>
</tr>
<tr>
<td valign='top'>
<pre>
#world NAME {
text-face-name: "Arial";
}</pre>
</td>
<td valign='top'>
<pre>
#world {
text-name: "NAME";
text-face-name: "Arial";
}</pre>
</td>
</tr>
</table>
## Mapnik2
_new_
`carto.js` is only compatible with [Mapnik2](http://trac.mapnik.org/wiki/Mapnik2). Compatibility with Mapnik 0.7.x is not planned.
## Rasters and Buildings
_new_
Rasters are supported in carto.js - it knows how to download `.vrt`, `.tiff`, and soon other raster formats, and the properties of the [RasterSymbolizer](http://trac.mapnik.org/wiki/RasterSymbolizer) are exposed in the language.
The [BuildingSymbolizer](http://trac.mapnik.org/wiki/BuildingSymbolizer) is also supported in `carto.js`. The code stores symbolizer types and properties in a JSON file (in `tree/reference.json`), so new Mapnik features can be quickly implemented here.
## Variables & Expressions
_new_
`carto.js` inherits from its basis in [less.js](http://lesscss.org/) some new features in CSS. One can define variables in stylesheets, and use expressions to modify them.
@mybackground: #2B4D2D;
Map {
background-color: @mybackground
}
#world {
polygon-fill: @mybackground + #222;
line-color: darken(@mybackground, 10%);
}
## Nested Styles
_new_
`carto.js` also inherits nesting of rules from less.js.
/* Applies to all layers with .land class */
.land {
line-color: #ccc;
line-width: 0.5;
polygon-fill: #eee;
/* Applies to #lakes.land */
#lakes {
polygon-fill: #000;
}
}
This can be a convenient way to group style changes by zoom level:
[zoom > 1] {
/* Applies to all layers at zoom > 1 */
polygon-gamma: 0.3;
#world {
polygon-fill: #323;
}
#lakes {
polygon-fill: #144;
}
}
## FontSets
_new_
By defining multiple fonts in a `text-face-name` definition, you create [FontSets](http://trac.mapnik.org/wiki/FontSet) in `carto.js`. These are useful for supporting multiple character sets and fallback fonts for distributed styles.
<table>
<tr>
<th>carto</th><th>XML</th>
</tr>
<tr>
<td valign='top'>
<pre>#world {
text-name: "[NAME]";
text-size: 11;
text-face-name: "Georgia Regular", "Arial Italic";
}</pre>
</td>
<td valign='top'>
<pre>&lt;FontSet name=&quot;fontset-0&quot;&gt;
&lt;Font face-name=&quot;Georgia Regular&quot;/&gt;
&lt;Font face-name=&quot;Arial Italic&quot;/&gt;
&lt;/FontSet&gt;
&lt;Style name=&quot;world-text&quot;&gt;
&lt;Rule&gt;
&lt;TextSymbolizer fontset-name=&quot;fontset-0&quot;
size=&quot;11&quot;
name=&quot;[NAME]&quot;/&gt;
&lt;/Rule&gt;
&lt;/Style&gt;</pre>
</td>
<tr>
</table>
## Filters
Carto supports a variety of filter styles:
Numeric comparisons:
```
#world[population > 100]
#world[population < 100]
#world[population >= 100]
#world[population <= 100]
```
General comparisons:
```
#world[population = 100]
#world[population != 100]
```
String comparisons:
```
/* a regular expression over name */
#world[name =~ "A.*"]
```
## Developers
#### Installation
If you're using [TileMill](http://mapbox.com/tilemill/), you're already
using Carto and don't need to do a thing.
If you're a developer-type and want to use the `carto` binary with
`node.js` (and you have [npm](http://npmjs.org/) installed),
npm install carto
#### From the binary
Install `millstone` to enable support for localizing external resources (URLs and local files) referenced in your mml file.
npm install millstone
carto map_file.json
#### From code
Currently `carto.js` is designed to be invoked from [node.js](http://nodejs.org/).
The `Renderer` interface is the main API for developers, and it takes an MML file as a string as input.
// defined variables:
// - input (the name or identifier of the file being parsed)
// - data (a string containing the MML or an object of MML)
var carto = require('carto');
new carto.Renderer({
filename: input,
local_data_dir: path.dirname(input),
}).render(data, function(err, output) {
if (err) {
if (Array.isArray(err)) {
err.forEach(function(e) {
carto.writeError(e, options);
});
} else { throw err; }
} else {
sys.puts(output);
}
});
### Vim
To install, download or clone this repository, then add the `vim-carto`
directory located at `build/vim-carto` to your `~/.vim` file.
## Credits
`carto.js` is based on [less.js](https://github.com/cloudhead/less.js), a CSS compiler written by Alexis Sellier.
It depends on:
* [underscore.js](https://github.com/documentcloud/underscore/)
Only for running tests:
* [mocha](https://github.com/visionmedia/mocha)
* [sax-js](https://github.com/isaacs/sax-js/)
## Authors
* Tom MacWright (tmcw)
* Konstantin Käfer (kkaefer)
* AJ Ashton (ajashton)
* Dane Springmeyer (springmeyer)

215
bin/carto
View File

@ -2,40 +2,46 @@
var path = require('path'),
fs = require('fs'),
carto = require('../lib/carto'),
url = require('url'),
_ = require('underscore');
util = require('util'),
carto = require('carto');
var existsSync = require('fs').existsSync || require('path').existsSync
var args = process.argv.slice(1);
var options = {};
var optimist = require('optimist')
.usage("Usage: $0 <source MML file>")
.options('h', {alias:'help', describe:'Display this help message'})
.options('v', {alias:'version', boolean:true, describe:'Display version information'})
.options('b', {alias:'benchmark', boolean:true, describe:'Outputs total compile time'})
.options('l', {alias:'localize', boolean:true, default:false, describe:'Use millstone to localize resources when loading an MML'})
.options('n', {alias:'nosymlink', boolean:true, describe:'Use absolute paths instead of symlinking files'})
.options('ppi', {describe:'Pixels per inch used to convert m, mm, cm, in, pt, pc to pixels', default:90.714});
args = args.filter(function (arg) {
var match;
var options = optimist.argv;
if (match = arg.match(/^--?([a-z][0-9a-z-]*)$/i)) { arg = match[1] }
else { return arg }
if (options.help) {
optimist.showHelp();
process.exit(0);
}
switch (arg) {
case 'v':
case 'version':
util.puts("carto " + carto.version.join('.') + " (Carto map stylesheet compiler)");
process.exit(0);
break;
case 'b':
case 'benchmark':
options.benchmark = true;
break;
if (options.version) {
console.log("carto " + carto.version.join('.') + " (Carto map stylesheet compiler)");
process.exit(0);
}
default:
util.puts("Usage: carto <source MML file>");
util.puts("Options:");
util.puts(" -v --version Parse JSON map manifest");
util.puts(" -b --benchmark Outputs total compile time");
process.exit(0);
break;
}
});
var input = options._[0];
var input = args[1];
if (input && input[0] != '/') {
input = path.join(process.cwd(), input);
}
if (!input) {
console.log("carto: no input files ('carto -h or --help' for help)");
util.puts("carto: no input files");
process.exit(1);
}
@ -43,120 +49,63 @@ if (options.benchmark) {
var start = +new Date;
}
var ext = path.extname(input);
if (!ext) {
console.log("carto: please pass either a .mml file or .mss file");
process.exit(1);
}
if (!existsSync(input)) {
console.log("carto: file does not exist: '" + input + "'");
process.exit(1);
}
function compileMML(err, data) {
// force drain the millstone download pool now
// to ensure we can exit without waiting
if (options.localize && millstone.drainPool) {
millstone.drainPool(function() {});
}
if (err) {
console.error(err);
process.exit(1);
}
var renderer = new carto.Renderer({
filename: input,
benchmark: options.benchmark,
ppi: options.ppi
});
try {
var output = renderer.render(data);
} catch (e) {
if (e.stack) {
console.error(e.stack);
} else {
console.error(e);
}
process.exit(1);
}
if (!options.benchmark) {
console.log(output);
} else {
var duration = (+new Date) - start;
console.log('TOTAL: ' + (duration) + 'ms');
}
};
function compileMSS(err, data) {
if (err) {
console.error(err);
process.exit(1);
}
var renderer = new carto.Renderer({
filename: path.basename(input),
benchmark: options.benchmark,
ppi: options.ppi
});
try {
var output = renderer.renderMSS(data);
} catch (e) {
if (e.stack) {
console.error(e.stack);
} else {
console.error(e);
}
process.exit(1);
}
if (!options.benchmark) {
console.log(output);
} else {
var duration = (+new Date) - start;
console.log('TOTAL: ' + (duration) + 'ms');
}
};
try {
var data = fs.readFileSync(input, 'utf-8');
} catch(err) {
console.error("carto: " + err.message.replace(/^[A-Z]+, /, ''));
util.puts("carto: " + err.message.replace(/^[A-Z]+, /, ''));
process.exit(1);
}
if (ext == '.mml') {
try {
data = JSON.parse(data);
} catch(err) {
console.error("carto: " + err.message.replace(/^[A-Z]+, /, ''));
process.exit(1);
}
if (options.localize) {
var millstone = undefined;
try {
require.resolve('millstone');
millstone = require('millstone');
} catch (err) {
console.error('carto: Millstone not found, required if localizing stylesheet resources. ' + err.message.replace(/^[A-Z]+, /, ''));
process.exit(1);
}
millstone.resolve({
mml: data,
base: path.dirname(input),
cache: path.join(path.dirname(input), 'cache'),
nosymlink: options.nosymlink
}, compileMML);
} else {
data.Stylesheet = data.Stylesheet.map(function(x) {
if (typeof x !== 'string') {
return { id: x, data: x.data }
}
return { id: x, data: fs.readFileSync(path.join(path.dirname(input), x), 'utf8') }
});
compileMML(null,data);
}
} else if (ext == '.mss') {
compileMSS(null,data);
} else {
console.log("carto: please pass either a .mml file or .mss file");
try {
data = JSON.parse(data);
} catch(err) {
util.puts("carto: " + err.message.replace(/^[A-Z]+, /, ''));
process.exit(1);
}
var millstone = undefined;
try {
require.resolve('millstone');
millstone = require('millstone');
} catch (err) {}
if (!millstone) {
console.warn('carto: Millstone not found. Externals will not be resolved.');
return compile(null, data);
} else {
millstone.resolve({
mml: data,
base: path.dirname(input),
cache: path.join(path.dirname(input), 'cache')
}, compile);
}
function compile(err, data) {
if (err) throw err;
try {
new carto.Renderer({
filename: input,
benchmark: options.benchmark
}).render(data, function(err, output) {
if (err) {
console.log(err);
throw err;
process.exit(1);
} else {
if (!options.benchmark) {
util.puts(output);
} else {
var duration = (+new Date) - start;
console.log('TOTAL: ' + (duration) + 'ms');
}
}
});
} catch (e) {
if (e.stack) {
util.error(e.stack);
} else {
util.error(e);
}
}
};

66
bin/mml2json.js Executable file
View File

@ -0,0 +1,66 @@
#!/usr/bin/env node
var xml2js = require('xml2js'),
fs = require('fs');
if (!process.argv[2]) {
console.log('Please specify a XML file.');
process.exit(1);
}
fs.readFile(process.argv[2], 'utf-8', function(err, data) {
if (err) throw err;
// Replace entities.
var entities = {};
var match = data.match(/<!ENTITY([^>]|"([^"]|\\")*")+>/g)
if (match != null) {
match.forEach(function(entity) {
var parts = entity.match(/^<!ENTITY\s+(\w+)\s+"(.+)">$/);
entities['&' + parts[1] + ';'] = parts[2];
});
}
data = data.replace(/&\w+;/g, function(entity) {
return entities[entity];
});
function addAttributes(obj) {
if (obj['@']) for (var key in obj['@']) obj[key] = obj['@'][key];
delete obj['@'];
return obj;
}
function simplifyExternal(obj) {
if (obj.src) return obj.src;
else return obj;
}
var parser = new xml2js.Parser();
parser.addListener('end', function(json) {
console.log(JSON.stringify(json, function(key, value) {
if (!key) {
return addAttributes(value);
}
else if (key === 'Stylesheet') {
if (Array.isArray(value)) return value.map(addAttributes).map(simplifyExternal);
else return [ simplifyExternal(addAttributes(value)) ];
}
else if (key === 'Layer' || key === 'Stylesheet') {
if (Array.isArray(value)) return value.map(addAttributes);
else return [ addAttributes(value) ];
}
else if (key === 'Datasource') {
value = addAttributes(value);
value.Parameter.forEach(function(parameter) {
value[parameter['@'].name] = parameter['#'];
});
delete value.Parameter;
return value;
}
else {
return value;
}
}, 4));
});
parser.parseString(data);
});

View File

@ -264,11 +264,11 @@
</dict>
</dict>
<key>match</key>
<string>\b(background-color|background-image|srs|buffer|font-directory|polygon-fill|polygon-gamma|polygon-opacity|polygon-meta-output|polygon-meta-writer|line-color|line-width|line-opacity|line-join|line-cap|line-gamma|line-dasharray|line-meta-output|line-meta-writer|marker-file|marker-opacity|marker-line-color|marker-line-width|marker-line-opacity|marker-placement|marker-type|marker-width|marker-height|marker-fill|marker-allow-overlap|marker-spacing|marker-max-error|marker-transform|marker-meta-output|marker-meta-writer|shield-name|shield-face-name|shield-size|shield-fill|shield-min-distance|shield-halo-fill|shield-halo-radius|shield-spacing|shield-character-spacing|shield-line-spacing|shield-file|shield-width|shield-height|shield-type|shield-text-dx|shield-text-dy|shield-dx|shield-dy|shield-meta-output|shield-meta-writer|line-pattern-file|line-pattern-width|line-pattern-height|line-pattern-type|line-pattern-meta-output|line-pattern-meta-writer|polygon-pattern-file|polygon-pattern-width|polygon-pattern-height|polygon-pattern-type|polygon-pattern-meta-output|polygon-pattern-meta-writer|raster-opacity|raster-comp-op|raster-scaling|point-file|point-width|point-height|point-type|point-allow-overlap|point-placement|point-meta-output|point-meta-writer|text-name|text-face-name|text-size|text-ratio|text-wrap-width|text-spacing|text-character-spacing|text-line-spacing|text-label-position-tolerance|text-max-char-angle-delta|text-fill|text-halo-fill|text-halo-radius|text-dx|text-dy|text-avoid-edges|text-min-distance|text-min-padding|text-allow-overlap|text-placement|text-placement-type|text-placements|text-transform|text-meta-output|text-meta-writer|building-fill|building-fill-opacity|building-height)\s*:</string>
<string>\b(background-color|background-image|srs|buffer|font-directory|polygon-fill|polygon-gamma|polygon-opacity|polygon-meta-output|polygon-meta-writer|line-color|line-width|line-opacity|line-join|line-cap|line-gamma|line-dasharray|line-meta-output|line-meta-writer|marker-file|marker-opacity|marker-line-color|marker-line-width|marker-line-opacity|marker-placement|marker-type|marker-width|marker-height|marker-fill|marker-allow-overlap|marker-spacing|marker-max-error|marker-transform|marker-meta-output|marker-meta-writer|shield-name|shield-face-name|shield-size|shield-fill|shield-min-distance|shield-halo-fill|shield-halo-radius|shield-spacing|shield-character-spacing|shield-line-spacing|shield-file|shield-width|shield-height|shield-type|shield-text-dx|shield-text-dy|shield-dx|shield-dy|shield-meta-output|shield-meta-writer|line-pattern-file|line-pattern-width|line-pattern-height|line-pattern-type|line-pattern-meta-output|line-pattern-meta-writer|polygon-pattern-file|polygon-pattern-width|polygon-pattern-height|polygon-pattern-type|polygon-pattern-meta-output|polygon-pattern-meta-writer|raster-opacity|raster-mode|raster-scaling|point-file|point-width|point-height|point-type|point-allow-overlap|point-placement|point-meta-output|point-meta-writer|text-name|text-face-name|text-size|text-ratio|text-wrap-width|text-spacing|text-character-spacing|text-line-spacing|text-label-position-tolerance|text-max-char-angle-delta|text-fill|text-halo-fill|text-halo-radius|text-dx|text-dy|text-avoid-edges|text-min-distance|text-min-padding|text-allow-overlap|text-placement|text-placement-type|text-placements|text-transform|text-meta-output|text-meta-writer|building-fill|building-fill-opacity|building-height)\s*:</string>
</dict>
<dict>
<key>match</key>
<string>\b(miter|round|bevel|butt|round|square|point|line|arrow|ellipse|png|jpg|svg|normal|grain_merge|grain_merge2|multiply|multiply2|divide|divide2|screen|hard_light|near|bilinear|bilinear8|centroid|interior|point|line|vertex|interior|none|uppercase|lowercase)\b</string>
<string>\b(miter|round|bevel|butt|round|square|point|line|arrow|ellipse|png|jpg|svg|normal|grain_merge|grain_merge2|multiply|multiply2|divide|divide2|screen|hard_light|fast|bilinear|bilinear8|centroid|interior|point|line|vertex|interior|none|uppercase|lowercase)\b</string>
<key>name</key>
<string>meta.property-value.carto</string>
</dict>

View File

@ -1,7 +0,0 @@
//
// LESS - Leaner CSS v@VERSION
// http://lesscss.org
//
// Copyright (c) 2010, Alexis Sellier
// Licensed under the MIT license.
//

View File

@ -2,7 +2,8 @@
var path = require('path'),
fs = require('fs'),
_ = require('underscore')._;
_ = require('underscore')._,
sys = require('sys');
var carto = require('../lib/carto');

View File

@ -47,7 +47,7 @@ syn region cartoFontDescriptorFunction contained matchgroup=cartoFunctionName st
syn match cartoUnicodeRange contained "U+[0-9A-Fa-f?]\+"
syn match cartoUnicodeRange contained "U+\x\+-\x\+"
syn match cartoKeywordAttr "/\|miter\|round\|bevel\|butt\|round\|square\|point\|line\|arrow\|ellipse\|point\|line\|vertex\|interior\|local\|global\|normal\|grain_merge\|grain_merge2\|multiply\|multiply2\|divide\|divide2\|screen\|hard_light\|near\|bilinear\|bilinear8\|bicubic\|spline16\|gaussian\|lanczos\|centroid\|interior\|top\|middle\|bottom\|point\|line\|vertex\|interior\|dummy\|simple\|none\|uppercase\|lowercase\|capitalize\|/"
syn match cartoKeywordAttr "/\|miter\|round\|bevel\|butt\|round\|square\|point\|line\|arrow\|ellipse\|point\|line\|vertex\|interior\|local\|global\|normal\|grain_merge\|grain_merge2\|multiply\|multiply2\|divide\|divide2\|screen\|hard_light\|fast\|bilinear\|bilinear8\|bicubic\|spline16\|gaussian\|lanczos\|centroid\|interior\|top\|middle\|bottom\|point\|line\|vertex\|interior\|dummy\|simple\|none\|uppercase\|lowercase\|capitalize\|/"
" syn keyword cartoColor contained {{#colors}}{{.}} {{/colors}}
syn match cartoColor "/\|aliceblue\|antiquewhite\|aqua\|aquamarine\|azure\|beige\|bisque\|black\|blanchedalmond\|blue\|blueviolet\|brown\|burlywood\|cadetblue\|chartreuse\|chocolate\|coral\|cornflowerblue\|cornsilk\|crimson\|cyan\|darkblue\|darkcyan\|darkgoldenrod\|darkgray\|darkgreen\|darkgrey\|darkkhaki\|darkmagenta\|darkolivegreen\|darkorange\|darkorchid\|darkred\|darksalmon\|darkseagreen\|darkslateblue\|darkslategrey\|darkturquoise\|darkviolet\|deeppink\|deepskyblue\|dimgray\|dimgrey\|dodgerblue\|firebrick\|floralwhite\|forestgreen\|fuchsia\|gainsboro\|ghostwhite\|gold\|goldenrod\|gray\|grey\|green\|greenyellow\|honeydew\|hotpink\|indianred\|indigo\|ivory\|khaki\|lavender\|lavenderblush\|lawngreen\|lemonchiffon\|lightblue\|lightcoral\|lightcyan\|lightgoldenrodyellow\|lightgray\|lightgreen\|lightgrey\|lightpink\|lightsalmon\|lightseagreen\|lightskyblue\|lightslategray\|lightslategrey\|lightsteelblue\|lightyellow\|lime\|limegreen\|linen\|magenta\|maroon\|mediumaquamarine\|mediumblue\|mediumorchid\|mediumpurple\|mediumseagreen\|mediumslateblue\|mediumspringgreen\|mediumturquoise\|mediumvioletred\|midnightblue\|mintcream\|mistyrose\|moccasin\|navajowhite\|navy\|oldlace\|olive\|olivedrab\|orange\|orangered\|orchid\|palegoldenrod\|palegreen\|paleturquoise\|palevioletred\|papayawhip\|peachpuff\|peru\|pink\|plum\|powderblue\|purple\|red\|rosybrown\|royalblue\|saddlebrown\|salmon\|sandybrown\|seagreen\|seashell\|sienna\|silver\|skyblue\|slateblue\|slategray\|slategrey\|snow\|springgreen\|steelblue\|tan\|teal\|thistle\|tomato\|turquoise\|violet\|wheat\|white\|whitesmoke\|yellow\|yellowgreen\|transparent\|/"

4
dist/carto.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +0,0 @@
# Generating CartoCSS docs
From the `docs-generator/` directory:
```
$ npm install
```
Then:
```
$ node generate.js
```
Will save docs to `docs/`.

View File

@ -1,23 +0,0 @@
var fs = require('fs'),
path = require('path'),
refs = require('mapnik-reference'),
_ = require('underscore');
function tmpl(x) {
return _.template(fs.readFileSync(path.join(__dirname, x), 'utf-8'));
}
var index = tmpl('index._');
var table = tmpl('symbolizers._');
var versions = Object.keys(refs.version);
for (var v in refs.version) {
var ref = refs.version[v];
fs.writeFileSync(path.join(__dirname, '../docs/' + v + '.md'), index({
symbolizers: ref.symbolizers,
table: table,
version: v,
versions: versions,
_: _
}));
}

View File

@ -1,144 +0,0 @@
# Carto documentation
The following is a list of properties provided in CartoCSS that you can apply to map elements.
<%= table({symbolizers:symbolizers}) %>
### Values
Below is a list of values and an explanation of any expression that can be applied to properties in CartCSS.
### Color
CartoCSS accepts a variety of syntaxes for colors - HTML-style hex values, rgb, rgba, hsl, and hsla. It also supports the predefined HTML colors names, like `yellow` and `blue`.
``` css
#line {
line-color: #ff0;
line-color: #ffff00;
line-color: rgb(255, 255, 0);
line-color: rgba(255, 255, 0, 1);
line-color: hsl(100, 50%, 50%);
line-color: hsla(100, 50%, 50%, 1);
line-color: yellow;
}
```
Especially of note is the support for hsl, which can be [easier to reason about than rgb()](http://mothereffinghsl.com/). Carto also includes several color operation functions [borrowed from less](http://lesscss.org/functions/#color-operations):
``` css
// lighten and darken colors
lighten(#ace, 10%);
darken(#ace, 10%);
// saturate and desaturate
saturate(#550000, 10%);
desaturate(#00ff00, 10%);
// increase or decrease the opacity of a color
fadein(#fafafa, 10%);
fadeout(#fefefe, 14%);
// spin rotates a color around the color wheel by degrees
spin(#ff00ff, 10);
// mix generates a color in between two other colors.
mix(#fff, #000, 50%);
```
These functions all take arguments which can be color variables, literal colors, or the results of other functions operating on colors.
### Float
Float is a fancy way of saying 'number'. In CartoCSS, you specify _just a number_ - unlike CSS, there are no units, but everything is specified in pixels.
``` css
#line {
line-width: 2;
}
```
It's also possible to do simple math with number values:
``` css
#line {
line-width: 4 / 2; // division
line-width: 4 + 2; // addition
line-width: 4 - 2; // subtraction
line-width: 4 * 2; // multiplication
line-width: 4 % 2; // modulus
}
```
### URI
URI is a fancy way of saying URL. When an argument is a URI, you use the same kind of `url('place.png')` notation that you would with HTML. Quotes around the URL aren't required, but are highly recommended. URIs can be paths to places on your computer, or on the internet.
```css
#markers {
marker-file: url('marker.png');
}
```
### String
A string is basically just text. In the case of CartoCSS, you're going to put it in quotes. Strings can be anything, though pay attention to the cases of `text-name` and `shield-name` - they actually will refer to features, which you refer to by putting them in brackets, as seen in the example below.
```css
#labels {
text-name: "[MY_FIELD]";
}
```
### Boolean
Boolean means yes or no, so it accepts the values `true` or `false`.
```css
#markers {
marker-allow-overlap:true;
}
```
### Expressions
Expressions are statements that can include fields, numbers, and other types in a really flexible way. You have run into expressions before, in the realm of 'fields', where you'd specify `"[FIELD]"`, but expressions allow you to drop the quotes and also do quick addition, division, multiplication, and concatenation from within Carto syntax.
```css
#buildings {
building-height: [HEIGHT_FIELD] * 10;
}
```
### Numbers
Numbers are comma-separated lists of one or more number in a specific order. They're used in line dash arrays, in which the numbers specify intervals of line, break, and line again.
```css
#disputedboundary {
line-dasharray: 1, 4, 2;
}
```
### Percentages
In Carto, the percentage symbol, `%` universally means `value/100`. It's meant to be used with ratio-related properties, like opacity rules.
_You should not use percentages as widths, heights, or other properties - unlike CSS, percentages are not relative to cascaded classes or page size, they're, as stated, simply the value divided by one hundred._
```css
#world {
// this syntax
polygon-opacity: 50%;
// is equivalent to
polygon-opacity: 0.5;
}
```
### Functions
Functions are comma-separated lists of one or more functions. For instance, transforms use the `functions` type to allow for transforms within Carto, which are optionally chainable.
```css
#point {
point-transform: scale(2, 2);
}
```

View File

@ -1,20 +0,0 @@
{
"name": "carto-site",
"private": true,
"version": "0.0.0",
"description": "Mapnik Stylesheet Compiler",
"url": "https://github.com/mapbox/carto",
"repositories": [{
"type": "git",
"url": "http://github.com/mapbox/carto.git"
}],
"author": {
"name": "MapBox",
"url": "http://mapbox.com/",
"email": "info@mapbox.com"
},
"dependencies": {
"mapnik-reference": "5.0.x",
"underscore": "~1.3.3"
}
}

View File

@ -1,13 +0,0 @@
<% _(symbolizers).each(function(symbolizer, name) { %>
<% if (name == '*') { %>## All elements<% } else { %>## <%= name %><% } %>
<% _(symbolizer).chain().filter(function(p) { return p.css; }).each(function(p) { %>
##### <%= p.css.replace(/\s/g, '') %> <% if (_.isArray(p.type)) { %>`keyword`<% } else { %>`<%= p.type %>`<% } %>
<% if (_.isArray(p.type)) { %><% _(p.type).each(function(type) { %>`<%= type %>`<% }); %><% } %>
<% if (typeof p['default-value'] !== '') { %>Default Value: <%= p['default-value'] %><% } %>
<% if (p['default-meaning']) { %>_(<%- p['default-meaning'] %>)_<% } %>
<% if (typeof p['range'] !== 'undefined') { %>Range: <%= '' + p['range'] %><% } %>
<% if (p.doc) { %><%- p.doc%><% } %>
* * *
<% }); %>
<% }); %>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7,38 +7,17 @@ tree.functions = {
rgba: function (r, g, b, a) {
var rgb = [r, g, b].map(function (c) { return number(c); });
a = number(a);
if (rgb.some(isNaN) || isNaN(a)) return null;
return new tree.Color(rgb, a);
},
// Only require val
stop: function (val) {
var color, mode;
if (arguments.length > 1) color = arguments[1];
if (arguments.length > 2) mode = arguments[2];
return {
is: 'tag',
val: val,
color: color,
mode: mode,
toString: function(env) {
return '\n\t<stop value="' + val.ev(env) + '"' +
(color ? ' color="' + color.ev(env) + '" ' : '') +
(mode ? ' mode="' + mode.ev(env) + '" ' : '') +
'/>';
}
};
},
hsl: function (h, s, l) {
return this.hsla(h, s, l, 1.0);
},
hsla: function (h, s, l, a) {
h = (number(h) % 360) / 360;
s = number(s); l = number(l); a = number(a);
if ([h, s, l, a].some(isNaN)) return null;
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s,
m1 = l * 2 - m2;
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
var m1 = l * 2 - m2;
return this.rgba(hue(h + 1/3) * 255,
hue(h) * 255,
@ -54,23 +33,18 @@ tree.functions = {
}
},
hue: function (color) {
if (!('toHSL' in color)) return null;
return new tree.Dimension(Math.round(color.toHSL().h));
},
saturation: function (color) {
if (!('toHSL' in color)) return null;
return new tree.Dimension(Math.round(color.toHSL().s * 100), '%');
},
lightness: function (color) {
if (!('toHSL' in color)) return null;
return new tree.Dimension(Math.round(color.toHSL().l * 100), '%');
},
alpha: function (color) {
if (!('toHSL' in color)) return null;
return new tree.Dimension(color.toHSL().a);
},
saturate: function (color, amount) {
if (!('toHSL' in color)) return null;
var hsl = color.toHSL();
hsl.s += amount.value / 100;
@ -78,7 +52,6 @@ tree.functions = {
return hsla(hsl);
},
desaturate: function (color, amount) {
if (!('toHSL' in color)) return null;
var hsl = color.toHSL();
hsl.s -= amount.value / 100;
@ -86,7 +59,6 @@ tree.functions = {
return hsla(hsl);
},
lighten: function (color, amount) {
if (!('toHSL' in color)) return null;
var hsl = color.toHSL();
hsl.l += amount.value / 100;
@ -94,7 +66,6 @@ tree.functions = {
return hsla(hsl);
},
darken: function (color, amount) {
if (!('toHSL' in color)) return null;
var hsl = color.toHSL();
hsl.l -= amount.value / 100;
@ -102,7 +73,6 @@ tree.functions = {
return hsla(hsl);
},
fadein: function (color, amount) {
if (!('toHSL' in color)) return null;
var hsl = color.toHSL();
hsl.a += amount.value / 100;
@ -110,7 +80,6 @@ tree.functions = {
return hsla(hsl);
},
fadeout: function (color, amount) {
if (!('toHSL' in color)) return null;
var hsl = color.toHSL();
hsl.a -= amount.value / 100;
@ -118,7 +87,6 @@ tree.functions = {
return hsla(hsl);
},
spin: function (color, amount) {
if (!('toHSL' in color)) return null;
var hsl = color.toHSL();
var hue = (hsl.h + amount.value) % 360;
@ -174,7 +142,7 @@ var image_filter_functors = [
'x-gradient', 'y-gradient', 'sharpen'];
for (var i = 0; i < image_filter_functors.length; i++) {
var f = image_filter_functors[i];
var f = image_filter_functors[i];
tree.functions[f] = (function(f) {
return function() {
return new tree.ImageFilter(f);
@ -186,12 +154,8 @@ tree.functions['agg-stack-blur'] = function(x, y) {
return new tree.ImageFilter('agg-stack-blur', [x, y]);
};
tree.functions['scale-hsla'] = function(h0,h1,s0,s1,l0,l1,a0,a1) {
return new tree.ImageFilter('scale-hsla', [h0,h1,s0,s1,l0,l1,a0,a1]);
};
function hsla(h) {
return tree.functions.hsla(h.h, h.s, h.l, h.a);
function hsla(hsla) {
return tree.functions.hsla(hsla.h, hsla.s, hsla.l, hsla.a);
}
function number(n) {
@ -200,7 +164,7 @@ function number(n) {
} else if (typeof(n) === 'number') {
return n;
} else {
return NaN;
throw new Error('Color functions take numbers as parameters.');
}
}

View File

@ -1,27 +1,9 @@
var util = require('util'),
fs = require('fs'),
path = require('path');
function getVersion() {
if (process.browser) {
return require('../../package.json').version.split('.');
} else if (parseInt(process.version.split('.')[1], 10) > 4) {
return require('../../package.json').version.split('.');
} else {
// older node
var package_json = JSON.parse(fs.readFileSync(path.join(__dirname,'../../package.json')));
return package_json.version.split('.');
}
}
var util = require('util');
var carto = {
version: getVersion(),
version: [0, 4, 7],
Parser: require('./parser').Parser,
Renderer: require('./renderer').Renderer,
tree: require('./tree'),
RendererJS: require('./renderer_js'),
default_reference: require('./torque-reference'),
// @TODO
writeError: function(ctx, options) {
@ -53,10 +35,10 @@ var carto = {
if (typeof(extract[2]) === 'string') {
error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
}
error = options.indent + error.join('\n' + options.indent) + '\x1B[0m\n';
error = options.indent + error.join('\n' + options.indent) + '\033[0m\n';
message = options.indent + message + stylize(ctx.message, 'red');
if (ctx.filename) (message += stylize(' in ', 'red') + ctx.filename);
ctx.filename && (message += stylize(' in ', 'red') + ctx.filename);
util.error(message, error);
@ -68,34 +50,15 @@ var carto = {
}
};
require('./tree/call');
require('./tree/color');
require('./tree/comment');
require('./tree/definition');
require('./tree/dimension');
require('./tree/element');
require('./tree/expression');
require('./tree/filterset');
require('./tree/filter');
require('./tree/field');
require('./tree/keyword');
require('./tree/layer');
require('./tree/literal');
require('./tree/operation');
require('./tree/quoted');
require('./tree/imagefilter');
require('./tree/reference');
require('./tree/rule');
require('./tree/ruleset');
require('./tree/selector');
require('./tree/style');
require('./tree/url');
require('./tree/value');
require('./tree/variable');
require('./tree/zoom');
require('./tree/invalid');
require('./tree/fontset');
require('./tree/frame_offset');
[ 'call', 'color', 'comment', 'definition', 'dimension',
'directive', 'element', 'expression', 'filterset', 'filter', 'field',
'keyword', 'layer', 'literal', 'operation', 'quoted', 'imagefilter',
'reference', 'rule', 'ruleset', 'selector', 'style', 'url', 'value',
'variable', 'zoom', 'invalid', 'fontset'
].forEach(function(n) {
require('./tree/' + n);
});
require('./functions');
for (var k in carto) { exports[k] = carto[k]; }
@ -111,6 +74,7 @@ function stylize(str, style) {
'red' : [31, 39],
'grey' : [90, 39]
};
return '\x1B[' + styles[style][0] + 'm' + str +
'\x1B[' + styles[style][1] + 'm';
return '\033[' + styles[style][0] + 'm' + str +
'\033[' + styles[style][1] + 'm';
}

View File

@ -1,10 +1,46 @@
var carto = exports,
tree = require('./tree'),
_ = global._ || require('underscore');
var carto, tree, _;
if (typeof(process) !== 'undefined') {
carto = exports;
tree = require('./tree');
_ = require('underscore');
} else {
if (typeof(window.carto) === 'undefined') { window.carto = {}; }
carto = window.carto;
tree = window.carto.tree = {};
}
// carto.js - parser
//
// A relatively straight-forward predictive parser.
// There is no tokenization/lexing stage, the input is parsed
// in one sweep.
//
// To make the parser fast enough to run in the browser, several
// optimization had to be made:
//
// - Matching and slicing on a huge input is often cause of slowdowns.
// The solution is to chunkify the input into smaller strings.
// The chunks are stored in the `chunks` var,
// `j` holds the current chunk index, and `current` holds
// the index of the current chunk in relation to `input`.
// This gives us an almost 4x speed-up.
//
// - In many cases, we don't need to match individual tokens;
// for example, if a value doesn't hold any variables, operations
// or dynamic references, the parser can effectively 'skip' it,
// treating it as a literal.
// An example would be '1px solid #000' - which evaluates to itself,
// we don't need to know what the individual components are.
// The drawback, of course is that you don't get the benefits of
// syntax-checking on the CSS. This gives us a 50% speed-up in the parser,
// and a smaller speed-up in the code-gen.
//
//
// Token matching is done with the `$` function, which either takes
// a terminal string or regexp, or a non-terminal function to call.
// It also takes care of moving all the indices forwards.
carto.Parser = function Parser(env) {
var input, // LeSS input string
i, // current index in `input`
@ -22,6 +58,29 @@ carto.Parser = function Parser(env) {
// have been imported through `@import`.
var finish = function() {};
var imports = this.imports = {
paths: env && env.paths || [], // Search paths, when importing
queue: [], // Files which haven't been imported yet
files: {}, // Holds the imported parse trees
mime: env && env.mime, // MIME type of .carto files
push: function(path, callback) {
var that = this;
this.queue.push(path);
//
// Import a file asynchronously
//
carto.Parser.importer(path, this.paths, function(root) {
that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue
that.files[path] = root; // Store the root
callback(root);
if (that.queue.length === 0) { finish(); } // Call `finish` if we're done importing
}, env);
}
};
function save() {
temp = chunks[j];
memo = i;
@ -45,12 +104,17 @@ carto.Parser = function Parser(env) {
function $(tok) {
var match, args, length, c, index, endIndex, k;
//
// Non-terminal
//
if (tok instanceof Function) {
return tok.call(parser.parsers);
//
// Terminal
// Either match a single character in the input,
// or match a regexp in the current chunk (chunk[j]).
//
// Either match a single character in the input,
// or match a regexp in the current chunk (chunk[j]).
//
} else if (typeof(tok) === 'string') {
match = input.charAt(i) === tok ? tok : null;
length = 1;
@ -98,15 +162,14 @@ carto.Parser = function Parser(env) {
if (typeof(tok) === 'string') {
return input.charAt(i) === tok;
} else {
return !!tok.test(chunks[j]);
if (tok.test(chunks[j])) {
return true;
} else {
return false;
}
}
}
function extractErrorLine(style, errorIndex) {
return (style.slice(0, errorIndex).match(/\n/g) || '').length + 1;
}
// Make an error object from a passed set of properties.
// Accepted properties:
// - `message`: Text of the error message.
@ -114,9 +177,8 @@ carto.Parser = function Parser(env) {
// - `index`: Char. index where the error occurred.
function makeError(err) {
var einput;
var errorTemplate;
_.defaults(err, {
_(err).defaults({
index: furthest,
filename: env.filename,
message: 'Parse error.',
@ -130,12 +192,11 @@ carto.Parser = function Parser(env) {
einput = input;
}
err.line = extractErrorLine(einput, err.index);
err.line = (einput.slice(0, err.index).match(/\n/g) || '').length + 1;
for (var n = err.index; n >= 0 && einput.charAt(n) !== '\n'; n--) {
err.column++;
}
errorTemplate = _.template('<%=filename%>:<%=line%>:<%=column%> <%=message%>');
return new Error(errorTemplate(err));
return new Error(_('<%=filename%>:<%=line%>:<%=column%> <%=message%>').template(err));
}
this.env = env = env || {};
@ -143,9 +204,9 @@ carto.Parser = function Parser(env) {
this.env.inputs = this.env.inputs || {};
// The Parser
parser = {
return parser = {
extractErrorLine: extractErrorLine,
imports: imports,
//
// Parse an input string into an abstract syntax tree.
// Throws an error on parse errors.
@ -160,19 +221,17 @@ carto.Parser = function Parser(env) {
}
var early_exit = false;
// Split the input into chunks.
chunks = (function (chunks) {
chunks = (function(chunks) {
var j = 0,
skip = /(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,
skip = /[^"'`\{\}\/]+/g,
comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,
level = 0,
match,
chunk = chunks[0],
inParam;
inString;
for (var i = 0, c, cc; i < input.length;) {
chunker: for (var i = 0, c, cc; i < input.length; i++) {
skip.lastIndex = i;
if (match = skip.exec(input)) {
if (match.index === i) {
@ -181,65 +240,59 @@ carto.Parser = function Parser(env) {
}
}
c = input.charAt(i);
comment.lastIndex = string.lastIndex = i;
comment.lastIndex = i;
if (match = string.exec(input)) {
if (match.index === i) {
i += match[0].length;
chunk.push(match[0]);
continue;
}
}
if (!inParam && c === '/') {
if (!inString && c === '/') {
cc = input.charAt(i + 1);
if (cc === '/' || cc === '*') {
if (match = comment.exec(input)) {
if (match.index === i) {
i += match[0].length;
i += match[0].length - 1;
chunk.push(match[0]);
continue;
c = input.charAt(i);
continue chunker;
}
}
}
}
switch (c) {
case '{': if (! inParam) { level ++; chunk.push(c); break; }
case '}': if (! inParam) { level --; chunk.push(c); chunks[++j] = chunk = []; break; }
case '(': if (! inParam) { inParam = true; chunk.push(c); break; }
case ')': if ( inParam) { inParam = false; chunk.push(c); break; }
default: chunk.push(c);
if (c === '{' && !inString) { level++;
chunk.push(c);
} else if (c === '}' && !inString) { level--;
chunk.push(c);
chunks[++j] = chunk = [];
} else {
if (c === '"' || c === "'" || c === '`') {
if (! inString) {
inString = c;
} else {
inString = inString === c ? false : inString;
}
}
chunk.push(c);
}
i++;
}
if (level !== 0) {
error = {
index: i - 1,
type: 'Parse',
message: (level > 0) ? "missing closing `}`" : "missing opening `{`"
};
if (level > 0) {
// TODO: make invalid instead
throw makeError({
message: 'Missing closing `}`',
index: i
});
}
return chunks.map(function (c) { return c.join(''); });
return chunks.map(function(c) { return c.join(''); });
})([[]]);
if (error) {
throw makeError(error);
}
// Start with the primary rule.
// The whole syntax tree is held under a Ruleset node,
// with the `root` property set to true, so no `{}` are
// output.
// output. The callback is called when the input is parsed.
root = new tree.Ruleset([], $(this.parsers.primary));
root.root = true;
// Get an array of Ruleset objects, flattened
// and sorted according to specificitySort
root.toList = (function() {
var line, lines, column;
return function(env) {
env.error = function(e) {
if (!env.errors) env.errors = new Error('');
@ -251,7 +304,6 @@ carto.Parser = function Parser(env) {
};
env.frames = env.frames || [];
// call populates Invalid-caused errors
var definitions = this.flatten([], [], env);
definitions.sort(specificitySort);
@ -276,9 +328,23 @@ carto.Parser = function Parser(env) {
return bs[3] - as[3];
};
// If `i` is smaller than the `input.length - 1`,
// it means the parser wasn't able to parse the whole
// string, so we've got a parsing error.
//
// We try to extract a \n delimited string,
// showing the line where the parse error occured.
// We split it up into two parts (the part which parsed,
// and the part which didn't), so we can color them differently.
if (i < input.length - 1) throw makeError({
message:'Parse error.',
index:i
});
return root;
},
//
// Here in, the parsing rules/functions
//
// The basic structure of the syntax tree generated is as follows:
@ -288,7 +354,9 @@ carto.Parser = function Parser(env) {
// In general, most rules will try to parse a token with the `$()` function, and if the return
// value is truly, will return a new node, of the relevant type. Sometimes, we need to check
// first, before parsing, that's when we use `peek()`.
//
parsers: {
//
// The `primary` rule is the *entry* and *exit* point of the parser.
// The rules here can appear at any level of the parse tree.
//
@ -302,13 +370,14 @@ carto.Parser = function Parser(env) {
//
// Only at one point is the primary rule not called from the
// block rule: at the root level.
//
primary: function() {
var node, root = [];
while ((node = $(this.rule) || $(this.ruleset) ||
$(this.comment)) ||
$(/^[\s\n]+/) || (node = $(this.invalid))) {
if (node) root.push(node);
node && root.push(node);
}
return root;
},
@ -318,7 +387,7 @@ carto.Parser = function Parser(env) {
// To fail gracefully, match everything until a semicolon or linebreak.
if (chunk) {
return new tree.Invalid(chunk, memo);
return new(tree.Invalid)(chunk, memo);
}
},
@ -337,10 +406,15 @@ carto.Parser = function Parser(env) {
}
},
//
// Entities are tokens which can be found inside an Expression
//
entities: {
// A string, which supports escaping " and ' "milky way" 'he\'s the one!'
//
// A string, which supports escaping " and '
//
// "milky way" 'he\'s the one!'
//
quoted: function() {
if (input.charAt(i) !== '"' && input.charAt(i) !== "'") return;
var str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/);
@ -349,12 +423,15 @@ carto.Parser = function Parser(env) {
}
},
// A reference to a Mapnik field, like [NAME]
// A reference to a Mapnik field, like
//
// [NAME]
//
// Behind the scenes, this has the same representation, but Carto
// needs to be careful to warn when unsupported operations are used.
field: function() {
if (! $('[')) return;
var field_name = $(/(^[^\]]+)/);
var field_name = $(/(^[a-zA-Z0-9\-_]+)/);
if (! $(']')) return;
if (field_name) return new tree.Field(field_name[1]);
},
@ -367,38 +444,41 @@ carto.Parser = function Parser(env) {
}
},
// A catch-all word, such as: hard-light
// These can start with either a letter or a dash (-),
// and then contain numbers, underscores, and letters.
// A catch-all word, such as:
//
// hard-light
//
// These can start with either a letter or a dash (-),
// and then contain numbers, underscores, and letters.
keyword: function() {
var k = $(/^[A-Za-z-]+[A-Za-z-0-9_]*/);
if (k) { return new tree.Keyword(k); }
},
// A function call like rgb(255, 0, 255)
// A function call
//
// rgb(255, 0, 255)
//
// The arguments are parsed with the `entities.arguments` parser.
call: function() {
var name, args;
if (!(name = /^([\w\-]+|%)\(/.exec(chunks[j]))) return;
if (! (name = /^([\w\-]+|%)\(/.exec(chunks[j]))) return;
name = name[1];
name = name[1].toLowerCase();
if (name === 'url') {
// url() is handled by the url parser instead
return null;
} else {
i += name.length;
i += name.length + 1;
}
$('('); // Parse the '(' and consume whitespace.
args = $(this.entities['arguments']);
args = $(this.entities.arguments);
if (!$(')')) return;
if (name) {
return new tree.Call(name, args, i);
return new tree.Call(name, args, i);
}
},
// Arguments are comma-separated expressions
@ -414,9 +494,8 @@ carto.Parser = function Parser(env) {
},
literal: function() {
return $(this.entities.dimension) ||
$(this.entities.keywordcolor) ||
$(this.entities.hexcolor) ||
$(this.entities.quoted);
$(this.entities.color) ||
$(this.entities.quoted);
},
// Parse url() tokens
@ -433,9 +512,8 @@ carto.Parser = function Parser(env) {
if (! $(')')) {
return new tree.Invalid(value, memo, 'Missing closing ) in URL.');
} else {
return new tree.URL((typeof value.value !== 'undefined' ||
value instanceof tree.Variable) ?
value : new tree.Quoted(value));
return new tree.URL((value.value || value instanceof tree.Variable) ?
value : new tree.Quoted(value), imports.paths);
}
},
@ -453,35 +531,41 @@ carto.Parser = function Parser(env) {
}
},
hexcolor: function() {
// A Hexadecimal color
//
// #4F3C2F
//
// `rgb` and `hsl` colors are parsed through the `entities.call` parser.
color: function() {
var rgb;
if (input.charAt(i) === '#' && (rgb = $(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/))) {
return new tree.Color(rgb[1]);
}
},
keywordcolor: function() {
var rgb = chunks[j].match(/^[a-z]+/);
if (rgb && rgb[0] in tree.Reference.data.colors) {
return new tree.Color(tree.Reference.data.colors[$(/^[a-z]+/)]);
} else {
rgb = chunks[j].match(/^[a-z]+/);
if (rgb && rgb[0] in tree.Reference.data.colors) {
return new tree.Color(tree.Reference.data.colors[$(/^[a-z]+/)]);
}
}
},
// A Dimension, that is, a number and a unit. The only
// unit that has an effect is %
//
// 0.5em 95%
dimension: function() {
var c = input.charCodeAt(i);
if ((c > 57 || c < 45) || c === 47) return;
var value = $(/^(-?\d*\.?\d+(?:[eE][-+]?\d+)?)(\%|\w+)?/);
var value = $(/^(-?\d*\.?\d+)(\%|\w+)?/);
if (value) {
return new tree.Dimension(value[1], value[2], memo);
}
}
},
// The variable part of a variable definition.
// Used in the `rule` parser. Like @fink:
// The variable part of a variable definition. Used in the `rule` parser
//
// @fink:
variable: function() {
var name;
@ -490,20 +574,24 @@ carto.Parser = function Parser(env) {
}
},
//
// Entities are the smallest recognized token,
// and can be found inside a rule's value.
//
entity: function() {
return $(this.entities.call) ||
$(this.entities.literal) ||
return $(this.entities.literal) ||
$(this.entities.field) ||
$(this.entities.variable) ||
$(this.entities.url) ||
$(this.entities.call) ||
$(this.entities.keyword);
},
//
// A Rule terminator. Note that we use `peek()` to check for '}',
// because the `block` rule will be expecting it, but we still need to make sure
// it's there, if ';' was ommitted.
//
end: function() {
return $(';') || peek('}');
},
@ -524,17 +612,15 @@ carto.Parser = function Parser(env) {
// Selectors are made out of one or more Elements, see above.
selector: function() {
var a, attachment,
e, elements = [],
f, filters = new tree.Filterset(),
z, zooms = [],
frame_offset = tree.FrameOffset.none;
segments = 0, conditions = 0;
var a, attachment;
var e, elements = [];
var f, filters = new tree.Filterset();
var z, zoom = tree.Zoom.all;
var segments = 0, conditions = 0;
while (
(e = $(this.element)) ||
(z = $(this.zoom)) ||
(fo = $(this.frame_offset)) ||
(f = $(this.filter)) ||
(a = $(this.attachment))
) {
@ -542,24 +628,15 @@ carto.Parser = function Parser(env) {
if (e) {
elements.push(e);
} else if (z) {
zooms.push(z);
conditions++;
} else if (fo) {
frame_offset = fo;
zoom &= z;
conditions++;
} else if (f) {
var err = filters.add(f);
if (err) {
throw makeError({
message: err,
index: i - 1
});
}
filters.add(f);
conditions++;
} else if (attachment) {
throw makeError({
message: 'Encountered second attachment name.',
index: i - 1
message:'Encountered second attachment name.',
index:i - 1
});
} else {
attachment = a;
@ -570,7 +647,7 @@ carto.Parser = function Parser(env) {
}
if (segments) {
return new tree.Selector(filters, zooms, frame_offset, elements, attachment, conditions, memo);
return new tree.Selector(filters, zoom, elements, attachment, conditions, memo);
}
},
@ -578,59 +655,30 @@ carto.Parser = function Parser(env) {
save();
var key, op, val;
if (! $('[')) return;
if (key = $(/^[a-zA-Z0-9\-_]+/) ||
$(this.entities.quoted) ||
$(this.entities.variable) ||
$(this.entities.keyword) ||
$(this.entities.field)) {
// TODO: remove at 1.0.0
if (key instanceof tree.Quoted) {
key = new tree.Field(key.toString());
}
if (key = $(/^[a-zA-Z0-9\-_]+/) || $(this.entities.quoted) || $(this.entities.variable)) {
if ((op = $(this.entities.comparison)) &&
(val = $(this.entities.quoted) ||
$(this.entities.variable) ||
$(this.entities.dimension) ||
$(this.entities.keyword) ||
$(this.entities.field))) {
if (! $(']')) {
throw makeError({
message: 'Missing closing ] of filter.',
index: memo - 1
});
}
if (!key.is) key = new tree.Field(key);
(val = $(this.entities.quoted) || $(this.entities.variable) || $(/^[\w\-\.]+/))) {
if (! $(']')) return;
return new tree.Filter(key, op, val, memo, env.filename);
}
}
},
frame_offset: function() {
save();
var op, val;
if ($(/^\[\s*frame-offset/g) &&
(op = $(this.entities.comparison)) &&
(val = $(/^\d+/)) &&
$(']')) {
return tree.FrameOffset(op, val, memo);
}
},
zoom: function() {
save();
var op, val;
if ($(/^\[\s*zoom/g) &&
if ($(/^\[zoom/g) &&
(op = $(this.entities.comparison)) &&
(val = $(this.entities.variable) || $(this.entities.dimension)) && $(']')) {
return new tree.Zoom(op, val, memo);
} else {
// backtrack
restore();
(val = $(/^\d+/)) &&
$(']')) {
return tree.Zoom(op, val, memo);
}
},
//
// The `block` rule is used by `ruleset`
// It's a wrapper around the `primary` rule, with added `{}`.
//
block: function() {
var content;
@ -639,20 +687,18 @@ carto.Parser = function Parser(env) {
}
},
//
// div, .class, body > p {...}
//
ruleset: function() {
var selectors = [], s, f, l, rules, filters = [];
save();
while (s = $(this.selector)) {
selectors.push(s);
while ($(this.comment)) {}
if (! $(',')) { break; }
while ($(this.comment)) {}
}
if (s) {
while ($(this.comment)) {}
if (! $(',')) { break }
}
if (s) $(this.comment);
if (selectors.length > 0 && (rules = $(this.block))) {
if (selectors.length === 1 &&
@ -668,12 +714,11 @@ carto.Parser = function Parser(env) {
restore();
}
},
rule: function() {
var name, value, c = input.charAt(i);
save();
if (c === '.' || c === '#') { return; }
if (c === '.' || c === '#' || c === '&') { return }
if (name = $(this.variable) || $(this.property)) {
value = $(this.value);
@ -716,32 +761,16 @@ carto.Parser = function Parser(env) {
if (! $(',')) { break; }
}
if (expressions.length > 1) {
return new tree.Value(expressions.map(function(e) {
return e.value[0];
}));
} else if (expressions.length === 1) {
if (expressions.length > 0) {
return new tree.Value(expressions);
}
},
// A sub-expression, contained by parenthensis
sub: function() {
var e, expressions = [];
var e;
if ($('(')) {
while (e = $(this.expression)) {
expressions.push(e);
if (! $(',')) { break; }
}
$(')');
}
if (expressions.length > 1) {
return new tree.Value(expressions.map(function(e) {
return e.value[0];
}));
} else if (expressions.length === 1) {
return new tree.Value(expressions);
if ($('(') && (e = $(this.expression)) && $(')')) {
return e;
}
},
// This is a misnomer because it actually handles multiplication
@ -773,14 +802,16 @@ carto.Parser = function Parser(env) {
},
// Expressions either represent mathematical operations,
// or white-space delimited Entities. @var * 2
// or white-space delimited Entities.
//
// 1px solid black
// @var * 2
expression: function() {
var e, delim, entities = [], d;
while (e = $(this.addition) || $(this.entity)) {
entities.push(e);
}
if (entities.length > 0) {
return new tree.Expression(entities);
}
@ -791,5 +822,5 @@ carto.Parser = function Parser(env) {
}
}
};
return parser;
};

View File

@ -1,98 +1,39 @@
var _ = global._ || require('underscore');
var _ = require('underscore');
var carto = require('./index');
var tree = require('./tree');
carto.Renderer = function Renderer(env, options) {
this.env = env || {};
this.options = options || {};
this.options.mapnik_version = this.options.mapnik_version || '3.0.0';
this.options.mapnik_version = this.options.mapnik_version || 'latest';
};
/**
* Prepare a MSS document (given as an string) into a
* XML Style fragment (mostly useful for debugging)
*
* @param {String} data the mss contents as a string.
*/
carto.Renderer.prototype.renderMSS = function render(data) {
// Prepare a MML document (given as an object) into a
// fully-localized XML file ready for Mapnik2 consumption
//
// - @param {String} str the JSON file as a string.
// - @param {Object} env renderer environment options.
carto.Renderer.prototype.render = function render(m, callback) {
// effects is a container for side-effects, which currently
// are limited to FontSets.
var env = _.defaults(this.env, {
var env = _(this.env).defaults({
benchmark: false,
validation_data: false,
effects: []
});
if (!carto.tree.Reference.setVersion(this.options.mapnik_version)) {
throw new Error("Could not set mapnik version to " + this.options.mapnik_version);
}
var output = [];
var styles = [];
if (env.benchmark) console.time('Parsing MSS');
var parser = (carto.Parser(env)).parse(data);
if (env.benchmark) console.timeEnd('Parsing MSS');
if (env.benchmark) console.time('Rule generation');
var rule_list = parser.toList(env);
if (env.benchmark) console.timeEnd('Rule generation');
if (env.benchmark) console.time('Rule inheritance');
var rules = inheritDefinitions(rule_list, env);
if (env.benchmark) console.timeEnd('Rule inheritance');
if (env.benchmark) console.time('Style sort');
var sorted = sortStyles(rules,env);
if (env.benchmark) console.timeEnd('Style sort');
if (env.benchmark) console.time('Total Style generation');
for (var k = 0, rule, style_name; k < sorted.length; k++) {
rule = sorted[k];
style_name = 'style' + (rule.attachment !== '__default__' ? '-' + rule.attachment : '');
styles.push(style_name);
var bench_name = '\tStyle "'+style_name+'" (#'+k+') toXML';
if (env.benchmark) console.time(bench_name);
// env.effects can be modified by this call
output.push(carto.tree.StyleXML(style_name, rule.attachment, rule, env));
if (env.benchmark) console.timeEnd(bench_name);
}
if (env.benchmark) console.timeEnd('Total Style generation');
if (env.errors) throw env.errors;
return output.join('\n');
};
/**
* Prepare a MML document (given as an object) into a
* fully-localized XML file ready for Mapnik2 consumption
*
* @param {String} m - the JSON file as a string.
*/
carto.Renderer.prototype.render = function render(m) {
// effects is a container for side-effects, which currently
// are limited to FontSets.
var env = _.defaults(this.env, {
benchmark: false,
validation_data: false,
effects: [],
ppi: 90.714
});
if (!carto.tree.Reference.setVersion(this.options.mapnik_version)) {
throw new Error("Could not set mapnik version to " + this.options.mapnik_version);
}
tree.Reference.setVersion(this.options.mapnik_version);
var output = [];
// Transform stylesheets into definitions.
var definitions = _.chain(m.Stylesheet)
// Transform stylesheets into rulesets.
var rulesets = _(m.Stylesheet).chain()
.map(function(s) {
if (typeof s == 'string') {
throw new Error("Stylesheet object is expected not a string: '" + s + "'");
}
// Passing the environment from stylesheet to stylesheet,
// allows frames and effects to be maintained.
env = _.extend(env, {filename:s.id});
env = _(env).extend({filename:s.id});
// @TODO try/catch?
var time = +new Date(),
root = (carto.Parser(env)).parse(s.data);
if (env.benchmark)
@ -102,65 +43,51 @@ carto.Renderer.prototype.render = function render(m) {
.flatten()
.value();
function appliesTo(name, classIndex) {
return function(definition) {
return definition.appliesTo(l.name, classIndex);
};
}
// Iterate through layers and create styles custom-built
// for each of them, and apply those styles to the layers.
var styles, l, classIndex, rules, sorted, matching;
for (var i = 0; i < m.Layer.length; i++) {
l = m.Layer[i];
styles = [];
classIndex = {};
if (env.benchmark) console.warn('processing layer: ' + l.id);
m.Layer.forEach(function(l) {
l.styles = [];
// Classes are given as space-separated alphanumeric strings.
var classes = (l['class'] || '').split(/\s+/g);
for (var j = 0; j < classes.length; j++) {
classIndex[classes[j]] = true;
}
matching = definitions.filter(appliesTo(l.name, classIndex));
rules = inheritDefinitions(matching, env);
sorted = sortStyles(rules, env);
var matching = rulesets.filter(function(definition) {
return definition.appliesTo(l.name, classes);
});
var rules = inheritRules(matching, env);
var sorted = sortStyles(rules, env);
_(sorted).each(function(rule) {
var style = new tree.Style(l.name, rule.attachment, rule);
if (style) {
l.styles.push(style.name);
for (var k = 0, rule, style_name; k < sorted.length; k++) {
rule = sorted[k];
style_name = l.name + (rule.attachment !== '__default__' ? '-' + rule.attachment : '');
// env.effects can be modified by this call
var styleXML = carto.tree.StyleXML(style_name, rule.attachment, rule, env);
if (styleXML) {
output.push(styleXML);
styles.push(style_name);
// env.effects can be modified by this call
output.push(style.toXML(env));
}
});
if (l.styles.length) {
output.push((new carto.tree.Layer(l)).toXML());
}
output.push(carto.tree.LayerXML(l, styles));
}
});
output.unshift(env.effects.map(function(e) {
return e.toXML(env);
}).join('\n'));
var map_properties = getMapProperties(m, definitions, env);
var map_properties;
try {
map_properties = getMapProperties(m, rulesets, env);
} catch (err) {
env.error(err);
return callback(err);
}
// Exit on errors.
if (env.errors) throw env.errors;
if (env.errors) return callback(env.errors);
// Pass TileJSON and other custom parameters through to Mapnik XML.
var parameters = _.reduce(m, function(memo, v, k) {
var parameters = _(m).reduce(function(memo, v, k) {
if (!v && v !== 0) return memo;
switch (k) {
// Known skippable properties.
case 'srs':
case 'Layer':
case 'Stylesheet':
break;
// Non URL-bound TileJSON properties.
case 'bounds':
case 'center':
@ -186,16 +113,6 @@ carto.Renderer.prototype.render = function render(m) {
memo.push(' <Parameter name="interactivity_layer">' + v.layer + '</Parameter>');
memo.push(' <Parameter name="interactivity_fields">' + v.fields + '</Parameter>');
break;
// Support any additional scalar properties.
default:
if ('string' === typeof v) {
memo.push(' <Parameter name="' + k + '"><![CDATA[' + v + ']]></Parameter>');
} else if ('number' === typeof v) {
memo.push(' <Parameter name="' + k + '">' + v + '</Parameter>');
} else if ('boolean' === typeof v) {
memo.push(' <Parameter name="' + k + '">' + v + '</Parameter>');
}
break;
}
return memo;
}, []);
@ -205,36 +122,28 @@ carto.Renderer.prototype.render = function render(m) {
'\n</Parameters>\n'
);
var properties = _.map(map_properties, function(v) { return ' ' + v; }).join('');
var properties = _(map_properties).map(function(v) { return ' ' + v; }).join('');
output.unshift(
'<?xml version="1.0" ' +
'encoding="utf-8"?>\n' +
'<!DOCTYPE Map[]>\n' +
'<Map' + properties +'>\n');
'<Map' + properties + ' maximum-extent="-20037508.34,-20037508.34,20037508.34,20037508.34">\n');
output.push('</Map>');
return output.join('\n');
return callback(null, output.join('\n'));
};
/**
* This function currently modifies 'current'
* @param {Array} current current list of rules
* @param {Object} definition a Definition object to add to the rules
* @param {Object} byFilter an object/dictionary of existing filters. This is
* actually keyed `attachment->filter`
* @param {Object} env the current environment
*/
function addRules(current, definition, byFilter, env) {
var newFilters = definition.filters,
newRules = definition.rules,
updatedFilters, clone, previous;
// This function currently modifies 'current'
function addRules(current, definition, existing) {
var newFilters = definition.filters;
var newRules = definition.rules;
var updatedFilters, clone, previous;
// The current definition might have been split up into
// multiple definitions already.
for (var k = 0; k < current.length; k++) {
updatedFilters = current[k].filters.cloneWith(newFilters);
if (updatedFilters) {
previous = byFilter[updatedFilters];
previous = existing[updatedFilters];
if (previous) {
// There's already a definition with those exact
// filters. Add the current definitions' rules
@ -251,63 +160,32 @@ function addRules(current, definition, byFilter, env) {
// to make sure that in the next loop iteration, we're
// not performing the same task for this element again,
// hence the k++.
byFilter[updatedFilters] = clone;
current.splice(k, 0, clone);
k++;
}
}
} else if (updatedFilters === null) {
// if updatedFilters is null, then adding the filters doesn't
// invalidate or split the selector, so we addRules to the
// combined selector
// Filters can be added, but they don't change the
// filters. This means we don't have to split the
// definition.
//
// this is cloned here because of shared classes, see
// sharedclass.mss
current[k] = current[k].clone();
current[k].addRules(newRules);
}
// if updatedFeatures is false, then the filters split the rule,
// so they aren't the same inheritance chain
}
return current;
}
/**
* Apply inherited styles from their ancestors to them.
*
* called either once per render (in the case of mss) or per layer
* (for mml)
*
* @param {Object} definitions - a list of definitions objects
* that contain .rules
* @param {Object} env - the environment
* @return {Array<Array>} an array of arrays is returned,
* in which each array refers to a specific attachment
*/
function inheritDefinitions(definitions, env) {
// Apply inherited styles from their ancestors to them.
function inheritRules(definitions, env) {
var inheritTime = +new Date();
// definitions are ordered by specificity,
// high (index 0) to low
var byAttachment = {},
byFilter = {};
var byAttachment = {}, byFilter = {};
var result = [];
var current, previous, attachment;
// Evaluate the filters specified by each definition with the given
// environment to correctly resolve variable references
definitions.forEach(function(d) {
d.filters.ev(env);
});
for (var i = 0; i < definitions.length; i++) {
attachment = definitions[i].attachment;
current = [definitions[i]];
if (!byAttachment[attachment]) {
byAttachment[attachment] = [];
byAttachment[attachment].attachment = attachment;
@ -315,11 +193,12 @@ function inheritDefinitions(definitions, env) {
result.push(byAttachment[attachment]);
}
current = [definitions[i]];
// Iterate over all subsequent rules.
for (var j = i + 1; j < definitions.length; j++) {
if (definitions[j].attachment === attachment) {
// Only inherit rules from the same attachment.
current = addRules(current, definitions[j], byFilter[attachment], env);
current = addRules(current, definitions[j], byFilter);
}
}
@ -332,54 +211,46 @@ function inheritDefinitions(definitions, env) {
if (env.benchmark) console.warn('Inheritance time: ' + ((new Date() - inheritTime)) + 'ms');
return result;
}
// Sort styles by the minimum index of their rules.
// This sorts a slice of the styles, so it returns a sorted
// array but does not change the input.
function sortStylesIndex(a, b) { return b.index - a.index; }
function sortStyles(styles, env) {
for (var i = 0; i < styles.length; i++) {
var style = styles[i];
styles.forEach(function(style) {
style.index = Infinity;
for (var b = 0; b < style.length; b++) {
var rules = style[b].rules;
for (var r = 0; r < rules.length; r++) {
var rule = rules[r];
style.forEach(function(block) {
block.rules.forEach(function(rule) {
if (rule.index < style.index) {
style.index = rule.index;
}
}
}
}
});
});
});
var result = styles.slice();
result.sort(sortStylesIndex);
result.sort(function(a, b) {
return b.index - a.index;
});
return result;
}
/**
* Find a rule like Map { background-color: #fff; },
* if any, and return a list of properties to be inserted
* into the <Map element of the resulting XML. Translates
* properties of the mml object at `m` directly into XML
* properties.
*
* @param {Object} m the mml object.
* @param {Array} definitions the output of toList.
* @param {Object} env
* @return {String} rendered properties.
*/
function getMapProperties(m, definitions, env) {
// Find a rule like Map { background-color: #fff; },
// if any, and return a list of properties to be inserted
// into the <Map element of the resulting XML. Translates
// properties of the mml object at `m` directly into XML
// properties.
//
// - @param {Object} m the mml object.
// - @param {Array} rulesets the output of toList.
// - @param {Object} env.
// - @return {String} rendered properties.
function getMapProperties(m, rulesets, env) {
var rules = {};
var symbolizers = carto.tree.Reference.data.symbolizers.map;
var symbolizers = tree.Reference.data.symbolizers.map;
_(m).each(function(value, key) {
if (key in symbolizers) rules[key] = key + '="' + value + '"';
});
definitions.filter(function(r) {
rulesets.filter(function(r) {
return r.elements.join('') === 'Map';
}).forEach(function(r) {
for (var i = 0; i < r.rules.length; i++) {
@ -390,13 +261,10 @@ function getMapProperties(m, definitions, env) {
index: r.rules[i].index
});
}
rules[key] = r.rules[i].ev(env).toXML(env);
rules[key] = r.rules[i].eval(env).toXML(env);
}
});
return rules;
}
module.exports = carto;
module.exports.addRules = addRules;
module.exports.inheritDefinitions = inheritDefinitions;
module.exports.sortStyles = sortStyles;

View File

@ -1,302 +0,0 @@
(function(carto) {
var tree = require('./tree');
var _ = global._ || require('underscore');
function CartoCSS(style, options) {
this.options = options || {};
this.imageURLs = [];
if(style) {
this.setStyle(style);
}
}
CartoCSS.Layer = function(shader, options) {
this.options = options;
this.shader = shader;
};
CartoCSS.Layer.prototype = {
fullName: function() {
return this.shader.attachment;
},
name: function() {
return this.fullName().split('::')[0];
},
// frames this layer need to be rendered
frames: function() {
return this.shader.frames;
},
attachment: function() {
return this.fullName().split('::')[1];
},
eval: function(prop) {
var p = this.shader[prop];
if (!p || !p.style) return;
return p.style({}, { zoom: 0, 'frame-offset': 0 });
},
/*
* `props`: feature properties
* `context`: rendering properties, i.e zoom
*/
getStyle: function(props, context) {
var style = {};
for(var i in this.shader) {
if(i !== 'attachment' && i !== 'zoom' && i !== 'frames' && i !== 'symbolizers') {
style[i] = this.shader[i].style(props, context);
}
}
return style;
},
/**
* return the symbolizers that need to be rendered with
* this style. The order is the rendering order.
* @returns a list with 3 possible values 'line', 'marker', 'polygon'
*/
getSymbolizers: function() {
return this.shader.symbolizers;
},
/**
* returns if the style varies with some feature property.
* Useful to optimize rendering
*/
isVariable: function() {
for(var i in this.shader) {
if(i !== 'attachment' && i !== 'zoom' && i !== 'frames' && i !== 'symbolizers') {
if (!this.shader[i].constant) {
return true;
}
}
}
return false;
},
getShader: function() {
return this.shader;
},
/**
* returns true if a feature needs to be rendered
*/
filter: function(featureType, props, context) {
for(var i in this.shader) {
var s = this.shader[i](props, context);
if(s) {
return true;
}
}
return false;
},
//
// given a geoemtry type returns the transformed one acording the CartoCSS
// For points there are two kind of types: point and sprite, the first one
// is a circle, second one is an image sprite
//
// the other geometry types are the same than geojson (polygon, linestring...)
//
transformGeometry: function(type) {
return type;
},
transformGeometries: function(geojson) {
return geojson;
}
};
CartoCSS.prototype = {
setStyle: function(style) {
var layers = this.parse(style);
if(!layers) {
throw new Error(this.parse_env.errors);
}
this.layers = layers.map(function(shader) {
return new CartoCSS.Layer(shader);
});
},
getLayers: function() {
return this.layers;
},
getDefault: function() {
return this.findLayer({ attachment: '__default__' });
},
findLayer: function(where) {
return _.find(this.layers, function(value) {
for (var key in where) {
var v = value[key];
if (typeof(v) === 'function') {
v = v.call(value);
}
if (where[key] !== v) return false;
}
return true;
});
},
_createFn: function(ops) {
var body = ops.join('\n');
if(this.options.debug) console.log(body);
return Function("data","ctx", "var _value = null; " + body + "; return _value; ");
},
_compile: function(shader) {
if(typeof shader === 'string') {
shader = eval("(function() { return " + shader +"; })()");
}
this.shader_src = shader;
for(var attr in shader) {
var c = mapper[attr];
if(c) {
this.compiled[c] = eval("(function() { return shader[attr]; })();");
}
}
},
getImageURLs: function(){
return this.imageURLs;
},
parse: function(cartocss) {
var parse_env = {
frames: [],
errors: [],
error: function(obj) {
this.errors.push(obj);
}
};
this.parse_env = parse_env;
var ruleset = null;
try {
ruleset = (new carto.Parser(parse_env)).parse(cartocss);
} catch(e) {
// add the style.mss string to match the response from the server
parse_env.errors.push(e.message);
return;
}
if(ruleset) {
function defKey(def) {
return def.elements[0] + "::" + def.attachment;
}
var defs = ruleset.toList(parse_env);
defs.reverse();
// group by elements[0].value::attachment
var layers = {};
for(var i = 0; i < defs.length; ++i) {
var def = defs[i];
var key = defKey(def);
var layer = layers[key] = (layers[key] || {
symbolizers: []
});
for(var u = 0; u<def.rules.length; u++){
var rule = def.rules[u];
if(rule.name === "marker-file" || rule.name === "point-file"){
var value = rule.value.value[0].value[0].value.value;
this.imageURLs.push(value);
}
}
layer.frames = [];
layer.zoom = tree.Zoom.all;
var props = def.toJS(parse_env);
if (this.options.debug) console.log("props", props);
for(var v in props) {
var lyr = layer[v] = layer[v] || {
constant: false,
symbolizer: null,
js: [],
index: 0
};
// build javascript statements
lyr.js.push(props[v].map(function(a) { return a.js; }).join('\n'));
// get symbolizer for prop
lyr.symbolizer = _.first(props[v].map(function(a) { return a.symbolizer; }));
// serach the max index to know rendering order
lyr.index = _.max(props[v].map(function(a) { return a.index; }).concat(lyr.index));
lyr.constant = !_.any(props[v].map(function(a) { return !a.constant; }));
// True when the property is filtered.
lyr.filtered = props[v][0].filtered;
}
}
var ordered_layers = [];
if (this.options.debug) console.log(layers);
var done = {};
for(var i = 0; i < defs.length; ++i) {
var def = defs[i];
if (this.options.strict) {
def.toXML(parse_env, {});
if (parse_env.errors.message) {
throw new Error(parse_env.errors.message);
}
}
var k = defKey(def);
var layer = layers[k];
if(!done[k]) {
if(this.options.debug) console.log("**", k);
for(var prop in layer) {
if (prop !== 'zoom' && prop !== 'frames' && prop !== 'symbolizers') {
if(this.options.debug) console.log("*", prop);
layer[prop].style = this._createFn(layer[prop].js);
layer.symbolizers.push(layer[prop].symbolizer);
layer.symbolizers = _.uniq(layer.symbolizers);
}
}
layer.attachment = k;
ordered_layers.push(layer);
done[k] = true;
}
layer.zoom |= def.zoom;
layer.frames.push(def.frame_offset);
}
// uniq the frames
for(i = 0; i < ordered_layers.length; ++i) {
ordered_layers[i].frames = _.uniq(ordered_layers[i].frames);
}
return ordered_layers;
}
return null;
}
};
carto.RendererJS = function (options) {
this.options = options || {};
this.options.mapnik_version = this.options.mapnik_version || 'latest';
this.reference = this.options.reference || require('./torque-reference').version.latest;
this.options.strict = this.options.hasOwnProperty('strict') ? this.options.strict : false;
};
// Prepare a javascript object which contains the layers
carto.RendererJS.prototype.render = function render(cartocss, callback) {
tree.Reference.setData(this.reference);
return new CartoCSS(cartocss, this.options);
}
if(typeof(module) !== 'undefined') {
module.exports = carto.RendererJS;
}
})(require('../carto'));

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,9 @@
/**
* TODO: document this. What does this do?
*/
if(typeof(module) !== "undefined") {
module.exports.find = function (obj, fun) {
for (var i = 0, r; i < obj.length; i++) {
if (r = fun.call(obj, obj[i])) { return r; }
}
return null;
};
}
module.exports.find = function (obj, fun) {
for (var i = 0, r; i < obj.length; i++) {
if (r = fun.call(obj, obj[i])) { return r; }
}
return null;
};

View File

@ -1,23 +1,28 @@
(function(tree) {
var _ = global._ || require('underscore');
tree.Call = function Call(name, args, index) {
this.is = 'call';
this.name = name;
this.args = args;
this.index = index;
};
tree.Call.prototype = {
is: 'call',
// When evuating a function call,
//
// When evaluating a function call,
// we either find the function in `tree.functions` [1],
// in which case we call it, passing the evaluated arguments,
// or we simply print it out as it appeared originally [2].
//
// The *functions.js* file contains the built-in functions.
//
// The reason why we evaluate the arguments, is in the case where
// we try to pass a variable to a function, like: `saturate(@color)`.
// The function should receive the value, not the variable.
'ev': function(env) {
var args = this.args.map(function(a) { return a.ev(env); });
//
eval: function(env) {
var args = this.args.map(function(a) { return a.eval(env); });
for (var i = 0; i < args.length; i++) {
if (args[i].is === 'undefined') {
@ -29,18 +34,8 @@ tree.Call.prototype = {
}
if (this.name in tree.functions) {
if (tree.functions[this.name].length <= args.length) {
var val = tree.functions[this.name].apply(tree.functions, args);
if (val === null) {
env.error({
message: 'incorrect arguments given to ' + this.name + '()',
index: this.index,
type: 'runtime',
filename: this.filename
});
return { is: 'undefined', value: 'undefined' };
}
return val;
if (tree.functions[this.name].length === args.length) {
return tree.functions[this.name].apply(tree.functions, args);
} else {
env.error({
message: 'incorrect number of arguments for ' + this.name +
@ -55,19 +50,10 @@ tree.Call.prototype = {
};
}
} else {
var fn = tree.Reference.mapnikFunctions[this.name];
if (fn === undefined) {
var functions = _.pairs(tree.Reference.mapnikFunctions);
// cheap closest, needs improvement.
var name = this.name;
var mean = functions.map(function(f) {
return [f[0], tree.Reference.editDistance(name, f[0]), f[1]];
}).sort(function(a, b) {
return a[1] - b[1];
});
var fn = tree.Reference.mapnikFunction(this.name);
if (!fn) {
env.error({
message: 'unknown function ' + this.name + '(), did you mean ' +
mean[0][0] + '(' + mean[0][2] + ')',
message: 'unknown function ' + this.name,
index: this.index,
type: 'runtime',
filename: this.filename
@ -77,13 +63,10 @@ tree.Call.prototype = {
value: 'undefined'
};
}
if (fn !== args.length &&
!(Array.isArray(fn) && _.include(fn, args.length)) &&
// support variable-arg functions like `colorize-alpha`
fn !== -1) {
if (fn[1] !== args.length) {
env.error({
message: 'function ' + this.name + '() takes ' +
fn + ' arguments and was given ' + args.length,
message: 'function ' + this.name + ' takes ' +
fn[1] + ' arguments and was given ' + args.length,
index: this.index,
type: 'runtime',
filename: this.filename
@ -101,10 +84,18 @@ tree.Call.prototype = {
},
toString: function(env, format) {
if (this.args.length) {
return this.name + '(' + this.args.join(',') + ')';
if (format === 'image-filter') {
if (this.args.length) {
return this.name + ':' + this.args.join(',');
} else {
return this.name;
}
} else {
return this.name;
if (this.args.length) {
return this.name + '(' + this.args.join(',') + ')';
} else {
return this.name;
}
}
}
};

View File

@ -1,14 +1,16 @@
(function(tree) {
//
// RGB Colors - #ff0014, #eee
// can be initialized with a 3 or 6 char string or a 3 or 4 element
// numerical array
//
tree.Color = function Color(rgb, a) {
//
// The end goal here, is to parse the arguments
// into an integer triplet, such as `128, 255, 0`
//
// This facilitates operations and conversions.
//
if (Array.isArray(rgb)) {
this.rgb = rgb.slice(0, 3);
this.rgb = rgb;
} else if (rgb.length == 6) {
this.rgb = rgb.match(/.{2}/g).map(function(c) {
return parseInt(c, 16);
@ -18,19 +20,12 @@ tree.Color = function Color(rgb, a) {
return parseInt(c + c, 16);
});
}
if (typeof(a) === 'number') {
this.alpha = a;
} else if (rgb.length === 4) {
this.alpha = rgb[3];
} else {
this.alpha = 1;
}
this.is = 'color';
this.alpha = typeof(a) === 'number' ? a : 1;
};
tree.Color.prototype = {
is: 'color',
'ev': function() { return this; },
eval: function() { return this; },
// If we have some transparency, the only way to represent it
// is via `rgba`. Otherwise, we use the hex representation,
@ -54,7 +49,7 @@ tree.Color.prototype = {
// channels will spill onto each other. Once we have
// our result, in the form of an integer triplet,
// we create a new Color node to hold the result.
operate: function(env, op, other) {
operate: function(op, other) {
var result = [];
if (! (other instanceof tree.Color)) {
@ -92,4 +87,5 @@ tree.Color.prototype = {
}
};
})(require('../tree'));

View File

@ -9,7 +9,7 @@ tree.Comment.prototype = {
toString: function(env) {
return '<!--' + this.value + '-->';
},
'ev': function() { return this; }
eval: function() { return this; }
};
})(require('../tree'));

View File

@ -1,26 +1,18 @@
(function(tree) {
var assert = require('assert'),
_ = global._ || require('underscore');
var assert = require('assert');
// A definition is the combination of a selector and rules, like
// #foo {
// polygon-opacity:1.0;
// }
//
// The selector can have filters
tree.Definition = function Definition(selector, rules) {
this.elements = selector.elements;
assert.ok(selector.filters instanceof tree.Filterset);
this.rules = rules;
this.ruleIndex = {};
this.ruleIndex = [];
for (var i = 0; i < this.rules.length; i++) {
if ('zoom' in this.rules[i]) this.rules[i] = this.rules[i].clone();
this.rules[i].zoom = selector.zoom;
this.ruleIndex[this.rules[i].updateID()] = true;
this.ruleIndex.push(this.rules[i].updateID());
}
this.filters = selector.filters;
this.zoom = selector.zoom;
this.frame_offset = selector.frame_offset;
this.attachment = selector.attachment || '__default__';
this.specificity = selector.specificity();
};
@ -37,7 +29,7 @@ tree.Definition.prototype.clone = function(filters) {
if (filters) assert.ok(filters instanceof tree.Filterset);
var clone = Object.create(tree.Definition.prototype);
clone.rules = this.rules.slice();
clone.ruleIndex = _.clone(this.ruleIndex);
clone.ruleIndex = this.ruleIndex.slice();
clone.filters = filters ? filters : this.filters.clone();
clone.attachment = this.attachment;
return clone;
@ -48,9 +40,9 @@ tree.Definition.prototype.addRules = function(rules) {
// Add only unique rules.
for (var i = 0; i < rules.length; i++) {
if (!this.ruleIndex[rules[i].id]) {
if (this.ruleIndex.indexOf(rules[i].id) < 0) {
this.rules.push(rules[i]);
this.ruleIndex[rules[i].id] = true;
this.ruleIndex.push(rules[i].id);
added++;
}
}
@ -58,33 +50,24 @@ tree.Definition.prototype.addRules = function(rules) {
return added;
};
// Determine whether this selector matches a given id
// and array of classes, by determining whether
// all elements it contains match.
/**
* Determine whether this selector matches a given id
* and array of classes, by determining whether
* all elements it contains match.
*/
tree.Definition.prototype.appliesTo = function(id, classes) {
for (var i = 0, l = this.elements.length; i < l; i++) {
var elem = this.elements[i];
if (!(elem.wildcard ||
(elem.type === 'class' && classes[elem.clean]) ||
(elem.type === 'id' && id === elem.clean))) return false;
for (var i = 0; i < this.elements.length; i++) {
if (!this.elements[i].matches(id, classes)) {
return false;
}
}
return true;
};
function symbolizerName(symbolizer) {
function capitalize(str) { return str[1].toUpperCase(); }
return symbolizer.charAt(0).toUpperCase() +
symbolizer.slice(1).replace(/\-./, capitalize) + 'Symbolizer';
}
// Get a simple list of the symbolizers, in order
function symbolizerList(sym_order) {
return sym_order.sort(function(a, b) { return a[1] - b[1]; })
.map(function(v) { return v[0]; });
}
tree.Definition.prototype.symbolizersToXML = function(env, symbolizers, zoom) {
var xml = zoom.toXML(env).join('') + this.filters.toXML(env);
var xml = ' <Rule>\n';
xml += tree.Zoom.toXML(zoom).join('');
xml += this.filters.toXML(env);
// Sort symbolizers by the index of their first property definition
var sym_order = [], indexes = [];
@ -97,17 +80,20 @@ tree.Definition.prototype.symbolizersToXML = function(env, symbolizers, zoom) {
sym_order.push([key, min_idx]);
}
sym_order = symbolizerList(sym_order);
var sym_count = 0;
// Get a simple list of the symbolizers, in order
sym_order = sym_order.sort(function(a, b) {
return a[1] - b[1];
}).map(function(v) {
return v[0];
});
for (var i = 0; i < sym_order.length; i++) {
var attributes = symbolizers[sym_order[i]];
var symbolizer = sym_order[i].split('/').pop();
// Skip the magical * symbolizer which is used for universal properties
// which are bubbled up to Style elements intead of Symbolizer elements.
if (symbolizer === '*') continue;
sym_count++;
var fail = tree.Reference.requiredProperties(symbolizer, attributes);
if (fail) {
@ -119,43 +105,37 @@ tree.Definition.prototype.symbolizersToXML = function(env, symbolizers, zoom) {
});
}
var name = symbolizerName(symbolizer);
var name = symbolizer.charAt(0).toUpperCase() +
symbolizer.slice(1).replace(/\-./, function(str) {
return str[1].toUpperCase();
}) + 'Symbolizer';
var selfclosing = true, tagcontent;
xml += ' <' + name + ' ';
for (var j in attributes) {
for (var key in attributes) {
if (symbolizer === 'map') env.error({
message: 'Map properties are not permitted in other rules',
index: attributes[j].index,
filename: attributes[j].filename
index: attributes[key].index,
filename: attributes[key].filename
});
var x = tree.Reference.selector(attributes[j].name);
var x = tree.Reference.selector(attributes[key].name);
if (x && x.serialization && x.serialization === 'content') {
selfclosing = false;
tagcontent = attributes[j].ev(env).toXML(env, true);
} else if (x && x.serialization && x.serialization === 'tag') {
selfclosing = false;
tagcontent = attributes[j].ev(env).toXML(env, true);
tagcontent = attributes[key].eval(env).toXML(env, true);
} else {
xml += attributes[j].ev(env).toXML(env) + ' ';
xml += attributes[key].eval(env).toXML(env) + ' ';
}
}
if (selfclosing) {
xml += '/>\n';
} else if (typeof tagcontent !== "undefined") {
if (tagcontent.indexOf('<') != -1) {
xml += '>' + tagcontent + '</' + name + '>\n';
} else {
xml += '><![CDATA[' + tagcontent + ']]></' + name + '>\n';
}
} else {
xml += '><![CDATA[' + tagcontent + ']]></' + name + '>\n';
}
}
if (!sym_count || !xml) return '';
return ' <Rule>\n' + xml + ' </Rule>\n';
xml += ' </Rule>\n';
return xml;
};
// Take a zoom range of zooms and 'i', the index of a rule in this.rules,
// and finds all applicable symbolizers
tree.Definition.prototype.collectSymbolizers = function(zooms, i) {
var symbolizers = {}, child;
@ -179,18 +159,18 @@ tree.Definition.prototype.collectSymbolizers = function(zooms, i) {
}
};
// The tree.Zoom.toString function ignores the holes in zoom ranges and outputs
// scaledenominators that cover the whole range from the first to last bit set.
// This algorithm can produces zoom ranges that may have holes. However,
// when using the filter-mode="first", more specific zoom filters will always
// end up before broader ranges. The filter-mode will pick those first before
// resorting to the zoom range with the hole and stop processing further rules.
tree.Definition.prototype.toXML = function(env, existing) {
// The tree.Zoom.toString function ignores the holes in zoom ranges and outputs
// scaledenominators that cover the whole range from the first to last bit set.
// This algorithm can produces zoom ranges that may have holes. However,
// when using the filter-mode="first", more specific zoom filters will always
// end up before broader ranges. The filter-mode will pick those first before
// resorting to the zoom range with the hole and stop processing further rules.
var filter = this.filters.toString();
if (!(filter in existing)) existing[filter] = tree.Zoom.all;
var available = tree.Zoom.all, xml = '', zoom, symbolizers,
zooms = { available: tree.Zoom.all };
var available = tree.Zoom.all, xml = '', zoom, symbolizers;
var zooms = { available: tree.Zoom.all };
for (var i = 0; i < this.rules.length && available; i++) {
zooms.rule = this.rules[i].zoom;
if (!(existing[filter] & zooms.rule)) continue;
@ -198,8 +178,7 @@ tree.Definition.prototype.toXML = function(env, existing) {
while (zooms.current = zooms.rule & available) {
if (symbolizers = this.collectSymbolizers(zooms, i)) {
if (!(existing[filter] & zooms.current)) continue;
xml += this.symbolizersToXML(env, symbolizers,
(new tree.Zoom()).setZoom(existing[filter] & zooms.current));
xml += this.symbolizersToXML(env, symbolizers, existing[filter] & zooms.current);
existing[filter] &= ~zooms.current;
}
}
@ -208,41 +187,4 @@ tree.Definition.prototype.toXML = function(env, existing) {
return xml;
};
tree.Definition.prototype.toJS = function(env) {
var shaderAttrs = {};
var frame_offset = this.frame_offset;
var zoomFilter = "(" + this.zoom + " & (1 << ctx.zoom))";
var filters = [zoomFilter];
var originalFilters = this.filters.toJS(env);
// Ignore default zoom for filtering (https://github.com/CartoDB/carto/issues/40)
var zoomFiltered = this.zoom !== tree.Zoom.all;
if (originalFilters) {
filters.push(originalFilters);
}
if (frame_offset) {
filters.push('ctx["frame-offset"] === ' + frame_offset);
}
_.each(this.rules, function (rule) {
var exportedRule = {};
if (!rule instanceof tree.Rule) {
throw new Error("Ruleset not supported");
}
exportedRule.index = rule.index;
exportedRule.symbolizer = rule.symbolizer;
exportedRule.js = "if(" + filters.join(" && ") + "){" + rule.value.toJS(env) + "}";
exportedRule.constant = rule.value.ev(env).is !== 'field';
exportedRule.filtered = zoomFiltered || (originalFilters !== '');
shaderAttrs[rule.name] = shaderAttrs[rule.name] || [];
shaderAttrs[rule.name].push(exportedRule);
});
return shaderAttrs;
};
})(require('../tree'));

View File

@ -1,98 +1,42 @@
(function(tree) {
var _ = global._ || require('underscore');
//
// A number with a unit
//
tree.Dimension = function Dimension(value, unit, index) {
this.value = parseFloat(value);
this.unit = unit || null;
this.is = 'float';
this.index = index;
};
tree.Dimension.prototype = {
is: 'float',
physical_units: ['m', 'cm', 'in', 'mm', 'pt', 'pc'],
screen_units: ['px', '%'],
all_units: ['m', 'cm', 'in', 'mm', 'pt', 'pc', 'px', '%'],
densities: {
m: 0.0254,
mm: 25.4,
cm: 2.54,
pt: 72,
pc: 6
},
ev: function (env) {
if (this.unit && !_.contains(this.all_units, this.unit)) {
env.error({
eval: function (env) {
if (this.unit && ['px', '%'].indexOf(this.unit) === -1) {
env.error({
message: "Invalid unit: '" + this.unit + "'",
index: this.index
});
return { is: 'undefined', value: 'undefined' };
}
// normalize units which are not px or %
if (this.unit && _.contains(this.physical_units, this.unit)) {
if (!env.ppi) {
env.error({
message: "ppi is not set, so metric units can't be used",
index: this.index
});
return { is: 'undefined', value: 'undefined' };
}
// convert all units to inch
// convert inch to px using ppi
this.value = (this.value / this.densities[this.unit]) * env.ppi;
this.unit = 'px';
}
return this;
},
round: function() {
this.value = Math.round(this.value);
return this;
},
toColor: function() {
return new tree.Color([this.value, this.value, this.value]);
},
round: function() {
this.value = Math.round(this.value);
return this;
},
toString: function() {
return this.value.toString();
},
operate: function(env, op, other) {
if (this.unit === '%' && other.unit !== '%') {
env.error({
message: 'If two operands differ, the first must not be %',
index: this.index
});
return {
is: 'undefined',
value: 'undefined'
};
}
if (this.unit !== '%' && other.unit === '%') {
if (op === '*' || op === '/' || op === '%') {
env.error({
message: 'Percent values can only be added or subtracted from other values',
index: this.index
});
return {
is: 'undefined',
value: 'undefined'
};
}
return new tree.Dimension(tree.operate(op,
this.value, this.value * other.value * 0.01),
this.unit);
}
//here the operands are either the same (% or undefined or px), or one is undefined and the other is px
// In an operation between two Dimensions,
// we default to the first Dimension's unit,
// so `1px + 2em` will yield `3px`.
// In the future, we could implement some unit
// conversions such that `100cm + 10mm` would yield
// `101cm`.
operate: function(op, other) {
return new tree.Dimension(tree.operate(op, this.value, other.value),
this.unit || other.unit);
this.unit || other.unit);
}
};

View File

@ -0,0 +1,39 @@
(function(tree) {
tree.Directive = function Directive(name, value) {
this.name = name;
if (Array.isArray(value)) {
this.ruleset = new tree.Ruleset([], value);
} else {
this.value = value;
}
};
tree.Directive.prototype = {
toString: function(ctx, env) {
if (this.ruleset) {
this.ruleset.root = true;
return this.name + ' {\n ' +
this.ruleset.toString(ctx, env).trim().replace(/\n/g, '\n ') +
'\n}\n';
} else {
return this.name + ' ' + this.value.toString() + ';\n';
}
},
eval: function(env) {
env.frames.unshift(this);
this.ruleset = this.ruleset && this.ruleset.eval(env);
env.frames.shift();
return this;
},
variable: function(name) {
return tree.Ruleset.prototype.variable.call(this.ruleset, name);
},
find: function() {
return tree.Ruleset.prototype.find.apply(this.ruleset, arguments);
},
rulesets: function() {
return tree.Ruleset.prototype.rulesets.apply(this.ruleset);
}
};
})(require('../tree'));

View File

@ -3,28 +3,31 @@
// An element is an id or class selector
tree.Element = function Element(value) {
this.value = value.trim();
if (this.value[0] === '#') {
this.type = 'id';
this.clean = this.value.replace(/^#/, '');
}
if (this.value[0] === '.') {
this.type = 'class';
this.clean = this.value.replace(/^\./, '');
}
if (this.value.indexOf('*') !== -1) {
this.type = 'wildcard';
}
};
// Determine the 'specificity matrix' of this
// specific selector
tree.Element.prototype.specificity = function() {
return [
(this.type === 'id') ? 1 : 0, // a
(this.type === 'class') ? 1 : 0 // b
(this.value[0] == '#') ? 1 : 0, // a
(this.value[0] == '.') ? 1 : 0 // b
];
};
tree.Element.prototype.toString = function() { return this.value; };
tree.Element.prototype.toString = function() {
return this.value;
};
// Determine whether this element matches an id or classes.
// An element is a single id or class, or check whether the given
// array of classes contains this, or the id is equal to this.
//
// Takes a plain string for id and plain strings in the array of
// classes.
tree.Element.prototype.matches = function(id, classes) {
return (classes.indexOf(this.value.replace(/^\./, '')) !== -1) ||
(this.value.replace(/^#/, '') === id) ||
(this.value === '*');
};
})(require('../tree'));

View File

@ -3,19 +3,16 @@
tree.Expression = function Expression(value) {
this.value = value;
};
tree.Expression.prototype = {
is: 'expression',
ev: function(env) {
eval: function(env) {
if (this.value.length > 1) {
return new tree.Expression(this.value.map(function(e) {
return e.ev(env);
return e.eval(env);
}));
} else {
return this.value[0].ev(env);
return this.value[0].eval(env);
}
},
toString: function(env) {
return this.value.map(function(e) {
return e.toString(env);

View File

@ -2,14 +2,14 @@
tree.Field = function Field(content) {
this.value = content || '';
this.is = 'field';
};
tree.Field.prototype = {
is: 'field',
toString: function() {
return '[' + this.value + ']';
},
'ev': function() {
'eval': function() {
return this;
}
};

View File

@ -1,15 +1,32 @@
(function(tree) {
tree.Filter = function Filter(key, op, val, index, filename) {
this.key = key;
if (key.is) {
this.key = key.value;
this._key = key;
} else {
this.key = key;
}
this.op = op;
this.val = val;
this.index = index;
this.filename = filename;
if (val.is) {
this.val = val.value;
this._val = val;
} else {
this.val = val;
}
if (ops[this.op][1] == 'numeric') {
this.val = 1 * this.val;
}
this.id = this.key + this.op + this.val;
};
// xmlsafe, numeric, suffix
var ops = {
'<': [' &lt; ', 'numeric'],
@ -21,35 +38,15 @@ var ops = {
'=~': ['.match(', 'string', ')']
};
tree.Filter.prototype.ev = function(env) {
this.key = this.key.ev(env);
this.val = this.val.ev(env);
return this;
};
tree.Filter.prototype.toXML = function(env) {
if (tree.Reference.data.filter) {
if (this.key.is === 'keyword' && -1 === tree.Reference.data.filter.value.indexOf(this.key.toString())) {
env.error({
message: this.key.toString() + ' is not a valid keyword in a filter expression',
index: this.index,
filename: this.filename
});
}
if (this.val.is === 'keyword' && -1 === tree.Reference.data.filter.value.indexOf(this.val.toString())) {
env.error({
message: this.val.toString() + ' is not a valid keyword in a filter expression',
index: this.index,
filename: this.filename
});
}
}
var key = this.key.toString(false);
var val = this.val.toString(this.val.is == 'string');
if (this.val.eval) this._val = this.val.eval(env);
if (this.key.eval) this._key = this.key.eval(env);
if (this._key) var key = this._key.toString(false);
if (this._val) var val = this._val.toString(this._val.is == 'string');
if (
(ops[this.op][1] == 'numeric' && isNaN(val) && this.val.is !== 'field') ||
(ops[this.op][1] == 'string' && (val)[0] != "'")
(ops[this.op][1] == 'numeric' && isNaN(this.val)) ||
(ops[this.op][1] == 'string' && (val || this.val)[0] != "'")
) {
env.error({
message: 'Cannot use operator "' + this.op + '" with value ' + this.val,
@ -58,7 +55,7 @@ tree.Filter.prototype.toXML = function(env) {
});
}
return key + ops[this.op][0] + val + (ops[this.op][2] || '');
return '[' + (key || this.key) + ']' + ops[this.op][0] + '' + (val || this.val) + (ops[this.op][2] || '');
};
tree.Filter.prototype.toString = function() {

View File

@ -1,270 +1,224 @@
var tree = require('../tree');
var _ = global._ || require('underscore');
tree.Filterset = function Filterset() {
this.filters = {};
};
tree.Filterset = function Filterset() {};
tree.Filterset.prototype.toXML = function(env) {
var filters = [];
for (var id in this.filters) {
filters.push('(' + this.filters[id].toXML(env).trim() + ')');
Object.defineProperty(tree.Filterset.prototype, 'toXML', {
enumerable: false,
value: function(env) {
var filters = [];
for (var id in this) {
filters.push('(' + this[id].toXML(env).trim() + ')');
}
if (filters.length) {
return ' <Filter>' + filters.join(' and ') + '</Filter>\n';
} else {
return '';
}
}
if (filters.length) {
return ' <Filter>' + filters.join(' and ') + '</Filter>\n';
} 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);
Object.defineProperty(tree.Filterset.prototype, 'toString', {
enumerable: false,
value: function() {
var arr = [];
for (var id in this) arr.push(this[id].id);
arr.sort();
return arr.join('\t');
}
return this;
};
});
tree.Filterset.prototype.clone = function() {
var clone = new tree.Filterset();
for (var id in this.filters) {
clone.filters[id] = this.filters[id];
Object.defineProperty(tree.Filterset.prototype, 'clone', {
enumerable: false,
value: function() {
var clone = new tree.Filterset();
for (var id in this) {
clone[id] = this[id];
}
return clone;
}
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;
Object.defineProperty(tree.Filterset.prototype, 'cloneWith', {
enumerable: false,
value: function(other) {
var additions;
for (var id in other) {
var status = this.addable(other[id]);
if (status === false) {
return false;
}
if (status === true) {
// Adding the filter will override another value.
if (!additions) additions = [];
additions.push(other[id]);
}
}
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) 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 (var id in this) {
clone[id] = this[id];
}
// Only add new filters that actually change the filter.
while (id = additions.shift()) {
clone.add(id);
}
return clone;
}
});
/**
* Returns true when the new filter can be added, false otherwise.
*/
Object.defineProperty(tree.Filterset.prototype, 'addable', {
enumerable: false,
value: function(filter) {
var key = filter.key, value = filter.val;
switch (filter.op) {
case '=':
if (key + '=' in this) return (this[key + '='].val != value) ? false : null;
if (key + '!=' + value in this) return false;
if (key + '>' in this && this[key + '>'].val >= value) return false;
if (key + '<' in this && this[key + '<'].val <= value) return false;
if (key + '>=' in this && this[key + '>='].val > value) return false;
if (key + '<=' in this && this[key + '<='].val < value) return false;
return true;
case '!=':
if (key + '=' in this) return (this[key + '='].val == value) ? false : null;
if (key + '!=' + value in this) return null;
if (key + '>' in this && this[key + '>'].val >= value) return null;
if (key + '<' in this && this[key + '<'].val <= value) return null;
if (key + '>=' in this && this[key + '>='].val > value) return null;
if (key + '<=' in this && this[key + '<='].val < value) return null;
return true;
case '>':
if (key + '=' in this) return (this[key + '='].val <= value) ? false : null;
if (key + '<' in this && this[key + '<'].val <= value) return false;
if (key + '<=' in this && this[key + '<='].val <= value) return false;
if (key + '>' in this && this[key + '>'].val >= value) return null;
if (key + '>=' in this && this[key + '>='].val > value) return null;
return true;
case '>=':
if (key + '=' in this) return (this[key + '='].val < value) ? false : null;
if (key + '<' in this && this[key + '<'].val <= value) return false;
if (key + '<=' in this && this[key + '<='].val < value) return false;
if (key + '>' in this && this[key + '>'].val >= value) return null;
if (key + '>=' in this && this[key + '>='].val >= value) return null;
return true;
case '<':
if (key + '=' in this) return (this[key + '='].val >= value) ? false : null;
if (key + '>' in this && this[key + '>'].val >= value) return false;
if (key + '>=' in this && this[key + '>='].val >= value) return false;
if (key + '<' in this && this[key + '<'].val <= value) return null;
if (key + '<=' in this && this[key + '<='].val < value) return null;
return true;
case '<=':
if (key + '=' in this) return (this[key + '='].val > value) ? false : null;
if (key + '>' in this && this[key + '>'].val >= value) return false;
if (key + '>=' in this && this[key + '>='].val > value) return false;
if (key + '<' in this && this[key + '<'].val <= value) return null;
if (key + '<=' in this && this[key + '<='].val <= value) return null;
return true;
}
}
});
// Adding the other filters doesn't make this filterset invalid, but it
// doesn't add anything to it either.
if (!additions.length) {
return null;
}
/**
* Only call this function for filters that have been cleared by .addable().
*/
Object.defineProperty(tree.Filterset.prototype, 'add', {
enumerable: false,
value: function(filter) {
var key = filter.key;
// 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.
while (id = additions.shift()) {
clone.add(id);
}
return clone;
};
tree.Filterset.prototype.toJS = function(env) {
var opMap = {
'=': '==='
};
return _.map(this.filters, function(filter) {
var op = filter.op;
if(op in opMap) {
op = opMap[op];
}
var val = filter.val;
if(filter._val !== undefined) {
val = filter._val.toString(true);
}
var attrs = "data";
if (op === '=~') {
return "(" + attrs + "['" + filter.key.value + "'] + '').match(" + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&amp;/g, '&') + "'" : val) + ")";
}
return attrs + "['" + filter.key.value + "'] " + op + " " + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&amp;/g, '&') + "'" : val);
}).join(' && ');
};
// 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();
if (value.match(/^[0-9]+(\.[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;
switch (filter.op) {
case '=':
for (var id in this) {
if (this[id].key == key) {
delete this[id];
}
}
}
if (this.filters[key + '!=' + value] !== undefined) 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 false;
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return false;
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return false;
return true;
this[key + '='] = filter;
break;
case '=~':
return true;
case '!=':
this[key + '!=' + filter.val] = filter;
break;
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) return null;
if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return null;
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 '=~':
this[key + '=~' + filter.val] = filter;
break;
case '>':
if (key + '=' in this.filters) {
if (this.filters[key + '='].val <= value) {
return false;
} else {
return null;
case '>':
// If there are other filters that are also >
// but are less than this one, they don't matter, so
// remove them.
for (var id in this) {
if (this[id].key == key && this[id].val <= filter.val) {
delete this[id];
}
}
}
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;
this[key + '>'] = filter;
break;
case '>=':
if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val < value) ? false : 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 '>=':
for (var id in this) {
if (this[id].key == key && this[id].val < filter.val) {
delete this[id];
}
}
if (key + '!=' + filter.val in this) {
delete this[key + '!=' + filter.val];
filter.op = '>';
this[key + '>'] = filter;
}
else {
this[key + '>='] = filter;
}
break;
case '<':
if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val >= value) ? false : 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 '<':
for (var id in this) {
if (this[id].key == key && this[id].val >= filter.val) {
delete this[id];
}
}
this[key + '<'] = filter;
break;
case '<=':
if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val > value) ? false : 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, env) {
var key = filter.key.toString(),
id,
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 === '>') {
// If there are other filters that are also >
// but are less than this one, they don't matter, so
// remove them.
for (var j in this.filters) {
if (this.filters[j].key == key && this.filters[j].val <= filter.val) {
delete this.filters[j];
}
}
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;
case '<=':
for (var id in this) {
if (this[id].key == key && this[id].val > filter.val) {
delete this[id];
}
}
if (key + '!=' + filter.val in this) {
delete this[key + '!=' + filter.val];
filter.op = '<';
this[key + '<'] = filter;
}
else {
this[key + '<='] = filter;
}
break;
}
}
};
});

View File

@ -1,16 +1,23 @@
(function(tree) {
tree._getFontSet = function(env, fonts) {
var fontKey = fonts.join('');
if (env._fontMap && env._fontMap[fontKey]) {
return env._fontMap[fontKey];
}
var find_existing = function(fonts) {
var findFonts = fonts.join('');
for (var i = 0; i < env.effects.length; i++) {
if (findFonts == env.effects[i].fonts.join('')) {
return env.effects[i];
}
}
};
var new_fontset = new tree.FontSet(env, fonts);
env.effects.push(new_fontset);
if (!env._fontMap) env._fontMap = {};
env._fontMap[fontKey] = new_fontset;
return new_fontset;
var existing = false;
if (existing = find_existing(fonts)) {
return existing;
} else {
var new_fontset = new tree.FontSet(env, fonts);
env.effects.push(new_fontset);
return new_fontset;
}
};
tree.FontSet = function FontSet(env, fonts) {

View File

@ -1,27 +0,0 @@
var tree = require('../tree');
// Storage for Frame offset value
// and stores them as bit-sequences so that they can be combined,
// inverted, and compared quickly.
tree.FrameOffset = function(op, value, index) {
value = parseInt(value, 10);
if (value > tree.FrameOffset.max || value <= 0) {
throw {
message: 'Only frame-offset levels between 1 and ' +
tree.FrameOffset.max + ' supported.',
index: index
};
}
if (op !== '=') {
throw {
message: 'only = operator is supported for frame-offset',
index: index
};
}
return value;
};
tree.FrameOffset.max = 32;
tree.FrameOffset.none = 0;

View File

@ -1,17 +1,18 @@
(function(tree) {
//
// RGB Colors - #ff0014, #eee
//
tree.ImageFilter = function ImageFilter(filter, args) {
this.is = 'imagefilter';
this.filter = filter;
this.args = args || null;
};
tree.ImageFilter.prototype = {
is: 'imagefilter',
ev: function() { return this; },
eval: function() { return this; },
toString: function() {
if (this.args) {
return this.filter + '(' + this.args.join(',') + ')';
return this.filter + ':' + this.args.join(',');
} else {
return this.filter;
}

View File

@ -6,9 +6,7 @@ tree.Invalid = function Invalid(chunk, index, message) {
this.message = message || "Invalid code: " + this.chunk;
};
tree.Invalid.prototype.is = 'invalid';
tree.Invalid.prototype.ev = function(env) {
tree.Invalid.prototype.eval = function(env) {
env.error({
chunk: this.chunk,
index: this.index,

View File

@ -10,7 +10,7 @@ tree.Keyword = function Keyword(value) {
this.is = special[value] ? special[value] : 'keyword';
};
tree.Keyword.prototype = {
ev: function() { return this; },
eval: function() { return this; },
toString: function() { return this.value; }
};

View File

@ -1,36 +1,35 @@
(function(tree) {
tree.LayerXML = function(obj, styles) {
tree.Layer = function Layer(obj) {
this.name = obj.name;
this.styles = obj.styles;
this.properties = obj.properties || {};
this.srs = obj.srs;
this.datasource = obj.Datasource;
};
tree.Layer.prototype.toXML = function() {
var dsoptions = [];
for (var i in obj.Datasource) {
for (var i in this.datasource) {
dsoptions.push('<Parameter name="' + i + '"><![CDATA[' +
obj.Datasource[i] + ']]></Parameter>');
this.datasource[i] + ']]></Parameter>');
}
var prop_string = '';
for (var prop in obj.properties) {
if (prop === 'minzoom') {
prop_string += ' maxzoom="' + tree.Zoom.ranges[obj.properties[prop]] + '"\n';
} else if (prop === 'maxzoom') {
prop_string += ' minzoom="' + tree.Zoom.ranges[obj.properties[prop]+1] + '"\n';
} else {
prop_string += ' ' + prop + '="' + obj.properties[prop] + '"\n';
}
for (var i in this.properties) {
prop_string += ' ' + i + '="' + this.properties[i] + '"\n';
}
return '<Layer' +
' name="' + obj.name + '"\n' +
' name="' + this.name + '"\n' +
prop_string +
((typeof obj.status === 'undefined') ? '' : ' status="' + obj.status + '"\n') +
((typeof obj.srs === 'undefined') ? '' : ' srs="' + obj.srs + '"') + '>\n ' +
styles.reverse().map(function(s) {
' srs="' + this.srs + '">\n ' +
this.styles.reverse().map(function(s) {
return '<StyleName>' + s + '</StyleName>';
}).join('\n ') +
(dsoptions.length ?
'\n <Datasource>\n ' +
dsoptions.join('\n ') +
'\n </Datasource>\n'
: '') +
'\n </Datasource>\n' +
' </Layer>\n';
};

View File

@ -12,7 +12,7 @@ tree.Literal.prototype = {
toString: function() {
return this.value;
},
'ev': function() {
'eval': function() {
return this;
}
};

View File

@ -1,18 +1,15 @@
// An operation is an expression with an op in between two operands,
// like 2 + 1.
(function(tree) {
tree.Operation = function Operation(op, operands, index) {
this.op = op.trim();
this.operands = operands;
this.index = index;
this.is = 'operation';
};
tree.Operation.prototype.is = 'operation';
tree.Operation.prototype.ev = function(env) {
var a = this.operands[0].ev(env),
b = this.operands[1].ev(env),
tree.Operation.prototype.eval = function(env) {
var a = this.operands[0].eval(env),
b = this.operands[1].eval(env),
temp;
if (a.is === 'undefined' || b.is === 'undefined') {
@ -64,24 +61,11 @@ tree.Operation.prototype.ev = function(env) {
value: 'undefined'
};
} else {
return new tree.Literal(a.ev(env).toString(true) + this.op + b.ev(env).toString(true));
return new tree.Literal(a.eval(env).toString(true) + this.op + b.eval(env).toString(true));
}
}
if (a.operate === undefined) {
env.error({
message: 'Cannot do math with type ' + a.is + '.',
index: this.index,
type: 'runtime',
filename: this.filename
});
return {
is: 'undefined',
value: 'undefined'
};
}
return a.operate(env, this.op, b);
return a.operate(this.op, b);
};
tree.operate = function(op, a, b) {

View File

@ -2,28 +2,22 @@
tree.Quoted = function Quoted(content) {
this.value = content || '';
this.is = 'string';
};
tree.Quoted.prototype = {
is: 'string',
toString: function(quotes) {
var escapedValue = this.value
.replace(/&/g, '&amp;')
var xmlvalue = escapedValue
.replace(/\'/g, '\\\'')
.replace(/\"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/\>/g, '&gt;');
return (quotes === true) ? "'" + xmlvalue + "'" : escapedValue;
var xmlvalue = this.value.replace(/\'/g, '&apos;');
return (quotes === true) ? "'" + xmlvalue + "'" : this.value;
},
'ev': function() {
'eval': function() {
return this;
},
operate: function(env, op, other) {
return new tree.Quoted(tree.operate(op, this.toString(), other.toString(this.contains_field)));
operate: function(op, other) {
return new tree.Quoted(true,
tree.operate(op, this.toString(), other.toString(this.contains_field)));
}
};

View File

@ -1,88 +1,103 @@
// Carto pulls in a reference from the `mapnik-reference`
// module. This file builds indexes from that file for its various
// options, and provides validation methods for property: value
// combinations.
/*
* Carto pulls in a reference from the `mapnik-reference`
* module. This file builds indexes from that file for its various
* options, and provides validation methods for property: value
* combinations.
*/
(function(tree) {
var _ = global._ || require('underscore'),
ref = {};
var _ = require('underscore');
var mapnik_reference = require('mapnik-reference');
ref.setData = function(data) {
ref.data = data;
ref.selector_cache = generateSelectorCache(data);
ref.mapnikFunctions = generateMapnikFunctions(data);
ref.mapnikFunctions.matrix = [6];
ref.mapnikFunctions.translate = [1, 2];
ref.mapnikFunctions.scale = [1, 2];
ref.mapnikFunctions.rotate = [1, 3];
ref.mapnikFunctions.skewX = [1];
ref.mapnikFunctions.skewY = [1];
ref.required_cache = generateRequiredProperties(data);
tree.Reference = {
data: mapnik_reference.version.latest
};
ref.setVersion = function(version) {
var mapnik_reference = require('mapnik-reference');
if (mapnik_reference.version.hasOwnProperty(version)) {
ref.setData(mapnik_reference.version[version]);
return true;
} else {
return false;
}
tree.Reference.setVersion = function(version) {
tree.Reference.data = mapnik_reference.version[version];
};
ref.selectorData = function(selector, i) {
if (ref.selector_cache[selector]) return ref.selector_cache[selector][i];
};
tree.Reference.required_prop_list_cache = {};
ref.validSelector = function(selector) { return !!ref.selector_cache[selector]; };
ref.selectorName = function(selector) { return ref.selectorData(selector, 2); };
ref.selector = function(selector) { return ref.selectorData(selector, 0); };
ref.symbolizer = function(selector) { return ref.selectorData(selector, 1); };
function generateSelectorCache(data) {
var index = {};
for (var i in data.symbolizers) {
for (var j in data.symbolizers[i]) {
if (data.symbolizers[i][j].hasOwnProperty('css')) {
index[data.symbolizers[i][j].css] = [data.symbolizers[i][j], i, j];
tree.Reference.selectors = tree.Reference.selectors || (function() {
var list = [];
for (var i in tree.Reference.data.symbolizers) {
for (var j in tree.Reference.data.symbolizers[i]) {
if (tree.Reference.data.symbolizers[i][j].hasOwnProperty('css')) {
list.push(tree.Reference.data.symbolizers[i][j].css);
}
}
}
return index;
}
return list;
})();
function generateMapnikFunctions(data) {
var functions = {};
for (var i in data.symbolizers) {
for (var j in data.symbolizers[i]) {
if (data.symbolizers[i][j].type === 'functions') {
for (var k = 0; k < data.symbolizers[i][j].functions.length; k++) {
var fn = data.symbolizers[i][j].functions[k];
functions[fn[0]] = fn[1];
}
tree.Reference.validSelector = function(selector) {
return tree.Reference.selectors.indexOf(selector) !== -1;
};
tree.Reference.selectorName = function(selector) {
for (var i in tree.Reference.data.symbolizers) {
for (var j in tree.Reference.data.symbolizers[i]) {
if (selector == tree.Reference.data.symbolizers[i][j].css) {
return j;
}
}
}
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);
tree.Reference.selector = function(selector) {
for (var i in tree.Reference.data.symbolizers) {
for (var j in tree.Reference.data.symbolizers[i]) {
if (selector == tree.Reference.data.symbolizers[i][j].css) {
return tree.Reference.data.symbolizers[i][j];
}
}
}
return cache;
}
};
ref.requiredProperties = function(symbolizer_name, rules) {
var req = ref.required_cache[symbolizer_name];
tree.Reference.symbolizer = function(selector) {
for (var i in tree.Reference.data.symbolizers) {
for (var j in tree.Reference.data.symbolizers[i]) {
if (selector == tree.Reference.data.symbolizers[i][j].css) {
return i;
}
}
}
};
/*
* For transform properties and image-filters,
* mapnik has its own functions.
*/
tree.Reference.mapnikFunction = function(name) {
var functions = [];
for (var i in tree.Reference.data.symbolizers) {
for (var j in tree.Reference.data.symbolizers[i]) {
if (tree.Reference.data.symbolizers[i][j].type === 'functions') {
functions = functions.concat(tree.Reference.data.symbolizers[i][j].functions);
}
}
}
return _.find(functions, function(f) {
return f[0] === name;
});
};
tree.Reference.requiredPropertyList = function(symbolizer_name) {
if (this.required_prop_list_cache[symbolizer_name]) {
return this.required_prop_list_cache[symbolizer_name];
}
var properties = [];
for (var j in tree.Reference.data.symbolizers[symbolizer_name]) {
if (tree.Reference.data.symbolizers[symbolizer_name][j].required) {
properties.push(tree.Reference.data.symbolizers[symbolizer_name][j].css);
}
}
return this.required_prop_list_cache[symbolizer_name] = properties;
};
tree.Reference.requiredProperties = function(symbolizer_name, rules) {
var req = tree.Reference.requiredPropertyList(symbolizer_name);
for (var i in req) {
if (!(req[i] in rules)) {
return 'Property ' + req[i] + ' required for defining ' +
@ -91,8 +106,10 @@ ref.requiredProperties = function(symbolizer_name, rules) {
}
};
// TODO: finish implementation - this is dead code
ref._validateValue = {
/**
* TODO: finish implementation - this is dead code
*/
tree.Reference._validateValue = {
'font': function(env, value) {
if (env.validation_data && env.validation_data.fonts) {
return env.validation_data.fonts.indexOf(value) != -1;
@ -102,118 +119,72 @@ ref._validateValue = {
}
};
ref.isFont = function(selector) {
return ref.selector(selector).validate == 'font';
tree.Reference.isFont = function(selector) {
return tree.Reference.selector(selector).validate == 'font';
};
// https://gist.github.com/982927
ref.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(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(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.validValue = function(env, selector, value) {
tree.Reference.validValue = function(env, selector, value) {
var i, j;
// TODO: handle in reusable way
if (!ref.selector(selector)) {
if (!tree.Reference.selector(selector)) {
return false;
} else if (value.value[0].is == 'keyword') {
return validateKeyword(value, selector);
return tree.Reference
.selector(selector).type
.indexOf(value.value[0].value) !== -1;
} else if (value.value[0].is == 'undefined') {
// caught earlier in the chain - ignore here so that
// error is not overridden
return true;
} else if (ref.selector(selector).type == 'numbers') {
} else if (tree.Reference.selector(selector).type == 'numbers') {
for (i in value.value) {
if (value.value[i].is !== 'float') {
return false;
}
}
return true;
} else if (ref.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 (ref.selector(selector).type == 'functions') {
} else if (tree.Reference.selector(selector).type == 'functions') {
// For backwards compatibility, you can specify a string for `functions`-compatible
// values, though they will not be validated.
return validateFunctions(value, selector);
} else if (ref.selector(selector).type === 'unsigned') {
if (value.value[0].is === 'float') {
value.value[0].round();
if (value.value[0].is === 'string') {
return true;
} else {
return false;
for (i in value.value) {
for (j in value.value[i].value) {
if (value.value[i].value[j].is !== 'call') {
return false;
}
var f = _.find(tree.Reference
.selector(selector).functions, function(x) {
return x[0] == value.value[i].value[j].name;
});
// This filter is unknown
if (!f) return false;
// The filter has been given an incorrect number of arguments
if (f[1] !== value.value[i].value[j].args.length) return false;
}
}
return true;
}
} else if ((ref.selector(selector).expression)) {
} else if (tree.Reference.selector(selector).type == 'expression') {
return true;
} else {
if (ref.selector(selector).validate) {
if (tree.Reference.selector(selector).validate) {
var valid = false;
for (i = 0; i < value.value.length; i++) {
if (ref.selector(selector).type == value.value[i].is &&
ref
if (tree.Reference.selector(selector).type == value.value[i].is &&
tree.Reference
._validateValue
[ref.selector(selector).validate]
[tree.Reference.selector(selector).validate]
(env, value.value[i].value)) {
return true;
}
}
return valid;
} else {
return ref.selector(selector).type == value.value[0].is;
return tree.Reference.selector(selector).type == value.value[0].is;
}
}
};
tree.Reference = ref;
})(require('../tree'));

View File

@ -1,7 +1,4 @@
(function(tree) {
// a rule is a single property and value combination, or variable
// name and value combination, like
// polygon-opacity: 1.0; or @opacity: 1.0;
tree.Rule = function Rule(name, value, index, filename) {
var parts = name.split('/');
this.name = parts.pop();
@ -14,8 +11,6 @@ tree.Rule = function Rule(name, value, index, filename) {
this.variable = (name.charAt(0) === '@');
};
tree.Rule.prototype.is = 'rule';
tree.Rule.prototype.clone = function() {
var clone = Object.create(tree.Rule.prototype);
clone.name = this.name;
@ -29,32 +24,21 @@ tree.Rule.prototype.clone = function() {
};
tree.Rule.prototype.updateID = function() {
return this.id = this.zoom + '#' + this.instance + '#' + this.name;
return this.id = this.zoom + '#' + this.name;
};
tree.Rule.prototype.toString = function() {
return '[' + tree.Zoom.toString(this.zoom) + '] ' + this.name + ': ' + this.value;
};
function getMean(name) {
return Object.keys(tree.Reference.selector_cache).map(function(f) {
return [f, tree.Reference.editDistance(name, f)];
}).sort(function(a, b) { return a[1] - b[1]; });
}
// second argument, if true, outputs the value of this
// rule without the usual attribute="content" wrapping. Right
// now this is just for the TextSymbolizer, but applies to other
// properties in reference.json which specify serialization=content
tree.Rule.prototype.toXML = function(env, content, sep, format) {
if (!tree.Reference.validSelector(this.name)) {
var mean = getMean(this.name);
var mean_message = '';
if (mean[0][1] < 3) {
mean_message = '. Did you mean ' + mean[0][0] + '?';
}
return env.error({
message: "Unrecognized rule: " + this.name + mean_message,
message: "Unrecognized rule: " + this.name,
index: this.index,
type: 'syntax',
filename: this.filename
@ -72,20 +56,13 @@ tree.Rule.prototype.toXML = function(env, content, sep, format) {
filename: this.filename
});
} else {
var typename;
if (tree.Reference.selector(this.name).validate) {
typename = tree.Reference.selector(this.name).validate;
} else if (typeof tree.Reference.selector(this.name).type === 'object') {
typename = 'keyword (options: ' + tree.Reference.selector(this.name).type.join(', ') + ')';
} else {
typename = tree.Reference.selector(this.name).type;
}
return env.error({
message: 'Invalid value for ' +
this.name +
', the type ' + typename +
', a valid ' +
(tree.Reference.selector(this.name).validate ||
tree.Reference.selector(this.name).type) +
' is expected. ' + this.value +
' (of type ' + this.value.value[0].is + ') ' +
' was given.',
index: this.index,
type: 'syntax',
@ -109,10 +86,12 @@ tree.Rule.prototype.toXML = function(env, content, sep, format) {
}
};
// TODO: Rule ev chain should add fontsets to env.frames
tree.Rule.prototype.ev = function(context) {
/**
* TODO: Rule eval chain should add fontsets to env.frames
*/
tree.Rule.prototype['eval'] = function(context) {
return new tree.Rule(this.name,
this.value.ev(context),
this.value['eval'](context),
this.index,
this.filename);
};

View File

@ -7,19 +7,27 @@ tree.Ruleset = function Ruleset(selectors, rules) {
this._lookups = {};
};
tree.Ruleset.prototype = {
is: 'ruleset',
'ev': function(env) {
var i,
ruleset = new tree.Ruleset(this.selectors, this.rules.slice(0));
eval: function(env) {
var 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 imports
if (ruleset.root) {
for (var i = 0; i < ruleset.rules.length; i++) {
if (ruleset.rules[i] instanceof tree.Import) {
Array.prototype.splice
.apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
}
}
}
// Evaluate everything else
for (i = 0, rule; i < ruleset.rules.length; i++) {
for (var i = 0, rule; i < ruleset.rules.length; i++) {
rule = ruleset.rules[i];
ruleset.rules[i] = rule.ev ? rule.ev(env) : rule;
ruleset.rules[i] = rule.eval ? rule.eval(env) : rule;
}
// Pop the stack
@ -44,6 +52,14 @@ tree.Ruleset.prototype = {
variable: function(name) {
return this.variables()[name];
},
/**
* Extend this rule by adding rules from another ruleset
*
* Currently this is designed to accept less specific
* rules and add their values only if this ruleset doesn't
* contain them.
*/
rulesets: function() {
if (this._rulesets) { return this._rulesets; }
else {
@ -62,11 +78,10 @@ tree.Ruleset.prototype = {
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 (match = selector.match(rule.selectors[j])) {
if (selector.elements.length > 1) {
Array.prototype.push.apply(rules, rule.find(
new tree.Selector(null, null, null, selector.elements.slice(1)), self));
new tree.Selector(null, null, selector.elements.slice(1)), self));
} else {
rules.push(rule);
}
@ -77,35 +92,19 @@ tree.Ruleset.prototype = {
});
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;
var selectors = [];
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++) {
for (var 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;
}
// This is an invalid filterset.
if (!child.filters) continue;
if (parents.length) {
for (j = 0; j < parents.length; j++) {
for (var j = 0; j < parents.length; j++) {
var parent = parents[j];
var mergedFilters = parent.filters.cloneWith(child.filters);
@ -114,10 +113,7 @@ tree.Ruleset.prototype = {
// 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.frame_offset === child.frame_offset &&
parent.attachment === child.attachment &&
parent.elements.join() === child.elements.join()) {
selectors.push(parent);
parent.attachment === child.attachment) {
continue;
} else {
mergedFilters = parent.filters;
@ -131,7 +127,6 @@ tree.Ruleset.prototype = {
var clone = Object.create(tree.Selector.prototype);
clone.filters = mergedFilters;
clone.zoom = parent.zoom & child.zoom;
clone.frame_offset = child.frame_offset;
clone.elements = parent.elements.concat(child.elements);
if (parent.attachment && child.attachment) {
clone.attachment = parent.attachment + '/' + child.attachment;
@ -147,10 +142,9 @@ tree.Ruleset.prototype = {
}
var rules = [];
for (i = 0; i < this.rules.length; i++) {
for (var 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) {
@ -161,7 +155,7 @@ tree.Ruleset.prototype = {
}
var index = rules.length ? rules[0].index : false;
for (i = 0; i < selectors.length; i++) {
for (var 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.

View File

@ -1,20 +1,23 @@
var assert = require('assert');
(function(tree) {
tree.Selector = function Selector(filters, zoom, frame_offset, elements, attachment, conditions, index) {
tree.Selector = function Selector(filters, zoom, elements, attachment, conditions, index) {
this.elements = elements || [];
this.attachment = attachment;
this.filters = filters || {};
this.frame_offset = frame_offset;
this.zoom = typeof zoom !== 'undefined' ? zoom : tree.Zoom.all;
this.conditions = conditions;
this.index = index;
};
// 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]
/**
* 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() {
return this.elements.reduce(function(memo, e) {
var spec = e.specificity();

View File

@ -1,68 +1,54 @@
(function(tree) {
var _ = global._ || require('underscore');
var _ = require('underscore');
// Given a style's name, attachment, definitions, and an environment object,
// return a stringified style for Mapnik
tree.StyleXML = function(name, attachment, definitions, env) {
tree.Style = function Style(name, attachment, definitions) {
this.attachment = attachment;
this.definitions = definitions;
this.name = name + (attachment !== '__default__' ? '-' + attachment : '');
};
tree.Style.prototype.toXML = function(env) {
var existing = {};
var image_filters = [], image_filters_inflate = [], direct_image_filters = [], comp_op = [], opacity = [];
for (var i = 0; i < definitions.length; i++) {
for (var j = 0; j < definitions[i].rules.length; j++) {
if (definitions[i].rules[j].name === 'image-filters') {
image_filters.push(definitions[i].rules[j]);
}
if (definitions[i].rules[j].name === 'image-filters-inflate') {
image_filters_inflate.push(definitions[i].rules[j]);
}
if (definitions[i].rules[j].name === 'direct-image-filters') {
direct_image_filters.push(definitions[i].rules[j]);
}
if (definitions[i].rules[j].name === 'comp-op') {
comp_op.push(definitions[i].rules[j]);
}
if (definitions[i].rules[j].name === 'opacity') {
opacity.push(definitions[i].rules[j]);
}
}
}
var image_filters = _.flatten(this.definitions.map(function(definition) {
return definition.rules.filter(function(rule) {
return (rule.name === 'image-filters');
});
}));
var rules = definitions.map(function(definition) {
var comp_op = _.flatten(this.definitions.map(function(definition) {
return definition.rules.filter(function(rule) {
return (rule.name === 'composite-operation');
});
}));
var opacity = _.flatten(this.definitions.map(function(definition) {
return definition.rules.filter(function(rule) {
return (rule.name === 'opacity');
});
}));
var rules = this.definitions.map(function(definition) {
return definition.toXML(env, existing);
});
var attrs_xml = '';
if (image_filters.length) {
attrs_xml += ' image-filters="' + _.chain(image_filters)
// prevent identical filters from being duplicated in the style
.uniq(function(i) { return i.id; }).map(function(f) {
return f.ev(env).toXML(env, true, ',', 'image-filter');
}).value().join(',') + '"';
attrs_xml += ' image-filters="' + image_filters.map(function(f) {
return f.eval(env).toXML(env, true, ' ', 'image-filter');
}).join(' ') + '" ';
}
if (image_filters_inflate.length) {
attrs_xml += ' image-filters-inflate="' + image_filters_inflate[0].value.ev(env).toString() + '"';
if (comp_op.length) {
attrs_xml += ' comp-op="' + comp_op[0].value.eval(env).toString() + '" ';
}
if (direct_image_filters.length) {
attrs_xml += ' direct-image-filters="' + _.chain(direct_image_filters)
// prevent identical filters from being duplicated in the style
.uniq(function(i) { return i.id; }).map(function(f) {
return f.ev(env).toXML(env, true, ',', 'direct-image-filter');
}).value().join(',') + '"';
if (opacity.length) {
attrs_xml += ' opacity="' + opacity[0].value.eval(env).toString() + '" ';
}
if (comp_op.length && comp_op[0].value.ev(env).value != 'src-over') {
attrs_xml += ' comp-op="' + comp_op[0].value.ev(env).toString() + '"';
}
if (opacity.length && opacity[0].value.ev(env).value != 1) {
attrs_xml += ' opacity="' + opacity[0].value.ev(env).toString() + '"';
}
var rule_string = rules.join('');
if (!attrs_xml && !rule_string) return '';
return '<Style name="' + name + '" filter-mode="first"' + attrs_xml + '>\n' + rule_string + '</Style>';
return '<Style name="' + this.name + '" filter-mode="first" ' + attrs_xml + '>\n' + rules.join('') + '</Style>';
};
})(require('../tree'));

View File

@ -3,15 +3,14 @@
tree.URL = function URL(val, paths) {
this.value = val;
this.paths = paths;
this.is = 'uri';
};
tree.URL.prototype = {
is: 'uri',
toString: function() {
return this.value.toString();
},
ev: function(ctx) {
return new tree.URL(this.value.ev(ctx), this.paths);
eval: function(ctx) {
return new tree.URL(this.value.eval(ctx), this.paths);
}
};

View File

@ -2,58 +2,33 @@
tree.Value = function Value(value) {
this.value = value;
this.is = 'value';
};
tree.Value.prototype = {
is: 'value',
ev: function(env) {
eval: function(env) {
if (this.value.length === 1) {
return this.value[0].ev(env);
return this.value[0].eval(env);
} else {
return new tree.Value(this.value.map(function(v) {
return v.ev(env);
return v.eval(env);
}));
}
},
toString: function(env, selector, sep, format) {
return this.value.map(function(e) {
return e.toString(env, format);
}).join(sep || ', ');
},
clone: function() {
var obj = Object.create(tree.Value.prototype);
if (Array.isArray(obj)) obj.value = this.value.slice();
else obj.value = this.value;
obj.is = this.is;
return obj;
},
toJS: function(env) {
//var v = this.value[0].value[0];
var val = this.ev(env);
var v = val.toString();
if(val.is === "color" || val.is === 'uri' || val.is === 'string' || val.is === 'keyword') {
v = "'" + v.replace(/&amp;/g, '&') + "'";
} else if (Array.isArray(this.value) && this.value.length > 1) {
// This covers something like `line-dasharray: 5, 10;`
// where the return _value has more than one element.
// Without this the generated code will look like:
// _value = 5, 10; which will ignore the 10.
v = '[' + this.value.join(',') + ']';
} else if (val.is === 'field') {
// replace [variable] by ctx['variable']
v = v.replace(/\[([^\]]*)\]/g, function(matched) {
return matched.replace(/\[(.*)\]/g, "data['$1']");
});
}else if (val.is === 'call') {
v = JSON.stringify({
name: val.name,
args: val.args
})
}
return "_value = " + v + ";";
}
};
})(require('../tree'));

View File

@ -7,22 +7,20 @@ tree.Variable = function Variable(name, index, filename) {
};
tree.Variable.prototype = {
is: 'variable',
toString: function() {
return this.name;
},
ev: function(env) {
eval: function(env) {
var variable,
v,
that = this,
name = this.name;
if (this._css) return this._css;
var thisframe = env.frames.filter(function(f) {
return f.name == this.name;
}.bind(this));
return f.name == that.name;
});
if (thisframe.length) {
return thisframe[0].value.ev(env);
return thisframe[0].value.eval(env);
} else {
env.error({
message: 'variable ' + this.name + ' is undefined',

View File

@ -4,34 +4,22 @@ var tree = require('../tree');
// and stores them as bit-sequences so that they can be combined,
// inverted, and compared quickly.
tree.Zoom = function(op, value, index) {
this.op = op;
this.value = value;
this.index = index;
};
tree.Zoom.prototype.setZoom = function(zoom) {
this.zoom = zoom;
return this;
};
tree.Zoom.prototype.ev = function(env) {
var start = 0,
end = Infinity,
value = parseInt(this.value.ev(env).toString(), 10),
zoom = 0;
value = parseInt(value, 10);
if (value > tree.Zoom.maxZoom || value < 0) {
env.error({
throw {
message: 'Only zoom levels between 0 and ' +
tree.Zoom.maxZoom + ' supported.',
index: this.index
});
index: index
};
}
switch (this.op) {
var start = 0,
end = Infinity,
zoom = 0;
switch (op) {
case '=':
this.zoom = 1 << value;
return this;
return 1 << value;
case '>':
start = value + 1;
break;
@ -50,12 +38,7 @@ tree.Zoom.prototype.ev = function(env) {
zoom |= (1 << i);
}
}
this.zoom = zoom;
return this;
};
tree.Zoom.prototype.toString = function() {
return this.zoom;
return zoom;
};
// Covers all zoomlevels from 0 to 22
@ -91,12 +74,12 @@ tree.Zoom.ranges = {
};
// Only works for single range zooms. `[XXX....XXXXX.........]` is invalid.
tree.Zoom.prototype.toXML = function() {
tree.Zoom.toXML = function(zoom) {
var conditions = [];
if (this.zoom != tree.Zoom.all) {
if (zoom != tree.Zoom.all) {
var start = null, end = null;
for (var i = 0; i <= tree.Zoom.maxZoom; i++) {
if (this.zoom & (1 << i)) {
if (zoom & (1 << i)) {
if (start === null) start = i;
end = i;
}
@ -109,10 +92,11 @@ tree.Zoom.prototype.toXML = function() {
return conditions;
};
tree.Zoom.prototype.toString = function() {
tree.Zoom.toString = function(zoom) {
var str = '';
for (var i = 0; i <= tree.Zoom.maxZoom; i++) {
str += (this.zoom & (1 << i)) ? 'X' : '.';
str += (zoom & (1 << i)) ? 'X' : '.';
}
return str;
};

View File

@ -1,24 +0,0 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.12.
.TH CARTO "1" "March 2013" "carto 0.9.4" "User Commands"
.SH NAME
carto \- Carto map stylesheet compiler
.SH SYNOPSIS
.B carto [OPTION]
\fI<source MML file>\fR
.SH DESCRIPTION
Carto is a stylesheet renderer for Mapnik. It's an evolution of
the Cascadenik idea and language, with an emphasis on speed and
flexibility.
.SH OPTIONS
.TP
\fB\-v\fR \fB\-\-version\fR
Parse JSON map manifest
.TP
\fB\-b\fR \fB\-\-benchmark\fR
Outputs total compile time
.TP
\fB\-n\fR \fB\-\-nosymlink\fR
Use absolute paths instead of symlinking files
.SH REPORTING BUGS
Please report bugs on the GitHub issue tracker:
<\fBhttps://github.com/mapbox/carto/issues\fR>

2110
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,19 @@
{
"name": "carto",
"version": "0.15.1-cdb5",
"description": "CartoCSS Stylesheet Compiler",
"url": "https://github.com/cartodb/carto",
"repository": {
"version": "0.7.0",
"description": "Mapnik Stylesheet Compiler",
"url": "https://github.com/mapbox/carto",
"repositories": [{
"type": "git",
"url": "http://github.com/cartodb/carto.git"
},
"url": "http://github.com/mapbox/carto.git"
}],
"author": {
"name": "CartoDB",
"url": "http://cartodb.com/"
"name": "MapBox",
"url": "http://mapbox.com/",
"email": "info@mapbox.com"
},
"keywords": [
"mapnik",
"maps",
"css",
"stylesheets"
@ -19,45 +21,32 @@
"contributors": [
"Tom MacWright <macwright@gmail.com>",
"Konstantin Käfer",
"Alexis Sellier <self@cloudhead.net>",
"Raul Ochoa <rochoa@cartodb.com>",
"Javi Santana <jsantana@cartodb.com>"
],
"licenses": [
{
"type": "Apache"
}
"Alexis Sellier <self@cloudhead.net>"
],
"licenses": [{
"type": "Apache"
}],
"bin": {
"carto": "./bin/carto"
"carto": "./bin/carto",
"mml2json.js": "./bin/mml2json.js"
},
"man": "./man/carto.1",
"main": "./lib/carto/index",
"engines": {
"node": ">=0.4.x"
},
"dependencies": {
"underscore": "1.8.3",
"mapnik-reference": "~6.0.2",
"optimist": "~0.6.0"
"underscore": "~1.3.3",
"mapnik-reference": "https://github.com/mapnik/mapnik-reference/tarball/transform-functions",
"xml2js": "~0.1.13"
},
"devDependencies": {
"mocha": "1.12.x",
"mocha": "1.0.x",
"docco": "0.3.x",
"jshint": "0.2.x",
"sax": "0.1.x",
"istanbul": "~0.2.14",
"coveralls": "~2.10.1",
"browserify": "~7.0.0",
"uglify-js": "1.3.3"
"sax": "0.1.x"
},
"scripts": {
"pretest": "npm install",
"test": "mocha -R spec",
"tdd": "env HIDE_LOGS=true mocha -w -R spec",
"coverage": "istanbul cover ./node_modules/.bin/_mocha && coveralls < ./coverage/lcov.info",
"bump": "npm version patch",
"bump:major": "npm version major",
"bump:minor": "npm version minor",
"postversion": "git push origin master --follow-tags"
"pretest": "npm install --dev",
"test": "mocha"
}
}

View File

@ -1,37 +0,0 @@
var assert = require('assert');
var exec = require('child_process').exec;
var path = require('path');
var util = require('util');
var helper = require('./support/helper');
var bin = path.resolve(path.join(__dirname, '..', 'bin', 'carto'));
var fs = require('fs');
describe('bin/carto', function() {
it('errors on no input', function(done) {
exec(bin, function(err, stdout, stderr) {
assert.equal(1, err.code);
assert.equal("carto: no input files ('carto -h or --help' for help)\n", stdout);
done();
});
});
it('renders mml', function(done) {
var file = path.join(__dirname, 'rendering', 'identity.mml');
exec(util.format('%s %s', bin, file), function(err, stdout, stderr) {
assert.ifError(err);
helper.compareToXMLFile(helper.resultFile(file), stdout, done, [
helper.removeAbsoluteImages,
helper.removeAbsoluteDatasources
]);
});
});
it('renders mss', function(done) {
var file = path.join(__dirname, 'rendering-mss', 'empty_name.mss');
exec(util.format('%s %s', bin, file), function(err, stdout, stderr) {
assert.ifError(err);
var expected = file.replace(path.extname(file),'')+'.xml';
var expected_data = fs.readFileSync(expected, 'utf8');
assert.equal(stdout,expected_data + '\n');
done();
});
});
});

View File

@ -1,25 +0,0 @@
var assert = require('assert');
var tree = require('../lib/carto/tree.js');
require('../lib/carto/functions');
require('../lib/carto/tree/color');
require('../lib/carto/tree/dimension');
describe('Color', function() {
describe('basic functionality', function() {
it('should be constructed', function() {
var f = new tree.Color([0, 0, 0], 1);
assert.deepEqual(f.toHSL(), {"h":0,"s":0,"l":0,"a":1});
assert.ok(f);
});
});
describe('functions', function() {
it('should be constructed', function() {
assert.deepEqual(tree.functions.rgb(0, 0, 0), new tree.Color([0, 0, 0], 1));
assert.deepEqual(tree.functions.hue(new tree.Color([0, 0, 0], 1)), new tree.Dimension(0));
assert.deepEqual(tree.functions.saturation(new tree.Color([0, 0, 0], 1)), new tree.Dimension(0, '%'));
assert.deepEqual(tree.functions.lightness(new tree.Color([0, 0, 0], 1)), new tree.Dimension(0, '%'));
assert.deepEqual(tree.functions.alpha(new tree.Color([0, 0, 0], 1)), new tree.Dimension(1));
assert.deepEqual(tree.functions.greyscale(new tree.Color([0, 0, 0], 1)), new tree.Color([0, 0, 0], 1));
});
});
});

View File

@ -1,14 +0,0 @@
var assert = require('assert');
var tree = require('../lib/carto/tree.js');
require('../lib/carto/tree/comment');
describe('Comment', function() {
describe('basic functionality', function() {
it('should be constructed', function() {
var f = new tree.Comment('hello world');
assert.deepEqual(f.toString(), '<!--hello world-->');
assert.deepEqual(f.ev(), f);
assert.ok(f);
});
});
});

View File

@ -6,10 +6,9 @@ var carto = require('../lib/carto');
var tree = require('../lib/carto/tree');
var helper = require('./support/helper');
describe('Error handling mml+mss', function() {
describe('Error handling', function() {
helper.files('errorhandling', 'mml', function(file) {
var basename = path.basename(file);
it('should handle errors in ' + basename, function(done) {
it('should handle errors in ' + path.basename(file), function(done) {
var completed = false;
var renderResult;
var mml = helper.mml(file);
@ -19,58 +18,26 @@ helper.files('errorhandling', 'mml', function(file) {
data_dir: path.join(__dirname, '../data'),
local_data_dir: path.join(__dirname, 'rendering'),
filename: file
}).render(mml);
// should not get here
assert.ok(false);
done();
}).render(mml, function (err) {
var result = helper.resultFile(file);
var output = err.message;
// @TODO for some reason, fs.readFile includes an additional \n
// at the end of read files. Determine why.
fs.readFile(helper.resultFile(file), 'utf8', function(err, data) {
if (!err) assert.deepEqual(output, data.substr(0, data.length - 1));
});
});
} catch(err) {
if (err.message.indexOf('***') > -1) throw err;
var result = helper.resultFile(file);
var output = err.message;
// @TODO for some reason, fs.readFile includes an additional \n
// at the end of read files. Determine why.
// fs.writeFileSync(helper.resultFile(file), output);
var data = fs.readFileSync(helper.resultFile(file), 'utf8');
assert.deepEqual(output, data);
done();
fs.readFile(helper.resultFile(file), 'utf8', function(err, data) {
if (!err) assert.deepEqual(output, data.substr(0, data.length - 1));
});
}
});
});
});
describe('Error handling mss', function() {
helper.files('errorhandling', 'mss', function(file) {
var basename = path.basename(file);
if (basename == 'multi_stylesheets_a.mss') {
return;
}
it('should handle errors in ' + basename, function(done) {
var completed = false;
var renderResult;
var mss = helper.mss(file);
try {
new carto.Renderer({
paths: [ path.dirname(file) ],
data_dir: path.join(__dirname, '../data'),
local_data_dir: path.join(__dirname, 'rendering'),
// note: we use the basename here so that the expected error result
// will match if the style was loaded from mml
filename: basename
}).renderMSS(mss);
// should not get here
assert.ok(false);
done();
} catch(err) {
if (err.message.indexOf('***') > -1) throw err;
var result = helper.resultFile(file);
var output = err.message;
// @TODO for some reason, fs.readFile includes an additional \n
// at the end of read files. Determine why.
// fs.writeFileSync(helper.resultFile(file), output);
var data = fs.readFileSync(helper.resultFile(file), 'utf8');
assert.deepEqual(output, data);
done();
}
done();
});
});
});

View File

@ -1,15 +0,0 @@
{
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
"Stylesheet": [
"bad_op.mss"
],
"Layer": [{
"id": "world",
"name": "world",
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
"Datasource": {
"file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
"type": "shape"
}
}]
}

View File

@ -1,3 +0,0 @@
#world {
line-width: 20% + 2px;
}

View File

@ -1 +0,0 @@
bad_op.mss:2:4 If two operands differ, the first must not be %

View File

@ -1,15 +0,0 @@
{
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
"Stylesheet": [
"bad_op_2.mss"
],
"Layer": [{
"id": "world",
"name": "world",
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
"Datasource": {
"file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
"type": "shape"
}
}]
}

View File

@ -1,3 +0,0 @@
#world {
line-width: 20px * 2%;
}

View File

@ -1 +0,0 @@
bad_op_2.mss:2:4 Percent values can only be added or subtracted from other values

View File

@ -1,15 +0,0 @@
{
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
"Stylesheet": [
"color_functions.mss"
],
"Layer": [{
"id": "world",
"name": "world",
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
"Datasource": {
"file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
"type": "shape"
}
}]
}

View File

@ -1,4 +0,0 @@
@foo: 'bar';
#world {
polygon-fill: hsl(1, @foo, 3);
}

View File

@ -1 +0,0 @@
color_functions.mss:3:31 incorrect arguments given to hsl()

View File

@ -1,15 +0,0 @@
{
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
"Stylesheet": [
"contradiction.mss"
],
"Layer": [{
"name": "world",
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
"Datasource": {
"file": "http://tilemill-data.s3.amazonaws.com/district.geojson",
"type": "ogr",
"layer": "OGRGeoJSON"
}
}]
}

View File

@ -1,3 +0,0 @@
#world[FeatureCla!=""][FeatureCla=""] {
polygon-fill: #fff;
}

View File

@ -1 +0,0 @@
contradiction.mss:1:37 [[FeatureCla]=] added to [FeatureCla]!= produces an invalid filter

View File

@ -1,3 +0,0 @@
#world[FeatureCla=""][FeatureCla!=""] {
polygon-fill: #fff;
}

View File

@ -1 +0,0 @@
contradiction_2.mss:1:37 [[FeatureCla]!=] added to [FeatureCla]= produces an invalid filter

View File

@ -1,15 +0,0 @@
{
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
"Stylesheet": [
"function_args.mss"
],
"Layer": [{
"id": "world",
"name": "world",
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
"Datasource": {
"file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
"type": "shape"
}
}]
}

View File

@ -1,4 +0,0 @@
#world {
point-transform: scale(2, 2);
image-filters: agg-stack-blu(2, 1);
}

View File

@ -1 +0,0 @@
function_args.mss:3:38 unknown function agg-stack-blu(), did you mean agg-stack-blur(2)

View File

@ -1,3 +0,0 @@
#world {
polygon-fill: spin(#f00f00f, 10);
}

View File

@ -1 +0,0 @@
invalid_color_in_fn.mss:2:34 incorrect arguments given to spin()

View File

@ -1 +1 @@
invalid_property.mss:3:2 Unrecognized rule: polygonopacity. Did you mean polygon-opacity?
invalid_property.mss:3:2 Unrecognized rule: polygonopacity

View File

@ -1,4 +1,3 @@
#world[zoom=5] {
text-face-name: 2;
text-name: 'foo';
polygon-opacity: #f00;
}

View File

@ -1 +1 @@
invalid_value.mss:2:2 Invalid value for text-face-name, the type font is expected. 2 (of type float) was given.
invalid_value.mss:2:2 Invalid value for polygon-opacity, a valid float is expected. #ff0000 was given.

View File

@ -1,15 +0,0 @@
{
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
"Stylesheet": [
"invaliddimension.mss"
],
"Layer": [{
"id": "world",
"name": "world",
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
"Datasource": {
"file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
"type": "shape"
}
}]
}

View File

@ -1,3 +0,0 @@
#world {
line-width: 10wifflewaffles;
}

View File

@ -1 +0,0 @@
invaliddimension.mss:2:4 Invalid unit: 'wifflewaffles'

View File

@ -1 +0,0 @@
issue119.mss:2:2 Map properties are not permitted in other rules

View File

@ -1 +1 @@
issue123.mss:3:31 incorrect number of arguments for darken(). 2 expected.
issue123.mss:3:31 incorrect number of arguments for darken(). 2 expected.

View File

@ -1 +0,0 @@
issue124.mss:6:0 missing closing `}`

View File

@ -1,8 +0,0 @@
{
"Stylesheet": [
"issue297.mss"
],
"Layer": [{
"name": "t"
}]
}

View File

@ -1,4 +0,0 @@
#t {
text-name: valid;
text-face-name: 2;
}

View File

@ -1 +0,0 @@
issue297.mss:3:2 Invalid value for text-face-name, the type font is expected. 2 (of type float) was given.

Some files were not shown because too many files have changed in this diff Show More