Compare commits
No commits in common. "master" and "field" have entirely different histories.
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,6 +1 @@
|
|||||||
/node_modules
|
/node_modules
|
||||||
.DS_Store
|
|
||||||
test/rendering/layers/
|
|
||||||
test/rendering/cache/
|
|
||||||
test/rendering-mss/npm-debug.log
|
|
||||||
.idea/
|
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
|
|
||||||
node_js:
|
node_js:
|
||||||
- '6'
|
- 0.6
|
||||||
- '8'
|
|
||||||
- '10'
|
|
||||||
|
|
||||||
script:
|
|
||||||
- npm test
|
|
||||||
|
@ -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
|
|
104
CHANGELOG.md
104
CHANGELOG.md
@ -1,112 +1,16 @@
|
|||||||
## Changelog
|
## 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
|
## 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 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.
|
* 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
|
### 0.7.0
|
||||||
|
|
||||||
* Updated mapnik-reference to `~2.1.0`
|
|
||||||
* Support an `opacity` property on any style that is a style-level property
|
* Support an `opacity` property on any style that is a style-level property
|
||||||
|
|
||||||
### 0.6.0
|
### 0.6.0
|
||||||
|
@ -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.
|
|
19
Makefile
19
Makefile
@ -3,15 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
expresso = ./node_modules/.bin/mocha
|
expresso = ./node_modules/.bin/mocha
|
||||||
UGLIFYJS=./node_modules/.bin/uglifyjs
|
docco = ./node_modules/.bin/docco
|
||||||
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 > $@
|
|
||||||
|
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
./node_modules/.bin/jshint lib/carto/*.js lib/carto/tree/*.js
|
./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
|
@NODE_PATH=./lib:$NODE_PATH $(expresso) -R spec -I lib test/${only}.test.js
|
||||||
endif
|
endif
|
||||||
|
|
||||||
check: test
|
doc:
|
||||||
|
$(docco) lib/carto/*.js lib/carto/tree/*.js
|
||||||
dist:
|
|
||||||
mkdir -p dist
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
|
375
README.md
375
README.md
@ -1,83 +1,308 @@
|
|||||||
# CartoCSS
|
# carto
|
||||||
|
|
||||||
[![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``
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[![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
|
## Reference Documentation
|
||||||
|
|
||||||
* [mapbox.com/carto](http://mapbox.com/carto/)
|
* [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><Stylesheet><![CDATA[
|
||||||
|
Map
|
||||||
|
{
|
||||||
|
map-bgcolor: #69f;
|
||||||
|
}
|
||||||
|
|
||||||
|
Layer
|
||||||
|
{
|
||||||
|
line-width: 1;
|
||||||
|
line-color: #696;
|
||||||
|
polygon-fill: #6f9;
|
||||||
|
}
|
||||||
|
]]></Stylesheet>
|
||||||
|
<Layer srs="+proj=latlong +ellps=WGS84 +datum=WGS84 +no_defs">
|
||||||
|
<Datasource>
|
||||||
|
<Parameter name="type">shape</Parameter>
|
||||||
|
<Parameter name="file">world_borders</Parameter>
|
||||||
|
</Datasource>
|
||||||
|
</Layer>
|
||||||
|
</Map></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><FontSet name="fontset-0">
|
||||||
|
<Font face-name="Georgia Regular"/>
|
||||||
|
<Font face-name="Arial Italic"/>
|
||||||
|
</FontSet>
|
||||||
|
<Style name="world-text">
|
||||||
|
<Rule>
|
||||||
|
<TextSymbolizer fontset-name="fontset-0"
|
||||||
|
size="11"
|
||||||
|
name="[NAME]"/>
|
||||||
|
</Rule>
|
||||||
|
</Style></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
215
bin/carto
@ -2,40 +2,46 @@
|
|||||||
|
|
||||||
var path = require('path'),
|
var path = require('path'),
|
||||||
fs = require('fs'),
|
fs = require('fs'),
|
||||||
carto = require('../lib/carto'),
|
util = require('util'),
|
||||||
url = require('url'),
|
carto = require('carto');
|
||||||
_ = require('underscore');
|
|
||||||
|
|
||||||
var existsSync = require('fs').existsSync || require('path').existsSync
|
var args = process.argv.slice(1);
|
||||||
|
var options = {};
|
||||||
|
|
||||||
var optimist = require('optimist')
|
args = args.filter(function (arg) {
|
||||||
.usage("Usage: $0 <source MML file>")
|
var match;
|
||||||
.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});
|
|
||||||
|
|
||||||
var options = optimist.argv;
|
if (match = arg.match(/^--?([a-z][0-9a-z-]*)$/i)) { arg = match[1] }
|
||||||
|
else { return arg }
|
||||||
|
|
||||||
if (options.help) {
|
switch (arg) {
|
||||||
optimist.showHelp();
|
case 'v':
|
||||||
process.exit(0);
|
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) {
|
default:
|
||||||
console.log("carto " + carto.version.join('.') + " (Carto map stylesheet compiler)");
|
util.puts("Usage: carto <source MML file>");
|
||||||
process.exit(0);
|
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] != '/') {
|
if (input && input[0] != '/') {
|
||||||
input = path.join(process.cwd(), input);
|
input = path.join(process.cwd(), input);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!input) {
|
if (!input) {
|
||||||
console.log("carto: no input files ('carto -h or --help' for help)");
|
util.puts("carto: no input files");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,120 +49,63 @@ if (options.benchmark) {
|
|||||||
var start = +new Date;
|
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 {
|
try {
|
||||||
var data = fs.readFileSync(input, 'utf-8');
|
var data = fs.readFileSync(input, 'utf-8');
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.error("carto: " + err.message.replace(/^[A-Z]+, /, ''));
|
util.puts("carto: " + err.message.replace(/^[A-Z]+, /, ''));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ext == '.mml') {
|
try {
|
||||||
try {
|
data = JSON.parse(data);
|
||||||
data = JSON.parse(data);
|
} catch(err) {
|
||||||
} catch(err) {
|
util.puts("carto: " + err.message.replace(/^[A-Z]+, /, ''));
|
||||||
console.error("carto: " + err.message.replace(/^[A-Z]+, /, ''));
|
process.exit(1);
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
66
bin/mml2json.js
Executable 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);
|
||||||
|
});
|
@ -264,11 +264,11 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>match</key>
|
<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>
|
||||||
<dict>
|
<dict>
|
||||||
<key>match</key>
|
<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>
|
<key>name</key>
|
||||||
<string>meta.property-value.carto</string>
|
<string>meta.property-value.carto</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
//
|
|
||||||
// LESS - Leaner CSS v@VERSION
|
|
||||||
// http://lesscss.org
|
|
||||||
//
|
|
||||||
// Copyright (c) 2010, Alexis Sellier
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
//
|
|
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
var path = require('path'),
|
var path = require('path'),
|
||||||
fs = require('fs'),
|
fs = require('fs'),
|
||||||
_ = require('underscore')._;
|
_ = require('underscore')._,
|
||||||
|
sys = require('sys');
|
||||||
|
|
||||||
var carto = require('../lib/carto');
|
var carto = require('../lib/carto');
|
||||||
|
|
||||||
|
@ -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+[0-9A-Fa-f?]\+"
|
||||||
syn match cartoUnicodeRange contained "U+\x\+-\x\+"
|
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 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\|/"
|
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
4
dist/carto.js
vendored
File diff suppressed because one or more lines are too long
7375
dist/carto.uncompressed.js
vendored
7375
dist/carto.uncompressed.js
vendored
File diff suppressed because it is too large
Load Diff
@ -1,15 +0,0 @@
|
|||||||
# Generating CartoCSS docs
|
|
||||||
|
|
||||||
From the `docs-generator/` directory:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
Then:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ node generate.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Will save docs to `docs/`.
|
|
@ -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,
|
|
||||||
_: _
|
|
||||||
}));
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
```
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -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%><% } %>
|
|
||||||
* * *
|
|
||||||
<% }); %>
|
|
||||||
<% }); %>
|
|
1159
docs/2.0.0.md
1159
docs/2.0.0.md
File diff suppressed because it is too large
Load Diff
1159
docs/2.0.1.md
1159
docs/2.0.1.md
File diff suppressed because it is too large
Load Diff
1159
docs/2.0.2.md
1159
docs/2.0.2.md
File diff suppressed because it is too large
Load Diff
1486
docs/2.1.0.md
1486
docs/2.1.0.md
File diff suppressed because it is too large
Load Diff
1495
docs/2.1.1.md
1495
docs/2.1.1.md
File diff suppressed because it is too large
Load Diff
1687
docs/latest.md
1687
docs/latest.md
File diff suppressed because it is too large
Load Diff
@ -7,38 +7,17 @@ tree.functions = {
|
|||||||
rgba: function (r, g, b, a) {
|
rgba: function (r, g, b, a) {
|
||||||
var rgb = [r, g, b].map(function (c) { return number(c); });
|
var rgb = [r, g, b].map(function (c) { return number(c); });
|
||||||
a = number(a);
|
a = number(a);
|
||||||
if (rgb.some(isNaN) || isNaN(a)) return null;
|
|
||||||
return new tree.Color(rgb, a);
|
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) {
|
hsl: function (h, s, l) {
|
||||||
return this.hsla(h, s, l, 1.0);
|
return this.hsla(h, s, l, 1.0);
|
||||||
},
|
},
|
||||||
hsla: function (h, s, l, a) {
|
hsla: function (h, s, l, a) {
|
||||||
h = (number(h) % 360) / 360;
|
h = (number(h) % 360) / 360;
|
||||||
s = number(s); l = number(l); a = number(a);
|
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,
|
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
|
||||||
m1 = l * 2 - m2;
|
var m1 = l * 2 - m2;
|
||||||
|
|
||||||
return this.rgba(hue(h + 1/3) * 255,
|
return this.rgba(hue(h + 1/3) * 255,
|
||||||
hue(h) * 255,
|
hue(h) * 255,
|
||||||
@ -54,23 +33,18 @@ tree.functions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
hue: function (color) {
|
hue: function (color) {
|
||||||
if (!('toHSL' in color)) return null;
|
|
||||||
return new tree.Dimension(Math.round(color.toHSL().h));
|
return new tree.Dimension(Math.round(color.toHSL().h));
|
||||||
},
|
},
|
||||||
saturation: function (color) {
|
saturation: function (color) {
|
||||||
if (!('toHSL' in color)) return null;
|
|
||||||
return new tree.Dimension(Math.round(color.toHSL().s * 100), '%');
|
return new tree.Dimension(Math.round(color.toHSL().s * 100), '%');
|
||||||
},
|
},
|
||||||
lightness: function (color) {
|
lightness: function (color) {
|
||||||
if (!('toHSL' in color)) return null;
|
|
||||||
return new tree.Dimension(Math.round(color.toHSL().l * 100), '%');
|
return new tree.Dimension(Math.round(color.toHSL().l * 100), '%');
|
||||||
},
|
},
|
||||||
alpha: function (color) {
|
alpha: function (color) {
|
||||||
if (!('toHSL' in color)) return null;
|
|
||||||
return new tree.Dimension(color.toHSL().a);
|
return new tree.Dimension(color.toHSL().a);
|
||||||
},
|
},
|
||||||
saturate: function (color, amount) {
|
saturate: function (color, amount) {
|
||||||
if (!('toHSL' in color)) return null;
|
|
||||||
var hsl = color.toHSL();
|
var hsl = color.toHSL();
|
||||||
|
|
||||||
hsl.s += amount.value / 100;
|
hsl.s += amount.value / 100;
|
||||||
@ -78,7 +52,6 @@ tree.functions = {
|
|||||||
return hsla(hsl);
|
return hsla(hsl);
|
||||||
},
|
},
|
||||||
desaturate: function (color, amount) {
|
desaturate: function (color, amount) {
|
||||||
if (!('toHSL' in color)) return null;
|
|
||||||
var hsl = color.toHSL();
|
var hsl = color.toHSL();
|
||||||
|
|
||||||
hsl.s -= amount.value / 100;
|
hsl.s -= amount.value / 100;
|
||||||
@ -86,7 +59,6 @@ tree.functions = {
|
|||||||
return hsla(hsl);
|
return hsla(hsl);
|
||||||
},
|
},
|
||||||
lighten: function (color, amount) {
|
lighten: function (color, amount) {
|
||||||
if (!('toHSL' in color)) return null;
|
|
||||||
var hsl = color.toHSL();
|
var hsl = color.toHSL();
|
||||||
|
|
||||||
hsl.l += amount.value / 100;
|
hsl.l += amount.value / 100;
|
||||||
@ -94,7 +66,6 @@ tree.functions = {
|
|||||||
return hsla(hsl);
|
return hsla(hsl);
|
||||||
},
|
},
|
||||||
darken: function (color, amount) {
|
darken: function (color, amount) {
|
||||||
if (!('toHSL' in color)) return null;
|
|
||||||
var hsl = color.toHSL();
|
var hsl = color.toHSL();
|
||||||
|
|
||||||
hsl.l -= amount.value / 100;
|
hsl.l -= amount.value / 100;
|
||||||
@ -102,7 +73,6 @@ tree.functions = {
|
|||||||
return hsla(hsl);
|
return hsla(hsl);
|
||||||
},
|
},
|
||||||
fadein: function (color, amount) {
|
fadein: function (color, amount) {
|
||||||
if (!('toHSL' in color)) return null;
|
|
||||||
var hsl = color.toHSL();
|
var hsl = color.toHSL();
|
||||||
|
|
||||||
hsl.a += amount.value / 100;
|
hsl.a += amount.value / 100;
|
||||||
@ -110,7 +80,6 @@ tree.functions = {
|
|||||||
return hsla(hsl);
|
return hsla(hsl);
|
||||||
},
|
},
|
||||||
fadeout: function (color, amount) {
|
fadeout: function (color, amount) {
|
||||||
if (!('toHSL' in color)) return null;
|
|
||||||
var hsl = color.toHSL();
|
var hsl = color.toHSL();
|
||||||
|
|
||||||
hsl.a -= amount.value / 100;
|
hsl.a -= amount.value / 100;
|
||||||
@ -118,7 +87,6 @@ tree.functions = {
|
|||||||
return hsla(hsl);
|
return hsla(hsl);
|
||||||
},
|
},
|
||||||
spin: function (color, amount) {
|
spin: function (color, amount) {
|
||||||
if (!('toHSL' in color)) return null;
|
|
||||||
var hsl = color.toHSL();
|
var hsl = color.toHSL();
|
||||||
var hue = (hsl.h + amount.value) % 360;
|
var hue = (hsl.h + amount.value) % 360;
|
||||||
|
|
||||||
@ -174,7 +142,7 @@ var image_filter_functors = [
|
|||||||
'x-gradient', 'y-gradient', 'sharpen'];
|
'x-gradient', 'y-gradient', 'sharpen'];
|
||||||
|
|
||||||
for (var i = 0; i < image_filter_functors.length; i++) {
|
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) {
|
tree.functions[f] = (function(f) {
|
||||||
return function() {
|
return function() {
|
||||||
return new tree.ImageFilter(f);
|
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]);
|
return new tree.ImageFilter('agg-stack-blur', [x, y]);
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.functions['scale-hsla'] = function(h0,h1,s0,s1,l0,l1,a0,a1) {
|
function hsla(hsla) {
|
||||||
return new tree.ImageFilter('scale-hsla', [h0,h1,s0,s1,l0,l1,a0,a1]);
|
return tree.functions.hsla(hsla.h, hsla.s, hsla.l, hsla.a);
|
||||||
};
|
|
||||||
|
|
||||||
function hsla(h) {
|
|
||||||
return tree.functions.hsla(h.h, h.s, h.l, h.a);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function number(n) {
|
function number(n) {
|
||||||
@ -200,7 +164,7 @@ function number(n) {
|
|||||||
} else if (typeof(n) === 'number') {
|
} else if (typeof(n) === 'number') {
|
||||||
return n;
|
return n;
|
||||||
} else {
|
} else {
|
||||||
return NaN;
|
throw new Error('Color functions take numbers as parameters.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,27 +1,9 @@
|
|||||||
var util = require('util'),
|
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 carto = {
|
var carto = {
|
||||||
version: getVersion(),
|
version: [0, 4, 7],
|
||||||
Parser: require('./parser').Parser,
|
Parser: require('./parser').Parser,
|
||||||
Renderer: require('./renderer').Renderer,
|
Renderer: require('./renderer').Renderer,
|
||||||
tree: require('./tree'),
|
tree: require('./tree'),
|
||||||
RendererJS: require('./renderer_js'),
|
|
||||||
default_reference: require('./torque-reference'),
|
|
||||||
|
|
||||||
// @TODO
|
// @TODO
|
||||||
writeError: function(ctx, options) {
|
writeError: function(ctx, options) {
|
||||||
@ -53,10 +35,10 @@ var carto = {
|
|||||||
if (typeof(extract[2]) === 'string') {
|
if (typeof(extract[2]) === 'string') {
|
||||||
error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
|
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');
|
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);
|
util.error(message, error);
|
||||||
|
|
||||||
@ -68,34 +50,15 @@ var carto = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
require('./tree/call');
|
[ 'call', 'color', 'comment', 'definition', 'dimension',
|
||||||
require('./tree/color');
|
'directive', 'element', 'expression', 'filterset', 'filter', 'field',
|
||||||
require('./tree/comment');
|
'keyword', 'layer', 'literal', 'operation', 'quoted', 'imagefilter',
|
||||||
require('./tree/definition');
|
'reference', 'rule', 'ruleset', 'selector', 'style', 'url', 'value',
|
||||||
require('./tree/dimension');
|
'variable', 'zoom', 'invalid', 'fontset'
|
||||||
require('./tree/element');
|
].forEach(function(n) {
|
||||||
require('./tree/expression');
|
require('./tree/' + n);
|
||||||
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');
|
|
||||||
require('./functions');
|
require('./functions');
|
||||||
|
|
||||||
for (var k in carto) { exports[k] = carto[k]; }
|
for (var k in carto) { exports[k] = carto[k]; }
|
||||||
@ -111,6 +74,7 @@ function stylize(str, style) {
|
|||||||
'red' : [31, 39],
|
'red' : [31, 39],
|
||||||
'grey' : [90, 39]
|
'grey' : [90, 39]
|
||||||
};
|
};
|
||||||
return '\x1B[' + styles[style][0] + 'm' + str +
|
return '\033[' + styles[style][0] + 'm' + str +
|
||||||
'\x1B[' + styles[style][1] + 'm';
|
'\033[' + styles[style][1] + 'm';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,46 @@
|
|||||||
var carto = exports,
|
var carto, tree, _;
|
||||||
tree = require('./tree'),
|
|
||||||
_ = global._ || require('underscore');
|
|
||||||
|
|
||||||
|
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
|
// Token matching is done with the `$` function, which either takes
|
||||||
// a terminal string or regexp, or a non-terminal function to call.
|
// a terminal string or regexp, or a non-terminal function to call.
|
||||||
// It also takes care of moving all the indices forwards.
|
// It also takes care of moving all the indices forwards.
|
||||||
|
|
||||||
carto.Parser = function Parser(env) {
|
carto.Parser = function Parser(env) {
|
||||||
var input, // LeSS input string
|
var input, // LeSS input string
|
||||||
i, // current index in `input`
|
i, // current index in `input`
|
||||||
@ -22,6 +58,29 @@ carto.Parser = function Parser(env) {
|
|||||||
// have been imported through `@import`.
|
// have been imported through `@import`.
|
||||||
var finish = function() {};
|
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() {
|
function save() {
|
||||||
temp = chunks[j];
|
temp = chunks[j];
|
||||||
memo = i;
|
memo = i;
|
||||||
@ -45,12 +104,17 @@ carto.Parser = function Parser(env) {
|
|||||||
function $(tok) {
|
function $(tok) {
|
||||||
var match, args, length, c, index, endIndex, k;
|
var match, args, length, c, index, endIndex, k;
|
||||||
|
|
||||||
|
//
|
||||||
// Non-terminal
|
// Non-terminal
|
||||||
|
//
|
||||||
if (tok instanceof Function) {
|
if (tok instanceof Function) {
|
||||||
return tok.call(parser.parsers);
|
return tok.call(parser.parsers);
|
||||||
|
//
|
||||||
// Terminal
|
// 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') {
|
} else if (typeof(tok) === 'string') {
|
||||||
match = input.charAt(i) === tok ? tok : null;
|
match = input.charAt(i) === tok ? tok : null;
|
||||||
length = 1;
|
length = 1;
|
||||||
@ -98,15 +162,14 @@ carto.Parser = function Parser(env) {
|
|||||||
if (typeof(tok) === 'string') {
|
if (typeof(tok) === 'string') {
|
||||||
return input.charAt(i) === tok;
|
return input.charAt(i) === tok;
|
||||||
} else {
|
} 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.
|
// Make an error object from a passed set of properties.
|
||||||
// Accepted properties:
|
// Accepted properties:
|
||||||
// - `message`: Text of the error message.
|
// - `message`: Text of the error message.
|
||||||
@ -114,9 +177,8 @@ carto.Parser = function Parser(env) {
|
|||||||
// - `index`: Char. index where the error occurred.
|
// - `index`: Char. index where the error occurred.
|
||||||
function makeError(err) {
|
function makeError(err) {
|
||||||
var einput;
|
var einput;
|
||||||
var errorTemplate;
|
|
||||||
|
|
||||||
_.defaults(err, {
|
_(err).defaults({
|
||||||
index: furthest,
|
index: furthest,
|
||||||
filename: env.filename,
|
filename: env.filename,
|
||||||
message: 'Parse error.',
|
message: 'Parse error.',
|
||||||
@ -130,12 +192,11 @@ carto.Parser = function Parser(env) {
|
|||||||
einput = input;
|
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--) {
|
for (var n = err.index; n >= 0 && einput.charAt(n) !== '\n'; n--) {
|
||||||
err.column++;
|
err.column++;
|
||||||
}
|
}
|
||||||
errorTemplate = _.template('<%=filename%>:<%=line%>:<%=column%> <%=message%>');
|
return new Error(_('<%=filename%>:<%=line%>:<%=column%> <%=message%>').template(err));
|
||||||
return new Error(errorTemplate(err));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.env = env = env || {};
|
this.env = env = env || {};
|
||||||
@ -143,9 +204,9 @@ carto.Parser = function Parser(env) {
|
|||||||
this.env.inputs = this.env.inputs || {};
|
this.env.inputs = this.env.inputs || {};
|
||||||
|
|
||||||
// The Parser
|
// The Parser
|
||||||
parser = {
|
return parser = {
|
||||||
|
|
||||||
extractErrorLine: extractErrorLine,
|
imports: imports,
|
||||||
//
|
//
|
||||||
// Parse an input string into an abstract syntax tree.
|
// Parse an input string into an abstract syntax tree.
|
||||||
// Throws an error on parse errors.
|
// Throws an error on parse errors.
|
||||||
@ -160,19 +221,17 @@ carto.Parser = function Parser(env) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var early_exit = false;
|
var early_exit = false;
|
||||||
|
|
||||||
// Split the input into chunks.
|
// Split the input into chunks.
|
||||||
chunks = (function (chunks) {
|
chunks = (function(chunks) {
|
||||||
var j = 0,
|
var j = 0,
|
||||||
skip = /(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,
|
skip = /[^"'`\{\}\/]+/g,
|
||||||
comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
|
comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
|
||||||
string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,
|
|
||||||
level = 0,
|
level = 0,
|
||||||
match,
|
match,
|
||||||
chunk = chunks[0],
|
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;
|
skip.lastIndex = i;
|
||||||
if (match = skip.exec(input)) {
|
if (match = skip.exec(input)) {
|
||||||
if (match.index === i) {
|
if (match.index === i) {
|
||||||
@ -181,65 +240,59 @@ carto.Parser = function Parser(env) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
c = input.charAt(i);
|
c = input.charAt(i);
|
||||||
comment.lastIndex = string.lastIndex = i;
|
comment.lastIndex = i;
|
||||||
|
|
||||||
if (match = string.exec(input)) {
|
if (!inString && c === '/') {
|
||||||
if (match.index === i) {
|
|
||||||
i += match[0].length;
|
|
||||||
chunk.push(match[0]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inParam && c === '/') {
|
|
||||||
cc = input.charAt(i + 1);
|
cc = input.charAt(i + 1);
|
||||||
if (cc === '/' || cc === '*') {
|
if (cc === '/' || cc === '*') {
|
||||||
if (match = comment.exec(input)) {
|
if (match = comment.exec(input)) {
|
||||||
if (match.index === i) {
|
if (match.index === i) {
|
||||||
i += match[0].length;
|
i += match[0].length - 1;
|
||||||
chunk.push(match[0]);
|
chunk.push(match[0]);
|
||||||
continue;
|
c = input.charAt(i);
|
||||||
|
continue chunker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (c) {
|
if (c === '{' && !inString) { level++;
|
||||||
case '{': if (! inParam) { level ++; chunk.push(c); break; }
|
chunk.push(c);
|
||||||
case '}': if (! inParam) { level --; chunk.push(c); chunks[++j] = chunk = []; break; }
|
} else if (c === '}' && !inString) { level--;
|
||||||
case '(': if (! inParam) { inParam = true; chunk.push(c); break; }
|
chunk.push(c);
|
||||||
case ')': if ( inParam) { inParam = false; chunk.push(c); break; }
|
chunks[++j] = chunk = [];
|
||||||
default: chunk.push(c);
|
} else {
|
||||||
|
if (c === '"' || c === "'" || c === '`') {
|
||||||
|
if (! inString) {
|
||||||
|
inString = c;
|
||||||
|
} else {
|
||||||
|
inString = inString === c ? false : inString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chunk.push(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
if (level !== 0) {
|
if (level > 0) {
|
||||||
error = {
|
// TODO: make invalid instead
|
||||||
index: i - 1,
|
throw makeError({
|
||||||
type: 'Parse',
|
message: 'Missing closing `}`',
|
||||||
message: (level > 0) ? "missing closing `}`" : "missing opening `{`"
|
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.
|
// Start with the primary rule.
|
||||||
// The whole syntax tree is held under a Ruleset node,
|
// The whole syntax tree is held under a Ruleset node,
|
||||||
// with the `root` property set to true, so no `{}` are
|
// 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 = new tree.Ruleset([], $(this.parsers.primary));
|
||||||
root.root = true;
|
root.root = true;
|
||||||
|
|
||||||
// Get an array of Ruleset objects, flattened
|
// Get an array of Ruleset objects, flattened
|
||||||
// and sorted according to specificitySort
|
// and sorted according to specificitySort
|
||||||
root.toList = (function() {
|
root.toList = (function() {
|
||||||
var line, lines, column;
|
|
||||||
return function(env) {
|
return function(env) {
|
||||||
env.error = function(e) {
|
env.error = function(e) {
|
||||||
if (!env.errors) env.errors = new Error('');
|
if (!env.errors) env.errors = new Error('');
|
||||||
@ -251,7 +304,6 @@ carto.Parser = function Parser(env) {
|
|||||||
};
|
};
|
||||||
env.frames = env.frames || [];
|
env.frames = env.frames || [];
|
||||||
|
|
||||||
|
|
||||||
// call populates Invalid-caused errors
|
// call populates Invalid-caused errors
|
||||||
var definitions = this.flatten([], [], env);
|
var definitions = this.flatten([], [], env);
|
||||||
definitions.sort(specificitySort);
|
definitions.sort(specificitySort);
|
||||||
@ -276,9 +328,23 @@ carto.Parser = function Parser(env) {
|
|||||||
return bs[3] - as[3];
|
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;
|
return root;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//
|
||||||
// Here in, the parsing rules/functions
|
// Here in, the parsing rules/functions
|
||||||
//
|
//
|
||||||
// The basic structure of the syntax tree generated is as follows:
|
// 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
|
// 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
|
// 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()`.
|
// first, before parsing, that's when we use `peek()`.
|
||||||
|
//
|
||||||
parsers: {
|
parsers: {
|
||||||
|
//
|
||||||
// The `primary` rule is the *entry* and *exit* point of the parser.
|
// The `primary` rule is the *entry* and *exit* point of the parser.
|
||||||
// The rules here can appear at any level of the parse tree.
|
// 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
|
// Only at one point is the primary rule not called from the
|
||||||
// block rule: at the root level.
|
// block rule: at the root level.
|
||||||
|
//
|
||||||
primary: function() {
|
primary: function() {
|
||||||
var node, root = [];
|
var node, root = [];
|
||||||
|
|
||||||
while ((node = $(this.rule) || $(this.ruleset) ||
|
while ((node = $(this.rule) || $(this.ruleset) ||
|
||||||
$(this.comment)) ||
|
$(this.comment)) ||
|
||||||
$(/^[\s\n]+/) || (node = $(this.invalid))) {
|
$(/^[\s\n]+/) || (node = $(this.invalid))) {
|
||||||
if (node) root.push(node);
|
node && root.push(node);
|
||||||
}
|
}
|
||||||
return root;
|
return root;
|
||||||
},
|
},
|
||||||
@ -318,7 +387,7 @@ carto.Parser = function Parser(env) {
|
|||||||
|
|
||||||
// To fail gracefully, match everything until a semicolon or linebreak.
|
// To fail gracefully, match everything until a semicolon or linebreak.
|
||||||
if (chunk) {
|
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 are tokens which can be found inside an Expression
|
||||||
|
//
|
||||||
entities: {
|
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() {
|
quoted: function() {
|
||||||
if (input.charAt(i) !== '"' && input.charAt(i) !== "'") return;
|
if (input.charAt(i) !== '"' && input.charAt(i) !== "'") return;
|
||||||
var str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/);
|
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
|
// Behind the scenes, this has the same representation, but Carto
|
||||||
// needs to be careful to warn when unsupported operations are used.
|
// needs to be careful to warn when unsupported operations are used.
|
||||||
field: function() {
|
field: function() {
|
||||||
if (! $('[')) return;
|
if (! $('[')) return;
|
||||||
var field_name = $(/(^[^\]]+)/);
|
var field_name = $(/(^[a-zA-Z0-9\-_]+)/);
|
||||||
if (! $(']')) return;
|
if (! $(']')) return;
|
||||||
if (field_name) return new tree.Field(field_name[1]);
|
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
|
// A catch-all word, such as:
|
||||||
// These can start with either a letter or a dash (-),
|
//
|
||||||
// and then contain numbers, underscores, and letters.
|
// hard-light
|
||||||
|
//
|
||||||
|
// These can start with either a letter or a dash (-),
|
||||||
|
// and then contain numbers, underscores, and letters.
|
||||||
keyword: function() {
|
keyword: function() {
|
||||||
var k = $(/^[A-Za-z-]+[A-Za-z-0-9_]*/);
|
var k = $(/^[A-Za-z-]+[A-Za-z-0-9_]*/);
|
||||||
if (k) { return new tree.Keyword(k); }
|
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.
|
// The arguments are parsed with the `entities.arguments` parser.
|
||||||
call: function() {
|
call: function() {
|
||||||
var name, args;
|
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') {
|
if (name === 'url') {
|
||||||
// url() is handled by the url parser instead
|
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
i += name.length;
|
i += name.length + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('('); // Parse the '(' and consume whitespace.
|
args = $(this.entities.arguments);
|
||||||
|
|
||||||
args = $(this.entities['arguments']);
|
|
||||||
|
|
||||||
if (!$(')')) return;
|
if (!$(')')) return;
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
return new tree.Call(name, args, i);
|
return new tree.Call(name, args, i);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Arguments are comma-separated expressions
|
// Arguments are comma-separated expressions
|
||||||
@ -414,9 +494,8 @@ carto.Parser = function Parser(env) {
|
|||||||
},
|
},
|
||||||
literal: function() {
|
literal: function() {
|
||||||
return $(this.entities.dimension) ||
|
return $(this.entities.dimension) ||
|
||||||
$(this.entities.keywordcolor) ||
|
$(this.entities.color) ||
|
||||||
$(this.entities.hexcolor) ||
|
$(this.entities.quoted);
|
||||||
$(this.entities.quoted);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Parse url() tokens
|
// Parse url() tokens
|
||||||
@ -433,9 +512,8 @@ carto.Parser = function Parser(env) {
|
|||||||
if (! $(')')) {
|
if (! $(')')) {
|
||||||
return new tree.Invalid(value, memo, 'Missing closing ) in URL.');
|
return new tree.Invalid(value, memo, 'Missing closing ) in URL.');
|
||||||
} else {
|
} else {
|
||||||
return new tree.URL((typeof value.value !== 'undefined' ||
|
return new tree.URL((value.value || value instanceof tree.Variable) ?
|
||||||
value instanceof tree.Variable) ?
|
value : new tree.Quoted(value), imports.paths);
|
||||||
value : new tree.Quoted(value));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -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;
|
var rgb;
|
||||||
|
|
||||||
if (input.charAt(i) === '#' && (rgb = $(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/))) {
|
if (input.charAt(i) === '#' && (rgb = $(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/))) {
|
||||||
return new tree.Color(rgb[1]);
|
return new tree.Color(rgb[1]);
|
||||||
}
|
} else {
|
||||||
},
|
rgb = chunks[j].match(/^[a-z]+/);
|
||||||
|
if (rgb && rgb[0] in tree.Reference.data.colors) {
|
||||||
keywordcolor: function() {
|
return new tree.Color(tree.Reference.data.colors[$(/^[a-z]+/)]);
|
||||||
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]+/)]);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// A Dimension, that is, a number and a unit. The only
|
// A Dimension, that is, a number and a unit. The only
|
||||||
// unit that has an effect is %
|
// unit that has an effect is %
|
||||||
|
//
|
||||||
|
// 0.5em 95%
|
||||||
dimension: function() {
|
dimension: function() {
|
||||||
var c = input.charCodeAt(i);
|
var c = input.charCodeAt(i);
|
||||||
if ((c > 57 || c < 45) || c === 47) return;
|
if ((c > 57 || c < 45) || c === 47) return;
|
||||||
var value = $(/^(-?\d*\.?\d+(?:[eE][-+]?\d+)?)(\%|\w+)?/);
|
var value = $(/^(-?\d*\.?\d+)(\%|\w+)?/);
|
||||||
if (value) {
|
if (value) {
|
||||||
return new tree.Dimension(value[1], value[2], memo);
|
return new tree.Dimension(value[1], value[2], memo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// The variable part of a variable definition.
|
// The variable part of a variable definition. Used in the `rule` parser
|
||||||
// Used in the `rule` parser. Like @fink:
|
//
|
||||||
|
// @fink:
|
||||||
variable: function() {
|
variable: function() {
|
||||||
var name;
|
var name;
|
||||||
|
|
||||||
@ -490,20 +574,24 @@ carto.Parser = function Parser(env) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//
|
||||||
// Entities are the smallest recognized token,
|
// Entities are the smallest recognized token,
|
||||||
// and can be found inside a rule's value.
|
// and can be found inside a rule's value.
|
||||||
|
//
|
||||||
entity: function() {
|
entity: function() {
|
||||||
return $(this.entities.call) ||
|
return $(this.entities.literal) ||
|
||||||
$(this.entities.literal) ||
|
|
||||||
$(this.entities.field) ||
|
$(this.entities.field) ||
|
||||||
$(this.entities.variable) ||
|
$(this.entities.variable) ||
|
||||||
$(this.entities.url) ||
|
$(this.entities.url) ||
|
||||||
|
$(this.entities.call) ||
|
||||||
$(this.entities.keyword);
|
$(this.entities.keyword);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//
|
||||||
// A Rule terminator. Note that we use `peek()` to check for '}',
|
// 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
|
// because the `block` rule will be expecting it, but we still need to make sure
|
||||||
// it's there, if ';' was ommitted.
|
// it's there, if ';' was ommitted.
|
||||||
|
//
|
||||||
end: function() {
|
end: function() {
|
||||||
return $(';') || peek('}');
|
return $(';') || peek('}');
|
||||||
},
|
},
|
||||||
@ -524,17 +612,15 @@ carto.Parser = function Parser(env) {
|
|||||||
|
|
||||||
// Selectors are made out of one or more Elements, see above.
|
// Selectors are made out of one or more Elements, see above.
|
||||||
selector: function() {
|
selector: function() {
|
||||||
var a, attachment,
|
var a, attachment;
|
||||||
e, elements = [],
|
var e, elements = [];
|
||||||
f, filters = new tree.Filterset(),
|
var f, filters = new tree.Filterset();
|
||||||
z, zooms = [],
|
var z, zoom = tree.Zoom.all;
|
||||||
frame_offset = tree.FrameOffset.none;
|
var segments = 0, conditions = 0;
|
||||||
segments = 0, conditions = 0;
|
|
||||||
|
|
||||||
while (
|
while (
|
||||||
(e = $(this.element)) ||
|
(e = $(this.element)) ||
|
||||||
(z = $(this.zoom)) ||
|
(z = $(this.zoom)) ||
|
||||||
(fo = $(this.frame_offset)) ||
|
|
||||||
(f = $(this.filter)) ||
|
(f = $(this.filter)) ||
|
||||||
(a = $(this.attachment))
|
(a = $(this.attachment))
|
||||||
) {
|
) {
|
||||||
@ -542,24 +628,15 @@ carto.Parser = function Parser(env) {
|
|||||||
if (e) {
|
if (e) {
|
||||||
elements.push(e);
|
elements.push(e);
|
||||||
} else if (z) {
|
} else if (z) {
|
||||||
zooms.push(z);
|
zoom &= z;
|
||||||
conditions++;
|
|
||||||
} else if (fo) {
|
|
||||||
frame_offset = fo;
|
|
||||||
conditions++;
|
conditions++;
|
||||||
} else if (f) {
|
} else if (f) {
|
||||||
var err = filters.add(f);
|
filters.add(f);
|
||||||
if (err) {
|
|
||||||
throw makeError({
|
|
||||||
message: err,
|
|
||||||
index: i - 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
conditions++;
|
conditions++;
|
||||||
} else if (attachment) {
|
} else if (attachment) {
|
||||||
throw makeError({
|
throw makeError({
|
||||||
message: 'Encountered second attachment name.',
|
message:'Encountered second attachment name.',
|
||||||
index: i - 1
|
index:i - 1
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
attachment = a;
|
attachment = a;
|
||||||
@ -570,7 +647,7 @@ carto.Parser = function Parser(env) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (segments) {
|
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();
|
save();
|
||||||
var key, op, val;
|
var key, op, val;
|
||||||
if (! $('[')) return;
|
if (! $('[')) return;
|
||||||
if (key = $(/^[a-zA-Z0-9\-_]+/) ||
|
if (key = $(/^[a-zA-Z0-9\-_]+/) || $(this.entities.quoted) || $(this.entities.variable)) {
|
||||||
$(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 ((op = $(this.entities.comparison)) &&
|
if ((op = $(this.entities.comparison)) &&
|
||||||
(val = $(this.entities.quoted) ||
|
(val = $(this.entities.quoted) || $(this.entities.variable) || $(/^[\w\-\.]+/))) {
|
||||||
$(this.entities.variable) ||
|
if (! $(']')) return;
|
||||||
$(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);
|
|
||||||
return new tree.Filter(key, op, val, memo, env.filename);
|
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() {
|
zoom: function() {
|
||||||
save();
|
save();
|
||||||
var op, val;
|
var op, val;
|
||||||
if ($(/^\[\s*zoom/g) &&
|
if ($(/^\[zoom/g) &&
|
||||||
(op = $(this.entities.comparison)) &&
|
(op = $(this.entities.comparison)) &&
|
||||||
(val = $(this.entities.variable) || $(this.entities.dimension)) && $(']')) {
|
(val = $(/^\d+/)) &&
|
||||||
return new tree.Zoom(op, val, memo);
|
$(']')) {
|
||||||
} else {
|
return tree.Zoom(op, val, memo);
|
||||||
// backtrack
|
|
||||||
restore();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//
|
||||||
// The `block` rule is used by `ruleset`
|
// The `block` rule is used by `ruleset`
|
||||||
// It's a wrapper around the `primary` rule, with added `{}`.
|
// It's a wrapper around the `primary` rule, with added `{}`.
|
||||||
|
//
|
||||||
block: function() {
|
block: function() {
|
||||||
var content;
|
var content;
|
||||||
|
|
||||||
@ -639,20 +687,18 @@ carto.Parser = function Parser(env) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//
|
||||||
// div, .class, body > p {...}
|
// div, .class, body > p {...}
|
||||||
|
//
|
||||||
ruleset: function() {
|
ruleset: function() {
|
||||||
var selectors = [], s, f, l, rules, filters = [];
|
var selectors = [], s, f, l, rules, filters = [];
|
||||||
save();
|
save();
|
||||||
|
|
||||||
while (s = $(this.selector)) {
|
while (s = $(this.selector)) {
|
||||||
selectors.push(s);
|
selectors.push(s);
|
||||||
while ($(this.comment)) {}
|
if (! $(',')) { break }
|
||||||
if (! $(',')) { break; }
|
|
||||||
while ($(this.comment)) {}
|
|
||||||
}
|
|
||||||
if (s) {
|
|
||||||
while ($(this.comment)) {}
|
|
||||||
}
|
}
|
||||||
|
if (s) $(this.comment);
|
||||||
|
|
||||||
if (selectors.length > 0 && (rules = $(this.block))) {
|
if (selectors.length > 0 && (rules = $(this.block))) {
|
||||||
if (selectors.length === 1 &&
|
if (selectors.length === 1 &&
|
||||||
@ -668,12 +714,11 @@ carto.Parser = function Parser(env) {
|
|||||||
restore();
|
restore();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
rule: function() {
|
rule: function() {
|
||||||
var name, value, c = input.charAt(i);
|
var name, value, c = input.charAt(i);
|
||||||
save();
|
save();
|
||||||
|
|
||||||
if (c === '.' || c === '#') { return; }
|
if (c === '.' || c === '#' || c === '&') { return }
|
||||||
|
|
||||||
if (name = $(this.variable) || $(this.property)) {
|
if (name = $(this.variable) || $(this.property)) {
|
||||||
value = $(this.value);
|
value = $(this.value);
|
||||||
@ -716,32 +761,16 @@ carto.Parser = function Parser(env) {
|
|||||||
if (! $(',')) { break; }
|
if (! $(',')) { break; }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expressions.length > 1) {
|
if (expressions.length > 0) {
|
||||||
return new tree.Value(expressions.map(function(e) {
|
|
||||||
return e.value[0];
|
|
||||||
}));
|
|
||||||
} else if (expressions.length === 1) {
|
|
||||||
return new tree.Value(expressions);
|
return new tree.Value(expressions);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// A sub-expression, contained by parenthensis
|
// A sub-expression, contained by parenthensis
|
||||||
sub: function() {
|
sub: function() {
|
||||||
var e, expressions = [];
|
var e;
|
||||||
|
|
||||||
if ($('(')) {
|
if ($('(') && (e = $(this.expression)) && $(')')) {
|
||||||
while (e = $(this.expression)) {
|
return e;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// This is a misnomer because it actually handles multiplication
|
// This is a misnomer because it actually handles multiplication
|
||||||
@ -773,14 +802,16 @@ carto.Parser = function Parser(env) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Expressions either represent mathematical operations,
|
// Expressions either represent mathematical operations,
|
||||||
// or white-space delimited Entities. @var * 2
|
// or white-space delimited Entities.
|
||||||
|
//
|
||||||
|
// 1px solid black
|
||||||
|
// @var * 2
|
||||||
expression: function() {
|
expression: function() {
|
||||||
var e, delim, entities = [], d;
|
var e, delim, entities = [], d;
|
||||||
|
|
||||||
while (e = $(this.addition) || $(this.entity)) {
|
while (e = $(this.addition) || $(this.entity)) {
|
||||||
entities.push(e);
|
entities.push(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entities.length > 0) {
|
if (entities.length > 0) {
|
||||||
return new tree.Expression(entities);
|
return new tree.Expression(entities);
|
||||||
}
|
}
|
||||||
@ -791,5 +822,5 @@ carto.Parser = function Parser(env) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return parser;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,98 +1,39 @@
|
|||||||
var _ = global._ || require('underscore');
|
var _ = require('underscore');
|
||||||
var carto = require('./index');
|
var carto = require('./index');
|
||||||
|
var tree = require('./tree');
|
||||||
|
|
||||||
carto.Renderer = function Renderer(env, options) {
|
carto.Renderer = function Renderer(env, options) {
|
||||||
this.env = env || {};
|
this.env = env || {};
|
||||||
this.options = options || {};
|
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 MML document (given as an object) into a
|
||||||
* Prepare a MSS document (given as an string) into a
|
// fully-localized XML file ready for Mapnik2 consumption
|
||||||
* XML Style fragment (mostly useful for debugging)
|
//
|
||||||
*
|
// - @param {String} str the JSON file as a string.
|
||||||
* @param {String} data the mss contents as a string.
|
// - @param {Object} env renderer environment options.
|
||||||
*/
|
carto.Renderer.prototype.render = function render(m, callback) {
|
||||||
carto.Renderer.prototype.renderMSS = function render(data) {
|
|
||||||
// effects is a container for side-effects, which currently
|
// effects is a container for side-effects, which currently
|
||||||
// are limited to FontSets.
|
// are limited to FontSets.
|
||||||
var env = _.defaults(this.env, {
|
var env = _(this.env).defaults({
|
||||||
benchmark: false,
|
benchmark: false,
|
||||||
validation_data: false,
|
validation_data: false,
|
||||||
effects: []
|
effects: []
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!carto.tree.Reference.setVersion(this.options.mapnik_version)) {
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
var output = [];
|
var output = [];
|
||||||
|
|
||||||
// Transform stylesheets into definitions.
|
// Transform stylesheets into rulesets.
|
||||||
var definitions = _.chain(m.Stylesheet)
|
var rulesets = _(m.Stylesheet).chain()
|
||||||
.map(function(s) {
|
.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,
|
// Passing the environment from stylesheet to stylesheet,
|
||||||
// allows frames and effects to be maintained.
|
// 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(),
|
var time = +new Date(),
|
||||||
root = (carto.Parser(env)).parse(s.data);
|
root = (carto.Parser(env)).parse(s.data);
|
||||||
if (env.benchmark)
|
if (env.benchmark)
|
||||||
@ -102,65 +43,51 @@ carto.Renderer.prototype.render = function render(m) {
|
|||||||
.flatten()
|
.flatten()
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
function appliesTo(name, classIndex) {
|
|
||||||
return function(definition) {
|
|
||||||
return definition.appliesTo(l.name, classIndex);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate through layers and create styles custom-built
|
// Iterate through layers and create styles custom-built
|
||||||
// for each of them, and apply those styles to the layers.
|
// for each of them, and apply those styles to the layers.
|
||||||
var styles, l, classIndex, rules, sorted, matching;
|
m.Layer.forEach(function(l) {
|
||||||
for (var i = 0; i < m.Layer.length; i++) {
|
l.styles = [];
|
||||||
l = m.Layer[i];
|
|
||||||
styles = [];
|
|
||||||
classIndex = {};
|
|
||||||
|
|
||||||
if (env.benchmark) console.warn('processing layer: ' + l.id);
|
|
||||||
// Classes are given as space-separated alphanumeric strings.
|
// Classes are given as space-separated alphanumeric strings.
|
||||||
var classes = (l['class'] || '').split(/\s+/g);
|
var classes = (l['class'] || '').split(/\s+/g);
|
||||||
for (var j = 0; j < classes.length; j++) {
|
var matching = rulesets.filter(function(definition) {
|
||||||
classIndex[classes[j]] = true;
|
return definition.appliesTo(l.name, classes);
|
||||||
}
|
});
|
||||||
matching = definitions.filter(appliesTo(l.name, classIndex));
|
var rules = inheritRules(matching, env);
|
||||||
rules = inheritDefinitions(matching, env);
|
var sorted = sortStyles(rules, env);
|
||||||
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++) {
|
// env.effects can be modified by this call
|
||||||
rule = sorted[k];
|
output.push(style.toXML(env));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
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) {
|
output.unshift(env.effects.map(function(e) {
|
||||||
return e.toXML(env);
|
return e.toXML(env);
|
||||||
}).join('\n'));
|
}).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.
|
// 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.
|
// 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;
|
if (!v && v !== 0) return memo;
|
||||||
|
|
||||||
switch (k) {
|
switch (k) {
|
||||||
// Known skippable properties.
|
|
||||||
case 'srs':
|
|
||||||
case 'Layer':
|
|
||||||
case 'Stylesheet':
|
|
||||||
break;
|
|
||||||
// Non URL-bound TileJSON properties.
|
// Non URL-bound TileJSON properties.
|
||||||
case 'bounds':
|
case 'bounds':
|
||||||
case 'center':
|
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_layer">' + v.layer + '</Parameter>');
|
||||||
memo.push(' <Parameter name="interactivity_fields">' + v.fields + '</Parameter>');
|
memo.push(' <Parameter name="interactivity_fields">' + v.fields + '</Parameter>');
|
||||||
break;
|
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;
|
return memo;
|
||||||
}, []);
|
}, []);
|
||||||
@ -205,36 +122,28 @@ carto.Renderer.prototype.render = function render(m) {
|
|||||||
'\n</Parameters>\n'
|
'\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(
|
output.unshift(
|
||||||
'<?xml version="1.0" ' +
|
'<?xml version="1.0" ' +
|
||||||
'encoding="utf-8"?>\n' +
|
'encoding="utf-8"?>\n' +
|
||||||
'<!DOCTYPE Map[]>\n' +
|
'<!DOCTYPE Map[]>\n' +
|
||||||
'<Map' + properties +'>\n');
|
'<Map' + properties + ' maximum-extent="-20037508.34,-20037508.34,20037508.34,20037508.34">\n');
|
||||||
output.push('</Map>');
|
output.push('</Map>');
|
||||||
return output.join('\n');
|
return callback(null, output.join('\n'));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// This function currently modifies 'current'
|
||||||
* This function currently modifies 'current'
|
function addRules(current, definition, existing) {
|
||||||
* @param {Array} current current list of rules
|
var newFilters = definition.filters;
|
||||||
* @param {Object} definition a Definition object to add to the rules
|
var newRules = definition.rules;
|
||||||
* @param {Object} byFilter an object/dictionary of existing filters. This is
|
var updatedFilters, clone, previous;
|
||||||
* 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;
|
|
||||||
|
|
||||||
// The current definition might have been split up into
|
// The current definition might have been split up into
|
||||||
// multiple definitions already.
|
// multiple definitions already.
|
||||||
for (var k = 0; k < current.length; k++) {
|
for (var k = 0; k < current.length; k++) {
|
||||||
updatedFilters = current[k].filters.cloneWith(newFilters);
|
updatedFilters = current[k].filters.cloneWith(newFilters);
|
||||||
if (updatedFilters) {
|
if (updatedFilters) {
|
||||||
previous = byFilter[updatedFilters];
|
previous = existing[updatedFilters];
|
||||||
if (previous) {
|
if (previous) {
|
||||||
// There's already a definition with those exact
|
// There's already a definition with those exact
|
||||||
// filters. Add the current definitions' rules
|
// 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
|
// to make sure that in the next loop iteration, we're
|
||||||
// not performing the same task for this element again,
|
// not performing the same task for this element again,
|
||||||
// hence the k++.
|
// hence the k++.
|
||||||
byFilter[updatedFilters] = clone;
|
|
||||||
current.splice(k, 0, clone);
|
current.splice(k, 0, clone);
|
||||||
k++;
|
k++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (updatedFilters === null) {
|
} 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 can be added, but they don't change the
|
||||||
// filters. This means we don't have to split the
|
// filters. This means we don't have to split the
|
||||||
// definition.
|
// definition.
|
||||||
//
|
|
||||||
// this is cloned here because of shared classes, see
|
|
||||||
// sharedclass.mss
|
|
||||||
current[k] = current[k].clone();
|
current[k] = current[k].clone();
|
||||||
current[k].addRules(newRules);
|
current[k].addRules(newRules);
|
||||||
}
|
}
|
||||||
// if updatedFeatures is false, then the filters split the rule,
|
|
||||||
// so they aren't the same inheritance chain
|
|
||||||
}
|
}
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Apply inherited styles from their ancestors to them.
|
||||||
* Apply inherited styles from their ancestors to them.
|
function inheritRules(definitions, env) {
|
||||||
*
|
|
||||||
* 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) {
|
|
||||||
var inheritTime = +new Date();
|
var inheritTime = +new Date();
|
||||||
// definitions are ordered by specificity,
|
// definitions are ordered by specificity,
|
||||||
// high (index 0) to low
|
// high (index 0) to low
|
||||||
var byAttachment = {},
|
var byAttachment = {}, byFilter = {};
|
||||||
byFilter = {};
|
|
||||||
var result = [];
|
var result = [];
|
||||||
var current, previous, attachment;
|
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++) {
|
for (var i = 0; i < definitions.length; i++) {
|
||||||
|
|
||||||
attachment = definitions[i].attachment;
|
attachment = definitions[i].attachment;
|
||||||
current = [definitions[i]];
|
|
||||||
|
|
||||||
if (!byAttachment[attachment]) {
|
if (!byAttachment[attachment]) {
|
||||||
byAttachment[attachment] = [];
|
byAttachment[attachment] = [];
|
||||||
byAttachment[attachment].attachment = attachment;
|
byAttachment[attachment].attachment = attachment;
|
||||||
@ -315,11 +193,12 @@ function inheritDefinitions(definitions, env) {
|
|||||||
result.push(byAttachment[attachment]);
|
result.push(byAttachment[attachment]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
current = [definitions[i]];
|
||||||
// Iterate over all subsequent rules.
|
// Iterate over all subsequent rules.
|
||||||
for (var j = i + 1; j < definitions.length; j++) {
|
for (var j = i + 1; j < definitions.length; j++) {
|
||||||
if (definitions[j].attachment === attachment) {
|
if (definitions[j].attachment === attachment) {
|
||||||
// Only inherit rules from the same 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');
|
if (env.benchmark) console.warn('Inheritance time: ' + ((new Date() - inheritTime)) + 'ms');
|
||||||
|
|
||||||
return result;
|
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) {
|
function sortStyles(styles, env) {
|
||||||
for (var i = 0; i < styles.length; i++) {
|
styles.forEach(function(style) {
|
||||||
var style = styles[i];
|
|
||||||
style.index = Infinity;
|
style.index = Infinity;
|
||||||
for (var b = 0; b < style.length; b++) {
|
style.forEach(function(block) {
|
||||||
var rules = style[b].rules;
|
block.rules.forEach(function(rule) {
|
||||||
for (var r = 0; r < rules.length; r++) {
|
|
||||||
var rule = rules[r];
|
|
||||||
if (rule.index < style.index) {
|
if (rule.index < style.index) {
|
||||||
style.index = rule.index;
|
style.index = rule.index;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
var result = styles.slice();
|
var result = styles.slice();
|
||||||
result.sort(sortStylesIndex);
|
result.sort(function(a, b) {
|
||||||
|
return b.index - a.index;
|
||||||
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Find a rule like Map { background-color: #fff; },
|
||||||
* Find a rule like Map { background-color: #fff; },
|
// if any, and return a list of properties to be inserted
|
||||||
* if any, and return a list of properties to be inserted
|
// into the <Map element of the resulting XML. Translates
|
||||||
* into the <Map element of the resulting XML. Translates
|
// properties of the mml object at `m` directly into XML
|
||||||
* properties of the mml object at `m` directly into XML
|
// properties.
|
||||||
* properties.
|
//
|
||||||
*
|
// - @param {Object} m the mml object.
|
||||||
* @param {Object} m the mml object.
|
// - @param {Array} rulesets the output of toList.
|
||||||
* @param {Array} definitions the output of toList.
|
// - @param {Object} env.
|
||||||
* @param {Object} env
|
// - @return {String} rendered properties.
|
||||||
* @return {String} rendered properties.
|
function getMapProperties(m, rulesets, env) {
|
||||||
*/
|
|
||||||
function getMapProperties(m, definitions, env) {
|
|
||||||
var rules = {};
|
var rules = {};
|
||||||
var symbolizers = carto.tree.Reference.data.symbolizers.map;
|
var symbolizers = tree.Reference.data.symbolizers.map;
|
||||||
|
|
||||||
_(m).each(function(value, key) {
|
_(m).each(function(value, key) {
|
||||||
if (key in symbolizers) rules[key] = key + '="' + value + '"';
|
if (key in symbolizers) rules[key] = key + '="' + value + '"';
|
||||||
});
|
});
|
||||||
|
|
||||||
definitions.filter(function(r) {
|
rulesets.filter(function(r) {
|
||||||
return r.elements.join('') === 'Map';
|
return r.elements.join('') === 'Map';
|
||||||
}).forEach(function(r) {
|
}).forEach(function(r) {
|
||||||
for (var i = 0; i < r.rules.length; i++) {
|
for (var i = 0; i < r.rules.length; i++) {
|
||||||
@ -390,13 +261,10 @@ function getMapProperties(m, definitions, env) {
|
|||||||
index: r.rules[i].index
|
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;
|
return rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = carto;
|
module.exports = carto;
|
||||||
module.exports.addRules = addRules;
|
|
||||||
module.exports.inheritDefinitions = inheritDefinitions;
|
|
||||||
module.exports.sortStyles = sortStyles;
|
|
||||||
|
@ -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
@ -1,11 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* TODO: document this. What does this do?
|
* TODO: document this. What does this do?
|
||||||
*/
|
*/
|
||||||
if(typeof(module) !== "undefined") {
|
module.exports.find = function (obj, fun) {
|
||||||
module.exports.find = function (obj, fun) {
|
for (var i = 0, r; i < obj.length; i++) {
|
||||||
for (var i = 0, r; i < obj.length; i++) {
|
if (r = fun.call(obj, obj[i])) { return r; }
|
||||||
if (r = fun.call(obj, obj[i])) { return r; }
|
}
|
||||||
}
|
return null;
|
||||||
return null;
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -1,23 +1,28 @@
|
|||||||
(function(tree) {
|
(function(tree) {
|
||||||
var _ = global._ || require('underscore');
|
|
||||||
tree.Call = function Call(name, args, index) {
|
tree.Call = function Call(name, args, index) {
|
||||||
|
this.is = 'call';
|
||||||
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.Call.prototype = {
|
tree.Call.prototype = {
|
||||||
is: 'call',
|
//
|
||||||
// When evuating a function call,
|
// When evaluating a function call,
|
||||||
// we either find the function in `tree.functions` [1],
|
// we either find the function in `tree.functions` [1],
|
||||||
// in which case we call it, passing the evaluated arguments,
|
// in which case we call it, passing the evaluated arguments,
|
||||||
// or we simply print it out as it appeared originally [2].
|
// or we simply print it out as it appeared originally [2].
|
||||||
|
//
|
||||||
// The *functions.js* file contains the built-in functions.
|
// The *functions.js* file contains the built-in functions.
|
||||||
|
//
|
||||||
// The reason why we evaluate the arguments, is in the case where
|
// The reason why we evaluate the arguments, is in the case where
|
||||||
// we try to pass a variable to a function, like: `saturate(@color)`.
|
// we try to pass a variable to a function, like: `saturate(@color)`.
|
||||||
// The function should receive the value, not the variable.
|
// 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++) {
|
for (var i = 0; i < args.length; i++) {
|
||||||
if (args[i].is === 'undefined') {
|
if (args[i].is === 'undefined') {
|
||||||
@ -29,18 +34,8 @@ tree.Call.prototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.name in tree.functions) {
|
if (this.name in tree.functions) {
|
||||||
if (tree.functions[this.name].length <= args.length) {
|
if (tree.functions[this.name].length === args.length) {
|
||||||
var val = tree.functions[this.name].apply(tree.functions, args);
|
return 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;
|
|
||||||
} else {
|
} else {
|
||||||
env.error({
|
env.error({
|
||||||
message: 'incorrect number of arguments for ' + this.name +
|
message: 'incorrect number of arguments for ' + this.name +
|
||||||
@ -55,19 +50,10 @@ tree.Call.prototype = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var fn = tree.Reference.mapnikFunctions[this.name];
|
var fn = tree.Reference.mapnikFunction(this.name);
|
||||||
if (fn === undefined) {
|
if (!fn) {
|
||||||
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];
|
|
||||||
});
|
|
||||||
env.error({
|
env.error({
|
||||||
message: 'unknown function ' + this.name + '(), did you mean ' +
|
message: 'unknown function ' + this.name,
|
||||||
mean[0][0] + '(' + mean[0][2] + ')',
|
|
||||||
index: this.index,
|
index: this.index,
|
||||||
type: 'runtime',
|
type: 'runtime',
|
||||||
filename: this.filename
|
filename: this.filename
|
||||||
@ -77,13 +63,10 @@ tree.Call.prototype = {
|
|||||||
value: 'undefined'
|
value: 'undefined'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (fn !== args.length &&
|
if (fn[1] !== args.length) {
|
||||||
!(Array.isArray(fn) && _.include(fn, args.length)) &&
|
|
||||||
// support variable-arg functions like `colorize-alpha`
|
|
||||||
fn !== -1) {
|
|
||||||
env.error({
|
env.error({
|
||||||
message: 'function ' + this.name + '() takes ' +
|
message: 'function ' + this.name + ' takes ' +
|
||||||
fn + ' arguments and was given ' + args.length,
|
fn[1] + ' arguments and was given ' + args.length,
|
||||||
index: this.index,
|
index: this.index,
|
||||||
type: 'runtime',
|
type: 'runtime',
|
||||||
filename: this.filename
|
filename: this.filename
|
||||||
@ -101,10 +84,18 @@ tree.Call.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
toString: function(env, format) {
|
toString: function(env, format) {
|
||||||
if (this.args.length) {
|
if (format === 'image-filter') {
|
||||||
return this.name + '(' + this.args.join(',') + ')';
|
if (this.args.length) {
|
||||||
|
return this.name + ':' + this.args.join(',');
|
||||||
|
} else {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return this.name;
|
if (this.args.length) {
|
||||||
|
return this.name + '(' + this.args.join(',') + ')';
|
||||||
|
} else {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
(function(tree) {
|
(function(tree) {
|
||||||
|
//
|
||||||
// RGB Colors - #ff0014, #eee
|
// 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) {
|
tree.Color = function Color(rgb, a) {
|
||||||
|
//
|
||||||
// The end goal here, is to parse the arguments
|
// The end goal here, is to parse the arguments
|
||||||
// into an integer triplet, such as `128, 255, 0`
|
// into an integer triplet, such as `128, 255, 0`
|
||||||
//
|
//
|
||||||
// This facilitates operations and conversions.
|
// This facilitates operations and conversions.
|
||||||
|
//
|
||||||
if (Array.isArray(rgb)) {
|
if (Array.isArray(rgb)) {
|
||||||
this.rgb = rgb.slice(0, 3);
|
this.rgb = rgb;
|
||||||
} else if (rgb.length == 6) {
|
} else if (rgb.length == 6) {
|
||||||
this.rgb = rgb.match(/.{2}/g).map(function(c) {
|
this.rgb = rgb.match(/.{2}/g).map(function(c) {
|
||||||
return parseInt(c, 16);
|
return parseInt(c, 16);
|
||||||
@ -18,19 +20,12 @@ tree.Color = function Color(rgb, a) {
|
|||||||
return parseInt(c + c, 16);
|
return parseInt(c + c, 16);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.is = 'color';
|
||||||
if (typeof(a) === 'number') {
|
this.alpha = typeof(a) === 'number' ? a : 1;
|
||||||
this.alpha = a;
|
|
||||||
} else if (rgb.length === 4) {
|
|
||||||
this.alpha = rgb[3];
|
|
||||||
} else {
|
|
||||||
this.alpha = 1;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.Color.prototype = {
|
tree.Color.prototype = {
|
||||||
is: 'color',
|
eval: function() { return this; },
|
||||||
'ev': function() { return this; },
|
|
||||||
|
|
||||||
// If we have some transparency, the only way to represent it
|
// If we have some transparency, the only way to represent it
|
||||||
// is via `rgba`. Otherwise, we use the hex representation,
|
// 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
|
// channels will spill onto each other. Once we have
|
||||||
// our result, in the form of an integer triplet,
|
// our result, in the form of an integer triplet,
|
||||||
// we create a new Color node to hold the result.
|
// we create a new Color node to hold the result.
|
||||||
operate: function(env, op, other) {
|
operate: function(op, other) {
|
||||||
var result = [];
|
var result = [];
|
||||||
|
|
||||||
if (! (other instanceof tree.Color)) {
|
if (! (other instanceof tree.Color)) {
|
||||||
@ -92,4 +87,5 @@ tree.Color.prototype = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
})(require('../tree'));
|
})(require('../tree'));
|
||||||
|
@ -9,7 +9,7 @@ tree.Comment.prototype = {
|
|||||||
toString: function(env) {
|
toString: function(env) {
|
||||||
return '<!--' + this.value + '-->';
|
return '<!--' + this.value + '-->';
|
||||||
},
|
},
|
||||||
'ev': function() { return this; }
|
eval: function() { return this; }
|
||||||
};
|
};
|
||||||
|
|
||||||
})(require('../tree'));
|
})(require('../tree'));
|
||||||
|
@ -1,26 +1,18 @@
|
|||||||
(function(tree) {
|
(function(tree) {
|
||||||
var assert = require('assert'),
|
var assert = require('assert');
|
||||||
_ = global._ || require('underscore');
|
|
||||||
|
|
||||||
// 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) {
|
tree.Definition = function Definition(selector, rules) {
|
||||||
this.elements = selector.elements;
|
this.elements = selector.elements;
|
||||||
assert.ok(selector.filters instanceof tree.Filterset);
|
assert.ok(selector.filters instanceof tree.Filterset);
|
||||||
this.rules = rules;
|
this.rules = rules;
|
||||||
this.ruleIndex = {};
|
this.ruleIndex = [];
|
||||||
for (var i = 0; i < this.rules.length; i++) {
|
for (var i = 0; i < this.rules.length; i++) {
|
||||||
if ('zoom' in this.rules[i]) this.rules[i] = this.rules[i].clone();
|
if ('zoom' in this.rules[i]) this.rules[i] = this.rules[i].clone();
|
||||||
this.rules[i].zoom = selector.zoom;
|
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.filters = selector.filters;
|
||||||
this.zoom = selector.zoom;
|
this.zoom = selector.zoom;
|
||||||
this.frame_offset = selector.frame_offset;
|
|
||||||
this.attachment = selector.attachment || '__default__';
|
this.attachment = selector.attachment || '__default__';
|
||||||
this.specificity = selector.specificity();
|
this.specificity = selector.specificity();
|
||||||
};
|
};
|
||||||
@ -37,7 +29,7 @@ tree.Definition.prototype.clone = function(filters) {
|
|||||||
if (filters) assert.ok(filters instanceof tree.Filterset);
|
if (filters) assert.ok(filters instanceof tree.Filterset);
|
||||||
var clone = Object.create(tree.Definition.prototype);
|
var clone = Object.create(tree.Definition.prototype);
|
||||||
clone.rules = this.rules.slice();
|
clone.rules = this.rules.slice();
|
||||||
clone.ruleIndex = _.clone(this.ruleIndex);
|
clone.ruleIndex = this.ruleIndex.slice();
|
||||||
clone.filters = filters ? filters : this.filters.clone();
|
clone.filters = filters ? filters : this.filters.clone();
|
||||||
clone.attachment = this.attachment;
|
clone.attachment = this.attachment;
|
||||||
return clone;
|
return clone;
|
||||||
@ -48,9 +40,9 @@ tree.Definition.prototype.addRules = function(rules) {
|
|||||||
|
|
||||||
// Add only unique rules.
|
// Add only unique rules.
|
||||||
for (var i = 0; i < rules.length; i++) {
|
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.rules.push(rules[i]);
|
||||||
this.ruleIndex[rules[i].id] = true;
|
this.ruleIndex.push(rules[i].id);
|
||||||
added++;
|
added++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,33 +50,24 @@ tree.Definition.prototype.addRules = function(rules) {
|
|||||||
return added;
|
return added;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determine whether this selector matches a given id
|
/**
|
||||||
// and array of classes, by determining whether
|
* Determine whether this selector matches a given id
|
||||||
// all elements it contains match.
|
* and array of classes, by determining whether
|
||||||
|
* all elements it contains match.
|
||||||
|
*/
|
||||||
tree.Definition.prototype.appliesTo = function(id, classes) {
|
tree.Definition.prototype.appliesTo = function(id, classes) {
|
||||||
for (var i = 0, l = this.elements.length; i < l; i++) {
|
for (var i = 0; i < this.elements.length; i++) {
|
||||||
var elem = this.elements[i];
|
if (!this.elements[i].matches(id, classes)) {
|
||||||
if (!(elem.wildcard ||
|
return false;
|
||||||
(elem.type === 'class' && classes[elem.clean]) ||
|
}
|
||||||
(elem.type === 'id' && id === elem.clean))) return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
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) {
|
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
|
// Sort symbolizers by the index of their first property definition
|
||||||
var sym_order = [], indexes = [];
|
var sym_order = [], indexes = [];
|
||||||
@ -97,17 +80,20 @@ tree.Definition.prototype.symbolizersToXML = function(env, symbolizers, zoom) {
|
|||||||
sym_order.push([key, min_idx]);
|
sym_order.push([key, min_idx]);
|
||||||
}
|
}
|
||||||
|
|
||||||
sym_order = symbolizerList(sym_order);
|
// Get a simple list of the symbolizers, in order
|
||||||
var sym_count = 0;
|
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++) {
|
for (var i = 0; i < sym_order.length; i++) {
|
||||||
var attributes = symbolizers[sym_order[i]];
|
var attributes = symbolizers[sym_order[i]];
|
||||||
var symbolizer = sym_order[i].split('/').pop();
|
var symbolizer = sym_order[i].split('/').pop();
|
||||||
|
|
||||||
// Skip the magical * symbolizer which is used for universal properties
|
// Skip the magical * symbolizer which is used for universal properties
|
||||||
// which are bubbled up to Style elements intead of Symbolizer elements.
|
// which are bubbled up to Style elements intead of Symbolizer elements.
|
||||||
if (symbolizer === '*') continue;
|
if (symbolizer === '*') continue;
|
||||||
sym_count++;
|
|
||||||
|
|
||||||
var fail = tree.Reference.requiredProperties(symbolizer, attributes);
|
var fail = tree.Reference.requiredProperties(symbolizer, attributes);
|
||||||
if (fail) {
|
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;
|
var selfclosing = true, tagcontent;
|
||||||
xml += ' <' + name + ' ';
|
xml += ' <' + name + ' ';
|
||||||
for (var j in attributes) {
|
for (var key in attributes) {
|
||||||
if (symbolizer === 'map') env.error({
|
if (symbolizer === 'map') env.error({
|
||||||
message: 'Map properties are not permitted in other rules',
|
message: 'Map properties are not permitted in other rules',
|
||||||
index: attributes[j].index,
|
index: attributes[key].index,
|
||||||
filename: attributes[j].filename
|
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') {
|
if (x && x.serialization && x.serialization === 'content') {
|
||||||
selfclosing = false;
|
selfclosing = false;
|
||||||
tagcontent = attributes[j].ev(env).toXML(env, true);
|
tagcontent = attributes[key].eval(env).toXML(env, true);
|
||||||
} else if (x && x.serialization && x.serialization === 'tag') {
|
|
||||||
selfclosing = false;
|
|
||||||
tagcontent = attributes[j].ev(env).toXML(env, true);
|
|
||||||
} else {
|
} else {
|
||||||
xml += attributes[j].ev(env).toXML(env) + ' ';
|
xml += attributes[key].eval(env).toXML(env) + ' ';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (selfclosing) {
|
if (selfclosing) {
|
||||||
xml += '/>\n';
|
xml += '/>\n';
|
||||||
} else if (typeof tagcontent !== "undefined") {
|
} else {
|
||||||
if (tagcontent.indexOf('<') != -1) {
|
xml += '><![CDATA[' + tagcontent + ']]></' + name + '>\n';
|
||||||
xml += '>' + tagcontent + '</' + name + '>\n';
|
|
||||||
} else {
|
|
||||||
xml += '><![CDATA[' + tagcontent + ']]></' + name + '>\n';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sym_count || !xml) return '';
|
xml += ' </Rule>\n';
|
||||||
return ' <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) {
|
tree.Definition.prototype.collectSymbolizers = function(zooms, i) {
|
||||||
var symbolizers = {}, child;
|
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) {
|
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();
|
var filter = this.filters.toString();
|
||||||
if (!(filter in existing)) existing[filter] = tree.Zoom.all;
|
if (!(filter in existing)) existing[filter] = tree.Zoom.all;
|
||||||
|
|
||||||
var available = tree.Zoom.all, xml = '', zoom, symbolizers,
|
var available = tree.Zoom.all, xml = '', zoom, symbolizers;
|
||||||
zooms = { available: tree.Zoom.all };
|
var zooms = { available: tree.Zoom.all };
|
||||||
for (var i = 0; i < this.rules.length && available; i++) {
|
for (var i = 0; i < this.rules.length && available; i++) {
|
||||||
zooms.rule = this.rules[i].zoom;
|
zooms.rule = this.rules[i].zoom;
|
||||||
if (!(existing[filter] & zooms.rule)) continue;
|
if (!(existing[filter] & zooms.rule)) continue;
|
||||||
@ -198,8 +178,7 @@ tree.Definition.prototype.toXML = function(env, existing) {
|
|||||||
while (zooms.current = zooms.rule & available) {
|
while (zooms.current = zooms.rule & available) {
|
||||||
if (symbolizers = this.collectSymbolizers(zooms, i)) {
|
if (symbolizers = this.collectSymbolizers(zooms, i)) {
|
||||||
if (!(existing[filter] & zooms.current)) continue;
|
if (!(existing[filter] & zooms.current)) continue;
|
||||||
xml += this.symbolizersToXML(env, symbolizers,
|
xml += this.symbolizersToXML(env, symbolizers, existing[filter] & zooms.current);
|
||||||
(new tree.Zoom()).setZoom(existing[filter] & zooms.current));
|
|
||||||
existing[filter] &= ~zooms.current;
|
existing[filter] &= ~zooms.current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,41 +187,4 @@ tree.Definition.prototype.toXML = function(env, existing) {
|
|||||||
return xml;
|
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'));
|
})(require('../tree'));
|
||||||
|
@ -1,98 +1,42 @@
|
|||||||
(function(tree) {
|
(function(tree) {
|
||||||
var _ = global._ || require('underscore');
|
|
||||||
//
|
//
|
||||||
// A number with a unit
|
// A number with a unit
|
||||||
//
|
//
|
||||||
tree.Dimension = function Dimension(value, unit, index) {
|
tree.Dimension = function Dimension(value, unit, index) {
|
||||||
this.value = parseFloat(value);
|
this.value = parseFloat(value);
|
||||||
this.unit = unit || null;
|
this.unit = unit || null;
|
||||||
|
this.is = 'float';
|
||||||
this.index = index;
|
this.index = index;
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.Dimension.prototype = {
|
tree.Dimension.prototype = {
|
||||||
is: 'float',
|
eval: function (env) {
|
||||||
physical_units: ['m', 'cm', 'in', 'mm', 'pt', 'pc'],
|
if (this.unit && ['px', '%'].indexOf(this.unit) === -1) {
|
||||||
screen_units: ['px', '%'],
|
env.error({
|
||||||
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({
|
|
||||||
message: "Invalid unit: '" + this.unit + "'",
|
message: "Invalid unit: '" + this.unit + "'",
|
||||||
index: this.index
|
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;
|
return this;
|
||||||
},
|
},
|
||||||
toColor: function() {
|
toColor: function() {
|
||||||
return new tree.Color([this.value, this.value, this.value]);
|
return new tree.Color([this.value, this.value, this.value]);
|
||||||
},
|
},
|
||||||
round: function() {
|
|
||||||
this.value = Math.round(this.value);
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
toString: function() {
|
toString: function() {
|
||||||
return this.value.toString();
|
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 === '%') {
|
// In an operation between two Dimensions,
|
||||||
if (op === '*' || op === '/' || op === '%') {
|
// we default to the first Dimension's unit,
|
||||||
env.error({
|
// so `1px + 2em` will yield `3px`.
|
||||||
message: 'Percent values can only be added or subtracted from other values',
|
// In the future, we could implement some unit
|
||||||
index: this.index
|
// conversions such that `100cm + 10mm` would yield
|
||||||
});
|
// `101cm`.
|
||||||
return {
|
operate: function(op, other) {
|
||||||
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
|
|
||||||
return new tree.Dimension(tree.operate(op, this.value, other.value),
|
return new tree.Dimension(tree.operate(op, this.value, other.value),
|
||||||
this.unit || other.unit);
|
this.unit || other.unit);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
39
lib/carto/tree/directive.js
Normal file
39
lib/carto/tree/directive.js
Normal 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'));
|
@ -3,28 +3,31 @@
|
|||||||
// An element is an id or class selector
|
// An element is an id or class selector
|
||||||
tree.Element = function Element(value) {
|
tree.Element = function Element(value) {
|
||||||
this.value = value.trim();
|
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
|
// Determine the 'specificity matrix' of this
|
||||||
// specific selector
|
// specific selector
|
||||||
tree.Element.prototype.specificity = function() {
|
tree.Element.prototype.specificity = function() {
|
||||||
return [
|
return [
|
||||||
(this.type === 'id') ? 1 : 0, // a
|
(this.value[0] == '#') ? 1 : 0, // a
|
||||||
(this.type === 'class') ? 1 : 0 // b
|
(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'));
|
})(require('../tree'));
|
||||||
|
@ -3,19 +3,16 @@
|
|||||||
tree.Expression = function Expression(value) {
|
tree.Expression = function Expression(value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.Expression.prototype = {
|
tree.Expression.prototype = {
|
||||||
is: 'expression',
|
eval: function(env) {
|
||||||
ev: function(env) {
|
|
||||||
if (this.value.length > 1) {
|
if (this.value.length > 1) {
|
||||||
return new tree.Expression(this.value.map(function(e) {
|
return new tree.Expression(this.value.map(function(e) {
|
||||||
return e.ev(env);
|
return e.eval(env);
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
return this.value[0].ev(env);
|
return this.value[0].eval(env);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toString: function(env) {
|
toString: function(env) {
|
||||||
return this.value.map(function(e) {
|
return this.value.map(function(e) {
|
||||||
return e.toString(env);
|
return e.toString(env);
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
tree.Field = function Field(content) {
|
tree.Field = function Field(content) {
|
||||||
this.value = content || '';
|
this.value = content || '';
|
||||||
|
this.is = 'field';
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.Field.prototype = {
|
tree.Field.prototype = {
|
||||||
is: 'field',
|
|
||||||
toString: function() {
|
toString: function() {
|
||||||
return '[' + this.value + ']';
|
return '[' + this.value + ']';
|
||||||
},
|
},
|
||||||
'ev': function() {
|
'eval': function() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,32 @@
|
|||||||
(function(tree) {
|
(function(tree) {
|
||||||
|
|
||||||
tree.Filter = function Filter(key, op, val, index, filename) {
|
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.op = op;
|
||||||
this.val = val;
|
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.filename = filename;
|
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;
|
this.id = this.key + this.op + this.val;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// xmlsafe, numeric, suffix
|
// xmlsafe, numeric, suffix
|
||||||
var ops = {
|
var ops = {
|
||||||
'<': [' < ', 'numeric'],
|
'<': [' < ', 'numeric'],
|
||||||
@ -21,35 +38,15 @@ var ops = {
|
|||||||
'=~': ['.match(', 'string', ')']
|
'=~': ['.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) {
|
tree.Filter.prototype.toXML = function(env) {
|
||||||
if (tree.Reference.data.filter) {
|
if (this.val.eval) this._val = this.val.eval(env);
|
||||||
if (this.key.is === 'keyword' && -1 === tree.Reference.data.filter.value.indexOf(this.key.toString())) {
|
if (this.key.eval) this._key = this.key.eval(env);
|
||||||
env.error({
|
if (this._key) var key = this._key.toString(false);
|
||||||
message: this.key.toString() + ' is not a valid keyword in a filter expression',
|
if (this._val) var val = this._val.toString(this._val.is == 'string');
|
||||||
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 (
|
if (
|
||||||
(ops[this.op][1] == 'numeric' && isNaN(val) && this.val.is !== 'field') ||
|
(ops[this.op][1] == 'numeric' && isNaN(this.val)) ||
|
||||||
(ops[this.op][1] == 'string' && (val)[0] != "'")
|
(ops[this.op][1] == 'string' && (val || this.val)[0] != "'")
|
||||||
) {
|
) {
|
||||||
env.error({
|
env.error({
|
||||||
message: 'Cannot use operator "' + this.op + '" with value ' + this.val,
|
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() {
|
tree.Filter.prototype.toString = function() {
|
||||||
|
@ -1,270 +1,224 @@
|
|||||||
var tree = require('../tree');
|
var tree = require('../tree');
|
||||||
var _ = global._ || require('underscore');
|
|
||||||
|
|
||||||
tree.Filterset = function Filterset() {
|
tree.Filterset = function Filterset() {};
|
||||||
this.filters = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
tree.Filterset.prototype.toXML = function(env) {
|
Object.defineProperty(tree.Filterset.prototype, 'toXML', {
|
||||||
var filters = [];
|
enumerable: false,
|
||||||
for (var id in this.filters) {
|
value: function(env) {
|
||||||
filters.push('(' + this.filters[id].toXML(env).trim() + ')');
|
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() {
|
Object.defineProperty(tree.Filterset.prototype, 'toString', {
|
||||||
var arr = [];
|
enumerable: false,
|
||||||
for (var id in this.filters) arr.push(this.filters[id].id);
|
value: function() {
|
||||||
return arr.sort().join('\t');
|
var arr = [];
|
||||||
};
|
for (var id in this) arr.push(this[id].id);
|
||||||
|
arr.sort();
|
||||||
tree.Filterset.prototype.ev = function(env) {
|
return arr.join('\t');
|
||||||
for (var i in this.filters) {
|
|
||||||
this.filters[i].ev(env);
|
|
||||||
}
|
}
|
||||||
return this;
|
});
|
||||||
};
|
|
||||||
|
|
||||||
tree.Filterset.prototype.clone = function() {
|
Object.defineProperty(tree.Filterset.prototype, 'clone', {
|
||||||
var clone = new tree.Filterset();
|
enumerable: false,
|
||||||
for (var id in this.filters) {
|
value: function() {
|
||||||
clone.filters[id] = this.filters[id];
|
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.
|
// Note: other has to be a tree.Filterset.
|
||||||
tree.Filterset.prototype.cloneWith = function(other) {
|
Object.defineProperty(tree.Filterset.prototype, 'cloneWith', {
|
||||||
var additions = [];
|
enumerable: false,
|
||||||
for (var id in other.filters) {
|
value: function(other) {
|
||||||
var status = this.addable(other.filters[id]);
|
var additions;
|
||||||
// status is true, false or null. if it's null we don't fail this
|
for (var id in other) {
|
||||||
// clone nor do we add the filter.
|
var status = this.addable(other[id]);
|
||||||
if (status === false) {
|
if (status === false) {
|
||||||
return 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.
|
// Adding the other filters doesn't make this filterset invalid, but it
|
||||||
additions.push(other.filters[id]);
|
// 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.
|
* Only call this function for filters that have been cleared by .addable().
|
||||||
if (!additions.length) {
|
*/
|
||||||
return null;
|
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
|
switch (filter.op) {
|
||||||
// new rules.
|
case '=':
|
||||||
var clone = new tree.Filterset();
|
for (var id in this) {
|
||||||
|
if (this[id].key == key) {
|
||||||
// We can add the rules that are already present without going through the
|
delete this[id];
|
||||||
// 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(/&/g, '&') + "'" : val) + ")";
|
|
||||||
}
|
|
||||||
return attrs + "['" + filter.key.value + "'] " + op + " " + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&/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;
|
|
||||||
}
|
}
|
||||||
}
|
this[key + '='] = filter;
|
||||||
if (this.filters[key + '!=' + value] !== undefined) return false;
|
break;
|
||||||
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;
|
|
||||||
|
|
||||||
case '=~':
|
case '!=':
|
||||||
return true;
|
this[key + '!=' + filter.val] = filter;
|
||||||
|
break;
|
||||||
|
|
||||||
case '!=':
|
case '=~':
|
||||||
if (this.filters[key + '='] !== undefined) return (this.filters[key + '='].val == value) ? false : null;
|
this[key + '=~' + filter.val] = filter;
|
||||||
if (this.filters[key + '!=' + value] !== undefined) return null;
|
break;
|
||||||
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 '>':
|
case '>':
|
||||||
if (key + '=' in this.filters) {
|
// If there are other filters that are also >
|
||||||
if (this.filters[key + '='].val <= value) {
|
// but are less than this one, they don't matter, so
|
||||||
return false;
|
// remove them.
|
||||||
} else {
|
for (var id in this) {
|
||||||
return null;
|
if (this[id].key == key && this[id].val <= filter.val) {
|
||||||
|
delete this[id];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
this[key + '>'] = filter;
|
||||||
if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return false;
|
break;
|
||||||
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 '>=':
|
case '>=':
|
||||||
if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val < value) ? false : null;
|
for (var id in this) {
|
||||||
if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return false;
|
if (this[id].key == key && this[id].val < filter.val) {
|
||||||
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return false;
|
delete this[id];
|
||||||
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;
|
if (key + '!=' + filter.val in this) {
|
||||||
|
delete this[key + '!=' + filter.val];
|
||||||
|
filter.op = '>';
|
||||||
|
this[key + '>'] = filter;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this[key + '>='] = filter;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case '<':
|
case '<':
|
||||||
if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val >= value) ? false : null;
|
for (var id in this) {
|
||||||
if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return false;
|
if (this[id].key == key && this[id].val >= filter.val) {
|
||||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val >= value) return false;
|
delete this[id];
|
||||||
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 '<=':
|
case '<=':
|
||||||
if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val > value) ? false : null;
|
for (var id in this) {
|
||||||
if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return false;
|
if (this[id].key == key && this[id].val > filter.val) {
|
||||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return false;
|
delete this[id];
|
||||||
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;
|
if (key + '!=' + filter.val in this) {
|
||||||
}
|
delete this[key + '!=' + filter.val];
|
||||||
};
|
filter.op = '<';
|
||||||
|
this[key + '<'] = filter;
|
||||||
// Does the new filter constitute a conflict?
|
}
|
||||||
tree.Filterset.prototype.conflict = function(filter) {
|
else {
|
||||||
var key = filter.key.toString(),
|
this[key + '<='] = filter;
|
||||||
value = filter.val.toString();
|
}
|
||||||
|
break;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
(function(tree) {
|
(function(tree) {
|
||||||
|
|
||||||
tree._getFontSet = function(env, fonts) {
|
tree._getFontSet = function(env, fonts) {
|
||||||
var fontKey = fonts.join('');
|
var find_existing = function(fonts) {
|
||||||
if (env._fontMap && env._fontMap[fontKey]) {
|
var findFonts = fonts.join('');
|
||||||
return env._fontMap[fontKey];
|
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);
|
var existing = false;
|
||||||
env.effects.push(new_fontset);
|
if (existing = find_existing(fonts)) {
|
||||||
if (!env._fontMap) env._fontMap = {};
|
return existing;
|
||||||
env._fontMap[fontKey] = new_fontset;
|
} else {
|
||||||
return new_fontset;
|
var new_fontset = new tree.FontSet(env, fonts);
|
||||||
|
env.effects.push(new_fontset);
|
||||||
|
return new_fontset;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.FontSet = function FontSet(env, fonts) {
|
tree.FontSet = function FontSet(env, fonts) {
|
||||||
|
@ -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;
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
|||||||
(function(tree) {
|
(function(tree) {
|
||||||
|
//
|
||||||
|
// RGB Colors - #ff0014, #eee
|
||||||
|
//
|
||||||
tree.ImageFilter = function ImageFilter(filter, args) {
|
tree.ImageFilter = function ImageFilter(filter, args) {
|
||||||
|
this.is = 'imagefilter';
|
||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
this.args = args || null;
|
this.args = args || null;
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.ImageFilter.prototype = {
|
tree.ImageFilter.prototype = {
|
||||||
is: 'imagefilter',
|
eval: function() { return this; },
|
||||||
ev: function() { return this; },
|
|
||||||
|
|
||||||
toString: function() {
|
toString: function() {
|
||||||
if (this.args) {
|
if (this.args) {
|
||||||
return this.filter + '(' + this.args.join(',') + ')';
|
return this.filter + ':' + this.args.join(',');
|
||||||
} else {
|
} else {
|
||||||
return this.filter;
|
return this.filter;
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,7 @@ tree.Invalid = function Invalid(chunk, index, message) {
|
|||||||
this.message = message || "Invalid code: " + this.chunk;
|
this.message = message || "Invalid code: " + this.chunk;
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.Invalid.prototype.is = 'invalid';
|
tree.Invalid.prototype.eval = function(env) {
|
||||||
|
|
||||||
tree.Invalid.prototype.ev = function(env) {
|
|
||||||
env.error({
|
env.error({
|
||||||
chunk: this.chunk,
|
chunk: this.chunk,
|
||||||
index: this.index,
|
index: this.index,
|
||||||
|
@ -10,7 +10,7 @@ tree.Keyword = function Keyword(value) {
|
|||||||
this.is = special[value] ? special[value] : 'keyword';
|
this.is = special[value] ? special[value] : 'keyword';
|
||||||
};
|
};
|
||||||
tree.Keyword.prototype = {
|
tree.Keyword.prototype = {
|
||||||
ev: function() { return this; },
|
eval: function() { return this; },
|
||||||
toString: function() { return this.value; }
|
toString: function() { return this.value; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,36 +1,35 @@
|
|||||||
(function(tree) {
|
(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 = [];
|
var dsoptions = [];
|
||||||
for (var i in obj.Datasource) {
|
for (var i in this.datasource) {
|
||||||
dsoptions.push('<Parameter name="' + i + '"><![CDATA[' +
|
dsoptions.push('<Parameter name="' + i + '"><![CDATA[' +
|
||||||
obj.Datasource[i] + ']]></Parameter>');
|
this.datasource[i] + ']]></Parameter>');
|
||||||
}
|
}
|
||||||
|
|
||||||
var prop_string = '';
|
var prop_string = '';
|
||||||
for (var prop in obj.properties) {
|
for (var i in this.properties) {
|
||||||
if (prop === 'minzoom') {
|
prop_string += ' ' + i + '="' + this.properties[i] + '"\n';
|
||||||
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';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return '<Layer' +
|
return '<Layer' +
|
||||||
' name="' + obj.name + '"\n' +
|
' name="' + this.name + '"\n' +
|
||||||
prop_string +
|
prop_string +
|
||||||
((typeof obj.status === 'undefined') ? '' : ' status="' + obj.status + '"\n') +
|
' srs="' + this.srs + '">\n ' +
|
||||||
((typeof obj.srs === 'undefined') ? '' : ' srs="' + obj.srs + '"') + '>\n ' +
|
this.styles.reverse().map(function(s) {
|
||||||
styles.reverse().map(function(s) {
|
|
||||||
return '<StyleName>' + s + '</StyleName>';
|
return '<StyleName>' + s + '</StyleName>';
|
||||||
}).join('\n ') +
|
}).join('\n ') +
|
||||||
(dsoptions.length ?
|
|
||||||
'\n <Datasource>\n ' +
|
'\n <Datasource>\n ' +
|
||||||
dsoptions.join('\n ') +
|
dsoptions.join('\n ') +
|
||||||
'\n </Datasource>\n'
|
'\n </Datasource>\n' +
|
||||||
: '') +
|
|
||||||
' </Layer>\n';
|
' </Layer>\n';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ tree.Literal.prototype = {
|
|||||||
toString: function() {
|
toString: function() {
|
||||||
return this.value;
|
return this.value;
|
||||||
},
|
},
|
||||||
'ev': function() {
|
'eval': function() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
// An operation is an expression with an op in between two operands,
|
|
||||||
// like 2 + 1.
|
|
||||||
(function(tree) {
|
(function(tree) {
|
||||||
|
|
||||||
tree.Operation = function Operation(op, operands, index) {
|
tree.Operation = function Operation(op, operands, index) {
|
||||||
this.op = op.trim();
|
this.op = op.trim();
|
||||||
this.operands = operands;
|
this.operands = operands;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
|
this.is = 'operation';
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.Operation.prototype.is = 'operation';
|
tree.Operation.prototype.eval = function(env) {
|
||||||
|
var a = this.operands[0].eval(env),
|
||||||
tree.Operation.prototype.ev = function(env) {
|
b = this.operands[1].eval(env),
|
||||||
var a = this.operands[0].ev(env),
|
|
||||||
b = this.operands[1].ev(env),
|
|
||||||
temp;
|
temp;
|
||||||
|
|
||||||
if (a.is === 'undefined' || b.is === 'undefined') {
|
if (a.is === 'undefined' || b.is === 'undefined') {
|
||||||
@ -64,24 +61,11 @@ tree.Operation.prototype.ev = function(env) {
|
|||||||
value: 'undefined'
|
value: 'undefined'
|
||||||
};
|
};
|
||||||
} else {
|
} 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) {
|
return a.operate(this.op, b);
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.operate = function(op, a, b) {
|
tree.operate = function(op, a, b) {
|
||||||
|
@ -2,28 +2,22 @@
|
|||||||
|
|
||||||
tree.Quoted = function Quoted(content) {
|
tree.Quoted = function Quoted(content) {
|
||||||
this.value = content || '';
|
this.value = content || '';
|
||||||
|
this.is = 'string';
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.Quoted.prototype = {
|
tree.Quoted.prototype = {
|
||||||
is: 'string',
|
|
||||||
|
|
||||||
toString: function(quotes) {
|
toString: function(quotes) {
|
||||||
var escapedValue = this.value
|
var xmlvalue = this.value.replace(/\'/g, ''');
|
||||||
.replace(/&/g, '&')
|
return (quotes === true) ? "'" + xmlvalue + "'" : this.value;
|
||||||
var xmlvalue = escapedValue
|
|
||||||
.replace(/\'/g, '\\\'')
|
|
||||||
.replace(/\"/g, '"')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/\>/g, '>');
|
|
||||||
return (quotes === true) ? "'" + xmlvalue + "'" : escapedValue;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
'ev': function() {
|
'eval': function() {
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
operate: function(env, op, other) {
|
operate: function(op, other) {
|
||||||
return new tree.Quoted(tree.operate(op, this.toString(), other.toString(this.contains_field)));
|
return new tree.Quoted(true,
|
||||||
|
tree.operate(op, this.toString(), other.toString(this.contains_field)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,88 +1,103 @@
|
|||||||
// Carto pulls in a reference from the `mapnik-reference`
|
/*
|
||||||
// module. This file builds indexes from that file for its various
|
* Carto pulls in a reference from the `mapnik-reference`
|
||||||
// options, and provides validation methods for property: value
|
* module. This file builds indexes from that file for its various
|
||||||
// combinations.
|
* options, and provides validation methods for property: value
|
||||||
|
* combinations.
|
||||||
|
*/
|
||||||
(function(tree) {
|
(function(tree) {
|
||||||
|
|
||||||
var _ = global._ || require('underscore'),
|
var _ = require('underscore');
|
||||||
ref = {};
|
var mapnik_reference = require('mapnik-reference');
|
||||||
|
|
||||||
ref.setData = function(data) {
|
tree.Reference = {
|
||||||
ref.data = data;
|
data: mapnik_reference.version.latest
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ref.setVersion = function(version) {
|
tree.Reference.setVersion = function(version) {
|
||||||
var mapnik_reference = require('mapnik-reference');
|
tree.Reference.data = mapnik_reference.version[version];
|
||||||
if (mapnik_reference.version.hasOwnProperty(version)) {
|
|
||||||
ref.setData(mapnik_reference.version[version]);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ref.selectorData = function(selector, i) {
|
tree.Reference.required_prop_list_cache = {};
|
||||||
if (ref.selector_cache[selector]) return ref.selector_cache[selector][i];
|
|
||||||
};
|
|
||||||
|
|
||||||
ref.validSelector = function(selector) { return !!ref.selector_cache[selector]; };
|
tree.Reference.selectors = tree.Reference.selectors || (function() {
|
||||||
ref.selectorName = function(selector) { return ref.selectorData(selector, 2); };
|
var list = [];
|
||||||
ref.selector = function(selector) { return ref.selectorData(selector, 0); };
|
for (var i in tree.Reference.data.symbolizers) {
|
||||||
ref.symbolizer = function(selector) { return ref.selectorData(selector, 1); };
|
for (var j in tree.Reference.data.symbolizers[i]) {
|
||||||
|
if (tree.Reference.data.symbolizers[i][j].hasOwnProperty('css')) {
|
||||||
function generateSelectorCache(data) {
|
list.push(tree.Reference.data.symbolizers[i][j].css);
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return index;
|
return list;
|
||||||
}
|
})();
|
||||||
|
|
||||||
function generateMapnikFunctions(data) {
|
tree.Reference.validSelector = function(selector) {
|
||||||
var functions = {};
|
return tree.Reference.selectors.indexOf(selector) !== -1;
|
||||||
for (var i in data.symbolizers) {
|
};
|
||||||
for (var j in data.symbolizers[i]) {
|
|
||||||
if (data.symbolizers[i][j].type === 'functions') {
|
tree.Reference.selectorName = function(selector) {
|
||||||
for (var k = 0; k < data.symbolizers[i][j].functions.length; k++) {
|
for (var i in tree.Reference.data.symbolizers) {
|
||||||
var fn = data.symbolizers[i][j].functions[k];
|
for (var j in tree.Reference.data.symbolizers[i]) {
|
||||||
functions[fn[0]] = fn[1];
|
if (selector == tree.Reference.data.symbolizers[i][j].css) {
|
||||||
}
|
return j;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return functions;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
function generateRequiredProperties(data) {
|
tree.Reference.selector = function(selector) {
|
||||||
var cache = {};
|
for (var i in tree.Reference.data.symbolizers) {
|
||||||
for (var symbolizer_name in data.symbolizers) {
|
for (var j in tree.Reference.data.symbolizers[i]) {
|
||||||
cache[symbolizer_name] = [];
|
if (selector == tree.Reference.data.symbolizers[i][j].css) {
|
||||||
for (var j in data.symbolizers[symbolizer_name]) {
|
return tree.Reference.data.symbolizers[i][j];
|
||||||
if (data.symbolizers[symbolizer_name][j].required) {
|
|
||||||
cache[symbolizer_name].push(data.symbolizers[symbolizer_name][j].css);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cache;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
ref.requiredProperties = function(symbolizer_name, rules) {
|
tree.Reference.symbolizer = function(selector) {
|
||||||
var req = ref.required_cache[symbolizer_name];
|
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) {
|
for (var i in req) {
|
||||||
if (!(req[i] in rules)) {
|
if (!(req[i] in rules)) {
|
||||||
return 'Property ' + req[i] + ' required for defining ' +
|
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) {
|
'font': function(env, value) {
|
||||||
if (env.validation_data && env.validation_data.fonts) {
|
if (env.validation_data && env.validation_data.fonts) {
|
||||||
return env.validation_data.fonts.indexOf(value) != -1;
|
return env.validation_data.fonts.indexOf(value) != -1;
|
||||||
@ -102,118 +119,72 @@ ref._validateValue = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ref.isFont = function(selector) {
|
tree.Reference.isFont = function(selector) {
|
||||||
return ref.selector(selector).validate == 'font';
|
return tree.Reference.selector(selector).validate == 'font';
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://gist.github.com/982927
|
tree.Reference.validValue = function(env, selector, value) {
|
||||||
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) {
|
|
||||||
var i, j;
|
var i, j;
|
||||||
// TODO: handle in reusable way
|
// TODO: handle in reusable way
|
||||||
if (!ref.selector(selector)) {
|
if (!tree.Reference.selector(selector)) {
|
||||||
return false;
|
return false;
|
||||||
} else if (value.value[0].is == 'keyword') {
|
} 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') {
|
} else if (value.value[0].is == 'undefined') {
|
||||||
// caught earlier in the chain - ignore here so that
|
// caught earlier in the chain - ignore here so that
|
||||||
// error is not overridden
|
// error is not overridden
|
||||||
return true;
|
return true;
|
||||||
} else if (ref.selector(selector).type == 'numbers') {
|
} else if (tree.Reference.selector(selector).type == 'numbers') {
|
||||||
for (i in value.value) {
|
for (i in value.value) {
|
||||||
if (value.value[i].is !== 'float') {
|
if (value.value[i].is !== 'float') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if (ref.selector(selector).type == 'tags') {
|
} else if (tree.Reference.selector(selector).type == 'functions') {
|
||||||
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') {
|
|
||||||
// For backwards compatibility, you can specify a string for `functions`-compatible
|
// For backwards compatibility, you can specify a string for `functions`-compatible
|
||||||
// values, though they will not be validated.
|
// values, though they will not be validated.
|
||||||
return validateFunctions(value, selector);
|
if (value.value[0].is === 'string') {
|
||||||
} else if (ref.selector(selector).type === 'unsigned') {
|
|
||||||
if (value.value[0].is === 'float') {
|
|
||||||
value.value[0].round();
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} 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;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (ref.selector(selector).validate) {
|
if (tree.Reference.selector(selector).validate) {
|
||||||
var valid = false;
|
var valid = false;
|
||||||
for (i = 0; i < value.value.length; i++) {
|
for (i = 0; i < value.value.length; i++) {
|
||||||
if (ref.selector(selector).type == value.value[i].is &&
|
if (tree.Reference.selector(selector).type == value.value[i].is &&
|
||||||
ref
|
tree.Reference
|
||||||
._validateValue
|
._validateValue
|
||||||
[ref.selector(selector).validate]
|
[tree.Reference.selector(selector).validate]
|
||||||
(env, value.value[i].value)) {
|
(env, value.value[i].value)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return valid;
|
return valid;
|
||||||
} else {
|
} 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'));
|
})(require('../tree'));
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
(function(tree) {
|
(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) {
|
tree.Rule = function Rule(name, value, index, filename) {
|
||||||
var parts = name.split('/');
|
var parts = name.split('/');
|
||||||
this.name = parts.pop();
|
this.name = parts.pop();
|
||||||
@ -14,8 +11,6 @@ tree.Rule = function Rule(name, value, index, filename) {
|
|||||||
this.variable = (name.charAt(0) === '@');
|
this.variable = (name.charAt(0) === '@');
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.Rule.prototype.is = 'rule';
|
|
||||||
|
|
||||||
tree.Rule.prototype.clone = function() {
|
tree.Rule.prototype.clone = function() {
|
||||||
var clone = Object.create(tree.Rule.prototype);
|
var clone = Object.create(tree.Rule.prototype);
|
||||||
clone.name = this.name;
|
clone.name = this.name;
|
||||||
@ -29,32 +24,21 @@ tree.Rule.prototype.clone = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
tree.Rule.prototype.updateID = 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() {
|
tree.Rule.prototype.toString = function() {
|
||||||
return '[' + tree.Zoom.toString(this.zoom) + '] ' + this.name + ': ' + this.value;
|
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
|
// second argument, if true, outputs the value of this
|
||||||
// rule without the usual attribute="content" wrapping. Right
|
// rule without the usual attribute="content" wrapping. Right
|
||||||
// now this is just for the TextSymbolizer, but applies to other
|
// now this is just for the TextSymbolizer, but applies to other
|
||||||
// properties in reference.json which specify serialization=content
|
// properties in reference.json which specify serialization=content
|
||||||
tree.Rule.prototype.toXML = function(env, content, sep, format) {
|
tree.Rule.prototype.toXML = function(env, content, sep, format) {
|
||||||
if (!tree.Reference.validSelector(this.name)) {
|
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({
|
return env.error({
|
||||||
message: "Unrecognized rule: " + this.name + mean_message,
|
message: "Unrecognized rule: " + this.name,
|
||||||
index: this.index,
|
index: this.index,
|
||||||
type: 'syntax',
|
type: 'syntax',
|
||||||
filename: this.filename
|
filename: this.filename
|
||||||
@ -72,20 +56,13 @@ tree.Rule.prototype.toXML = function(env, content, sep, format) {
|
|||||||
filename: this.filename
|
filename: this.filename
|
||||||
});
|
});
|
||||||
} else {
|
} 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({
|
return env.error({
|
||||||
message: 'Invalid value for ' +
|
message: 'Invalid value for ' +
|
||||||
this.name +
|
this.name +
|
||||||
', the type ' + typename +
|
', a valid ' +
|
||||||
|
(tree.Reference.selector(this.name).validate ||
|
||||||
|
tree.Reference.selector(this.name).type) +
|
||||||
' is expected. ' + this.value +
|
' is expected. ' + this.value +
|
||||||
' (of type ' + this.value.value[0].is + ') ' +
|
|
||||||
' was given.',
|
' was given.',
|
||||||
index: this.index,
|
index: this.index,
|
||||||
type: 'syntax',
|
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,
|
return new tree.Rule(this.name,
|
||||||
this.value.ev(context),
|
this.value['eval'](context),
|
||||||
this.index,
|
this.index,
|
||||||
this.filename);
|
this.filename);
|
||||||
};
|
};
|
||||||
|
@ -7,19 +7,27 @@ tree.Ruleset = function Ruleset(selectors, rules) {
|
|||||||
this._lookups = {};
|
this._lookups = {};
|
||||||
};
|
};
|
||||||
tree.Ruleset.prototype = {
|
tree.Ruleset.prototype = {
|
||||||
is: 'ruleset',
|
eval: function(env) {
|
||||||
'ev': function(env) {
|
var ruleset = new tree.Ruleset(this.selectors, this.rules.slice(0));
|
||||||
var i,
|
|
||||||
ruleset = new tree.Ruleset(this.selectors, this.rules.slice(0));
|
|
||||||
ruleset.root = this.root;
|
ruleset.root = this.root;
|
||||||
|
|
||||||
// push the current ruleset to the frames stack
|
// push the current ruleset to the frames stack
|
||||||
env.frames.unshift(ruleset);
|
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
|
// 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];
|
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
|
// Pop the stack
|
||||||
@ -44,6 +52,14 @@ tree.Ruleset.prototype = {
|
|||||||
variable: function(name) {
|
variable: function(name) {
|
||||||
return this.variables()[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() {
|
rulesets: function() {
|
||||||
if (this._rulesets) { return this._rulesets; }
|
if (this._rulesets) { return this._rulesets; }
|
||||||
else {
|
else {
|
||||||
@ -62,11 +78,10 @@ tree.Ruleset.prototype = {
|
|||||||
this.rulesets().forEach(function(rule) {
|
this.rulesets().forEach(function(rule) {
|
||||||
if (rule !== self) {
|
if (rule !== self) {
|
||||||
for (var j = 0; j < rule.selectors.length; j++) {
|
for (var j = 0; j < rule.selectors.length; j++) {
|
||||||
match = selector.match(rule.selectors[j]);
|
if (match = selector.match(rule.selectors[j])) {
|
||||||
if (match) {
|
|
||||||
if (selector.elements.length > 1) {
|
if (selector.elements.length > 1) {
|
||||||
Array.prototype.push.apply(rules, rule.find(
|
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 {
|
} else {
|
||||||
rules.push(rule);
|
rules.push(rule);
|
||||||
}
|
}
|
||||||
@ -77,35 +92,19 @@ tree.Ruleset.prototype = {
|
|||||||
});
|
});
|
||||||
return this._lookups[key] = rules;
|
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) {
|
flatten: function(result, parents, env) {
|
||||||
var selectors = [], i, j;
|
var selectors = [];
|
||||||
if (this.selectors.length === 0) {
|
if (this.selectors.length === 0) {
|
||||||
env.frames = env.frames.concat(this.rules);
|
env.frames = env.frames.concat(this.rules);
|
||||||
}
|
}
|
||||||
// evaluate zoom variables on this object.
|
for (var i = 0; i < this.selectors.length; i++) {
|
||||||
this.evZooms(env);
|
|
||||||
for (i = 0; i < this.selectors.length; i++) {
|
|
||||||
var child = this.selectors[i];
|
var child = this.selectors[i];
|
||||||
|
|
||||||
if (!child.filters) {
|
// This is an invalid filterset.
|
||||||
// TODO: is this internal inconsistency?
|
if (!child.filters) continue;
|
||||||
// This is an invalid filterset.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parents.length) {
|
if (parents.length) {
|
||||||
for (j = 0; j < parents.length; j++) {
|
for (var j = 0; j < parents.length; j++) {
|
||||||
var parent = parents[j];
|
var parent = parents[j];
|
||||||
|
|
||||||
var mergedFilters = parent.filters.cloneWith(child.filters);
|
var mergedFilters = parent.filters.cloneWith(child.filters);
|
||||||
@ -114,10 +113,7 @@ tree.Ruleset.prototype = {
|
|||||||
// filters. This means that we only have to clone when
|
// filters. This means that we only have to clone when
|
||||||
// the zoom levels or the attachment is different too.
|
// the zoom levels or the attachment is different too.
|
||||||
if (parent.zoom === (parent.zoom & child.zoom) &&
|
if (parent.zoom === (parent.zoom & child.zoom) &&
|
||||||
parent.frame_offset === child.frame_offset &&
|
parent.attachment === child.attachment) {
|
||||||
parent.attachment === child.attachment &&
|
|
||||||
parent.elements.join() === child.elements.join()) {
|
|
||||||
selectors.push(parent);
|
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
mergedFilters = parent.filters;
|
mergedFilters = parent.filters;
|
||||||
@ -131,7 +127,6 @@ tree.Ruleset.prototype = {
|
|||||||
var clone = Object.create(tree.Selector.prototype);
|
var clone = Object.create(tree.Selector.prototype);
|
||||||
clone.filters = mergedFilters;
|
clone.filters = mergedFilters;
|
||||||
clone.zoom = parent.zoom & child.zoom;
|
clone.zoom = parent.zoom & child.zoom;
|
||||||
clone.frame_offset = child.frame_offset;
|
|
||||||
clone.elements = parent.elements.concat(child.elements);
|
clone.elements = parent.elements.concat(child.elements);
|
||||||
if (parent.attachment && child.attachment) {
|
if (parent.attachment && child.attachment) {
|
||||||
clone.attachment = parent.attachment + '/' + child.attachment;
|
clone.attachment = parent.attachment + '/' + child.attachment;
|
||||||
@ -147,10 +142,9 @@ tree.Ruleset.prototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var rules = [];
|
var rules = [];
|
||||||
for (i = 0; i < this.rules.length; i++) {
|
for (var i = 0; i < this.rules.length; i++) {
|
||||||
var rule = this.rules[i];
|
var rule = this.rules[i];
|
||||||
|
|
||||||
// Recursively flatten any nested rulesets
|
|
||||||
if (rule instanceof tree.Ruleset) {
|
if (rule instanceof tree.Ruleset) {
|
||||||
rule.flatten(result, selectors, env);
|
rule.flatten(result, selectors, env);
|
||||||
} else if (rule instanceof tree.Rule) {
|
} else if (rule instanceof tree.Rule) {
|
||||||
@ -161,7 +155,7 @@ tree.Ruleset.prototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var index = rules.length ? rules[0].index : false;
|
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
|
// For specificity sort, use the position of the first rule to allow
|
||||||
// defining attachments that are under current element as a descendant
|
// defining attachments that are under current element as a descendant
|
||||||
// selector.
|
// selector.
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
(function(tree) {
|
(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.elements = elements || [];
|
||||||
this.attachment = attachment;
|
this.attachment = attachment;
|
||||||
this.filters = filters || {};
|
this.filters = filters || {};
|
||||||
this.frame_offset = frame_offset;
|
|
||||||
this.zoom = typeof zoom !== 'undefined' ? zoom : tree.Zoom.all;
|
this.zoom = typeof zoom !== 'undefined' ? zoom : tree.Zoom.all;
|
||||||
this.conditions = conditions;
|
this.conditions = conditions;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determine the specificity of this selector
|
/**
|
||||||
// based on the specificity of its elements - calling
|
* Determine the specificity of this selector
|
||||||
// Element.specificity() in order to do so
|
* based on the specificity of its elements - calling
|
||||||
//
|
* Element.specificity() in order to do so
|
||||||
// [ID, Class, Filters, Position in document]
|
*
|
||||||
|
* [ID, Class, Filters, Position in document]
|
||||||
|
*/
|
||||||
tree.Selector.prototype.specificity = function() {
|
tree.Selector.prototype.specificity = function() {
|
||||||
return this.elements.reduce(function(memo, e) {
|
return this.elements.reduce(function(memo, e) {
|
||||||
var spec = e.specificity();
|
var spec = e.specificity();
|
||||||
|
@ -1,68 +1,54 @@
|
|||||||
(function(tree) {
|
(function(tree) {
|
||||||
var _ = global._ || require('underscore');
|
var _ = require('underscore');
|
||||||
|
|
||||||
// Given a style's name, attachment, definitions, and an environment object,
|
tree.Style = function Style(name, attachment, definitions) {
|
||||||
// return a stringified style for Mapnik
|
this.attachment = attachment;
|
||||||
tree.StyleXML = function(name, attachment, definitions, env) {
|
this.definitions = definitions;
|
||||||
|
this.name = name + (attachment !== '__default__' ? '-' + attachment : '');
|
||||||
|
};
|
||||||
|
|
||||||
|
tree.Style.prototype.toXML = function(env) {
|
||||||
var existing = {};
|
var existing = {};
|
||||||
var image_filters = [], image_filters_inflate = [], direct_image_filters = [], comp_op = [], opacity = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < definitions.length; i++) {
|
var image_filters = _.flatten(this.definitions.map(function(definition) {
|
||||||
for (var j = 0; j < definitions[i].rules.length; j++) {
|
return definition.rules.filter(function(rule) {
|
||||||
if (definitions[i].rules[j].name === 'image-filters') {
|
return (rule.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 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);
|
return definition.toXML(env, existing);
|
||||||
});
|
});
|
||||||
|
|
||||||
var attrs_xml = '';
|
var attrs_xml = '';
|
||||||
|
|
||||||
if (image_filters.length) {
|
if (image_filters.length) {
|
||||||
attrs_xml += ' image-filters="' + _.chain(image_filters)
|
attrs_xml += ' image-filters="' + image_filters.map(function(f) {
|
||||||
// prevent identical filters from being duplicated in the style
|
return f.eval(env).toXML(env, true, ' ', 'image-filter');
|
||||||
.uniq(function(i) { return i.id; }).map(function(f) {
|
}).join(' ') + '" ';
|
||||||
return f.ev(env).toXML(env, true, ',', 'image-filter');
|
|
||||||
}).value().join(',') + '"';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (image_filters_inflate.length) {
|
if (comp_op.length) {
|
||||||
attrs_xml += ' image-filters-inflate="' + image_filters_inflate[0].value.ev(env).toString() + '"';
|
attrs_xml += ' comp-op="' + comp_op[0].value.eval(env).toString() + '" ';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (direct_image_filters.length) {
|
if (opacity.length) {
|
||||||
attrs_xml += ' direct-image-filters="' + _.chain(direct_image_filters)
|
attrs_xml += ' opacity="' + opacity[0].value.eval(env).toString() + '" ';
|
||||||
// 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 (comp_op.length && comp_op[0].value.ev(env).value != 'src-over') {
|
return '<Style name="' + this.name + '" filter-mode="first" ' + attrs_xml + '>\n' + rules.join('') + '</Style>';
|
||||||
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>';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
})(require('../tree'));
|
})(require('../tree'));
|
||||||
|
@ -3,15 +3,14 @@
|
|||||||
tree.URL = function URL(val, paths) {
|
tree.URL = function URL(val, paths) {
|
||||||
this.value = val;
|
this.value = val;
|
||||||
this.paths = paths;
|
this.paths = paths;
|
||||||
|
this.is = 'uri';
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.URL.prototype = {
|
tree.URL.prototype = {
|
||||||
is: 'uri',
|
|
||||||
toString: function() {
|
toString: function() {
|
||||||
return this.value.toString();
|
return this.value.toString();
|
||||||
},
|
},
|
||||||
ev: function(ctx) {
|
eval: function(ctx) {
|
||||||
return new tree.URL(this.value.ev(ctx), this.paths);
|
return new tree.URL(this.value.eval(ctx), this.paths);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,58 +2,33 @@
|
|||||||
|
|
||||||
tree.Value = function Value(value) {
|
tree.Value = function Value(value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.is = 'value';
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.Value.prototype = {
|
tree.Value.prototype = {
|
||||||
is: 'value',
|
eval: function(env) {
|
||||||
ev: function(env) {
|
|
||||||
if (this.value.length === 1) {
|
if (this.value.length === 1) {
|
||||||
return this.value[0].ev(env);
|
return this.value[0].eval(env);
|
||||||
} else {
|
} else {
|
||||||
return new tree.Value(this.value.map(function(v) {
|
return new tree.Value(this.value.map(function(v) {
|
||||||
return v.ev(env);
|
return v.eval(env);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toString: function(env, selector, sep, format) {
|
toString: function(env, selector, sep, format) {
|
||||||
return this.value.map(function(e) {
|
return this.value.map(function(e) {
|
||||||
return e.toString(env, format);
|
return e.toString(env, format);
|
||||||
}).join(sep || ', ');
|
}).join(sep || ', ');
|
||||||
},
|
},
|
||||||
|
|
||||||
clone: function() {
|
clone: function() {
|
||||||
var obj = Object.create(tree.Value.prototype);
|
var obj = Object.create(tree.Value.prototype);
|
||||||
if (Array.isArray(obj)) obj.value = this.value.slice();
|
if (Array.isArray(obj)) obj.value = this.value.slice();
|
||||||
else obj.value = this.value;
|
else obj.value = this.value;
|
||||||
obj.is = this.is;
|
obj.is = this.is;
|
||||||
return obj;
|
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(/&/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'));
|
})(require('../tree'));
|
||||||
|
@ -7,22 +7,20 @@ tree.Variable = function Variable(name, index, filename) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
tree.Variable.prototype = {
|
tree.Variable.prototype = {
|
||||||
is: 'variable',
|
eval: function(env) {
|
||||||
toString: function() {
|
|
||||||
return this.name;
|
|
||||||
},
|
|
||||||
ev: function(env) {
|
|
||||||
var variable,
|
var variable,
|
||||||
v,
|
v,
|
||||||
|
that = this,
|
||||||
name = this.name;
|
name = this.name;
|
||||||
|
|
||||||
if (this._css) return this._css;
|
if (this._css) return this._css;
|
||||||
|
|
||||||
var thisframe = env.frames.filter(function(f) {
|
var thisframe = env.frames.filter(function(f) {
|
||||||
return f.name == this.name;
|
return f.name == that.name;
|
||||||
}.bind(this));
|
});
|
||||||
|
|
||||||
if (thisframe.length) {
|
if (thisframe.length) {
|
||||||
return thisframe[0].value.ev(env);
|
return thisframe[0].value.eval(env);
|
||||||
} else {
|
} else {
|
||||||
env.error({
|
env.error({
|
||||||
message: 'variable ' + this.name + ' is undefined',
|
message: 'variable ' + this.name + ' is undefined',
|
||||||
|
@ -4,34 +4,22 @@ var tree = require('../tree');
|
|||||||
// and stores them as bit-sequences so that they can be combined,
|
// and stores them as bit-sequences so that they can be combined,
|
||||||
// inverted, and compared quickly.
|
// inverted, and compared quickly.
|
||||||
tree.Zoom = function(op, value, index) {
|
tree.Zoom = function(op, value, index) {
|
||||||
this.op = op;
|
value = parseInt(value, 10);
|
||||||
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;
|
|
||||||
|
|
||||||
if (value > tree.Zoom.maxZoom || value < 0) {
|
if (value > tree.Zoom.maxZoom || value < 0) {
|
||||||
env.error({
|
throw {
|
||||||
message: 'Only zoom levels between 0 and ' +
|
message: 'Only zoom levels between 0 and ' +
|
||||||
tree.Zoom.maxZoom + ' supported.',
|
tree.Zoom.maxZoom + ' supported.',
|
||||||
index: this.index
|
index: index
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (this.op) {
|
var start = 0,
|
||||||
|
end = Infinity,
|
||||||
|
zoom = 0;
|
||||||
|
|
||||||
|
switch (op) {
|
||||||
case '=':
|
case '=':
|
||||||
this.zoom = 1 << value;
|
return 1 << value;
|
||||||
return this;
|
|
||||||
case '>':
|
case '>':
|
||||||
start = value + 1;
|
start = value + 1;
|
||||||
break;
|
break;
|
||||||
@ -50,12 +38,7 @@ tree.Zoom.prototype.ev = function(env) {
|
|||||||
zoom |= (1 << i);
|
zoom |= (1 << i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.zoom = zoom;
|
return zoom;
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
tree.Zoom.prototype.toString = function() {
|
|
||||||
return this.zoom;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Covers all zoomlevels from 0 to 22
|
// Covers all zoomlevels from 0 to 22
|
||||||
@ -91,12 +74,12 @@ tree.Zoom.ranges = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Only works for single range zooms. `[XXX....XXXXX.........]` is invalid.
|
// Only works for single range zooms. `[XXX....XXXXX.........]` is invalid.
|
||||||
tree.Zoom.prototype.toXML = function() {
|
tree.Zoom.toXML = function(zoom) {
|
||||||
var conditions = [];
|
var conditions = [];
|
||||||
if (this.zoom != tree.Zoom.all) {
|
if (zoom != tree.Zoom.all) {
|
||||||
var start = null, end = null;
|
var start = null, end = null;
|
||||||
for (var i = 0; i <= tree.Zoom.maxZoom; i++) {
|
for (var i = 0; i <= tree.Zoom.maxZoom; i++) {
|
||||||
if (this.zoom & (1 << i)) {
|
if (zoom & (1 << i)) {
|
||||||
if (start === null) start = i;
|
if (start === null) start = i;
|
||||||
end = i;
|
end = i;
|
||||||
}
|
}
|
||||||
@ -109,10 +92,11 @@ tree.Zoom.prototype.toXML = function() {
|
|||||||
return conditions;
|
return conditions;
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.Zoom.prototype.toString = function() {
|
|
||||||
|
tree.Zoom.toString = function(zoom) {
|
||||||
var str = '';
|
var str = '';
|
||||||
for (var i = 0; i <= tree.Zoom.maxZoom; i++) {
|
for (var i = 0; i <= tree.Zoom.maxZoom; i++) {
|
||||||
str += (this.zoom & (1 << i)) ? 'X' : '.';
|
str += (zoom & (1 << i)) ? 'X' : '.';
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
|
24
man/carto.1
24
man/carto.1
@ -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
2110
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
59
package.json
59
package.json
@ -1,17 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "carto",
|
"name": "carto",
|
||||||
"version": "0.15.1-cdb5",
|
"version": "0.7.0",
|
||||||
"description": "CartoCSS Stylesheet Compiler",
|
"description": "Mapnik Stylesheet Compiler",
|
||||||
"url": "https://github.com/cartodb/carto",
|
"url": "https://github.com/mapbox/carto",
|
||||||
"repository": {
|
"repositories": [{
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://github.com/cartodb/carto.git"
|
"url": "http://github.com/mapbox/carto.git"
|
||||||
},
|
}],
|
||||||
"author": {
|
"author": {
|
||||||
"name": "CartoDB",
|
"name": "MapBox",
|
||||||
"url": "http://cartodb.com/"
|
"url": "http://mapbox.com/",
|
||||||
|
"email": "info@mapbox.com"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
"mapnik",
|
||||||
"maps",
|
"maps",
|
||||||
"css",
|
"css",
|
||||||
"stylesheets"
|
"stylesheets"
|
||||||
@ -19,45 +21,32 @@
|
|||||||
"contributors": [
|
"contributors": [
|
||||||
"Tom MacWright <macwright@gmail.com>",
|
"Tom MacWright <macwright@gmail.com>",
|
||||||
"Konstantin Käfer",
|
"Konstantin Käfer",
|
||||||
"Alexis Sellier <self@cloudhead.net>",
|
"Alexis Sellier <self@cloudhead.net>"
|
||||||
"Raul Ochoa <rochoa@cartodb.com>",
|
|
||||||
"Javi Santana <jsantana@cartodb.com>"
|
|
||||||
],
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"type": "Apache"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
|
"licenses": [{
|
||||||
|
"type": "Apache"
|
||||||
|
}],
|
||||||
"bin": {
|
"bin": {
|
||||||
"carto": "./bin/carto"
|
"carto": "./bin/carto",
|
||||||
|
"mml2json.js": "./bin/mml2json.js"
|
||||||
},
|
},
|
||||||
"man": "./man/carto.1",
|
|
||||||
"main": "./lib/carto/index",
|
"main": "./lib/carto/index",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.x"
|
"node": ">=0.4.x"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"underscore": "1.8.3",
|
"underscore": "~1.3.3",
|
||||||
"mapnik-reference": "~6.0.2",
|
"mapnik-reference": "https://github.com/mapnik/mapnik-reference/tarball/transform-functions",
|
||||||
"optimist": "~0.6.0"
|
"xml2js": "~0.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"mocha": "1.12.x",
|
"mocha": "1.0.x",
|
||||||
|
"docco": "0.3.x",
|
||||||
"jshint": "0.2.x",
|
"jshint": "0.2.x",
|
||||||
"sax": "0.1.x",
|
"sax": "0.1.x"
|
||||||
"istanbul": "~0.2.14",
|
|
||||||
"coveralls": "~2.10.1",
|
|
||||||
"browserify": "~7.0.0",
|
|
||||||
"uglify-js": "1.3.3"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"pretest": "npm install",
|
"pretest": "npm install --dev",
|
||||||
"test": "mocha -R spec",
|
"test": "mocha"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -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));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -6,10 +6,9 @@ var carto = require('../lib/carto');
|
|||||||
var tree = require('../lib/carto/tree');
|
var tree = require('../lib/carto/tree');
|
||||||
var helper = require('./support/helper');
|
var helper = require('./support/helper');
|
||||||
|
|
||||||
describe('Error handling mml+mss', function() {
|
describe('Error handling', function() {
|
||||||
helper.files('errorhandling', 'mml', function(file) {
|
helper.files('errorhandling', 'mml', function(file) {
|
||||||
var basename = path.basename(file);
|
it('should handle errors in ' + path.basename(file), function(done) {
|
||||||
it('should handle errors in ' + basename, function(done) {
|
|
||||||
var completed = false;
|
var completed = false;
|
||||||
var renderResult;
|
var renderResult;
|
||||||
var mml = helper.mml(file);
|
var mml = helper.mml(file);
|
||||||
@ -19,58 +18,26 @@ helper.files('errorhandling', 'mml', function(file) {
|
|||||||
data_dir: path.join(__dirname, '../data'),
|
data_dir: path.join(__dirname, '../data'),
|
||||||
local_data_dir: path.join(__dirname, 'rendering'),
|
local_data_dir: path.join(__dirname, 'rendering'),
|
||||||
filename: file
|
filename: file
|
||||||
}).render(mml);
|
}).render(mml, function (err) {
|
||||||
// should not get here
|
var result = helper.resultFile(file);
|
||||||
assert.ok(false);
|
var output = err.message;
|
||||||
done();
|
// @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) {
|
} catch(err) {
|
||||||
if (err.message.indexOf('***') > -1) throw err;
|
|
||||||
var result = helper.resultFile(file);
|
var result = helper.resultFile(file);
|
||||||
var output = err.message;
|
var output = err.message;
|
||||||
// @TODO for some reason, fs.readFile includes an additional \n
|
// @TODO for some reason, fs.readFile includes an additional \n
|
||||||
// at the end of read files. Determine why.
|
// at the end of read files. Determine why.
|
||||||
// fs.writeFileSync(helper.resultFile(file), output);
|
fs.readFile(helper.resultFile(file), 'utf8', function(err, data) {
|
||||||
var data = fs.readFileSync(helper.resultFile(file), 'utf8');
|
if (!err) assert.deepEqual(output, data.substr(0, data.length - 1));
|
||||||
assert.deepEqual(output, data);
|
});
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Error handling mss', function() {
|
done();
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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"
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
#world {
|
|
||||||
line-width: 20% + 2px;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
bad_op.mss:2:4 If two operands differ, the first must not be %
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
#world {
|
|
||||||
line-width: 20px * 2%;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
bad_op_2.mss:2:4 Percent values can only be added or subtracted from other values
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
@foo: 'bar';
|
|
||||||
#world {
|
|
||||||
polygon-fill: hsl(1, @foo, 3);
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
color_functions.mss:3:31 incorrect arguments given to hsl()
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
#world[FeatureCla!=""][FeatureCla=""] {
|
|
||||||
polygon-fill: #fff;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
contradiction.mss:1:37 [[FeatureCla]=] added to [FeatureCla]!= produces an invalid filter
|
|
@ -1,3 +0,0 @@
|
|||||||
#world[FeatureCla=""][FeatureCla!=""] {
|
|
||||||
polygon-fill: #fff;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
contradiction_2.mss:1:37 [[FeatureCla]!=] added to [FeatureCla]= produces an invalid filter
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
#world {
|
|
||||||
point-transform: scale(2, 2);
|
|
||||||
image-filters: agg-stack-blu(2, 1);
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
function_args.mss:3:38 unknown function agg-stack-blu(), did you mean agg-stack-blur(2)
|
|
@ -1,3 +0,0 @@
|
|||||||
#world {
|
|
||||||
polygon-fill: spin(#f00f00f, 10);
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
invalid_color_in_fn.mss:2:34 incorrect arguments given to spin()
|
|
@ -1 +1 @@
|
|||||||
invalid_property.mss:3:2 Unrecognized rule: polygonopacity. Did you mean polygon-opacity?
|
invalid_property.mss:3:2 Unrecognized rule: polygonopacity
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
#world[zoom=5] {
|
#world[zoom=5] {
|
||||||
text-face-name: 2;
|
polygon-opacity: #f00;
|
||||||
text-name: 'foo';
|
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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"
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
#world {
|
|
||||||
line-width: 10wifflewaffles;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
invaliddimension.mss:2:4 Invalid unit: 'wifflewaffles'
|
|
@ -1 +0,0 @@
|
|||||||
issue119.mss:2:2 Map properties are not permitted in other rules
|
|
@ -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.
|
||||||
|
@ -1 +0,0 @@
|
|||||||
issue124.mss:6:0 missing closing `}`
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"Stylesheet": [
|
|
||||||
"issue297.mss"
|
|
||||||
],
|
|
||||||
"Layer": [{
|
|
||||||
"name": "t"
|
|
||||||
}]
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
#t {
|
|
||||||
text-name: valid;
|
|
||||||
text-face-name: 2;
|
|
||||||
}
|
|
@ -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
Loading…
Reference in New Issue
Block a user