diff --git a/examples/sld.html b/examples/sld.html
index 0a23159f2d..f57b9804bd 100644
--- a/examples/sld.html
+++ b/examples/sld.html
@@ -6,89 +6,114 @@
width: 800px;
height: 475px;
border: 1px solid black;
+ background: #ccddff;
}
-
+
This example uses a SLD
- file to style the vector features. The style to be used is either
- determined by the NamedLayer and IsDefault properties in the sld file, or
- can directly be applied by addressing a style from the styles
- hash with the UserStyle name from the sld file as key. Select a new style for the WaterBodies layer below:
+ file to style the vector features. To construct layers that use styles
+ from SLD, create a StyleMap for the layer that uses one of the userStyles in the
+ namedLayers object of the return from format.read().
+ Select a new style for the WaterBodies layer below:
diff --git a/examples/tasmania/sld-tasmania.xml b/examples/tasmania/sld-tasmania.xml
index 1ff20afa1c..ce3e3e5538 100644
--- a/examples/tasmania/sld-tasmania.xml
+++ b/examples/tasmania/sld-tasmania.xml
@@ -1,5 +1,11 @@
-
+
WaterBodies
@@ -55,7 +61,26 @@
testRuleNameElse
title
Abstract
-
+
+
+
+ #aaaaff
+
+ 0.5
+
+
+
+
+ #C0C0C0
+
+
+ 1
+
+
+ 1
+
+
+
@@ -123,44 +148,35 @@
testRuleNameHoverElse
title
Abstract
-
-
-
-
-
-
-
-
- Attribute Filter Styler
- Attribute Filter Styler
-
- attribute filter type
- attribute filter type
- Feature
- generic:geometry
-
-
- rulePropertyIsEqualTo
- rulePropertyIsEqualTo
- rulePropertyIsEqualTo
-
-
- name
- My simple Polygon
-
-
+
- #000033
+ black
+
+
+ 0.5
+
+
+ fuchsia
+
+
+ 0.5
+
+
+ 5
+
+
+ 0
+
+
-
Attribute Filter Styler
Attribute Filter Styler
@@ -220,39 +236,7 @@
-
-
- Styler Test Not FeatureId
- Styler Test Not FeatureId
-
- attribute filter type
- attribute filter type
- Feature
- generic:geometry
-
-
- ruleNotFeatureId
- ruleNotFeatureId
- ruleNotFeatureId
-
-
-
-
-
-
-
-
- red
-
-
-
-
-
-
-
-
-
-
+
Styler Test WATER_TYPE
Styler Test WATER_TYPE
@@ -429,7 +413,7 @@
-
+
Styler Test PropertyIsLike
Styler Test PropertyIsLike
@@ -460,7 +444,6 @@
-
Styler Test PropertyIsBetween
Styler Test PropertyIsBetween
@@ -478,10 +461,10 @@
AREA
- 1060000000
+ 1064866676
- 1070000000
+ 1065512599
@@ -496,6 +479,23 @@
+
+ FeatureId
+ Styler Test FeatureId
+
+
+
+
+
+
+
+ blue
+
+
+
+
+
+
@@ -503,19 +503,21 @@
RoadsDefault
1
-
- justAStyler
-
-
-
- red
-
-
- 2
-
-
-
-
+
+
+ justAStyler
+
+
+
+ red
+
+
+ 2
+
+
+
+
+
@@ -524,19 +526,52 @@
DefaultCities
1
-
-
-
-
-
- image/png
-
- 10
- 0.5
-
-
-
+
+
+
+
+
+
+ image/png
+
+ 0.5
+ 10
+
+
+
+
+
+ Land
+
+ Land Style
+ 1
+
+
+
+
+ #ccffaa
+
+ 0.5
+
+
+
+
+ #C0C0C0
+
+
+ 1
+
+
+ 1
+
+
+
+
+
+
+
+
diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js
index 7de7d4bcd7..c29021b67c 100644
--- a/lib/OpenLayers.js
+++ b/lib/OpenLayers.js
@@ -193,6 +193,8 @@
"OpenLayers/Format/WKT.js",
"OpenLayers/Format/OSM.js",
"OpenLayers/Format/SLD.js",
+ "OpenLayers/Format/SLD/v1.js",
+ "OpenLayers/Format/SLD/v1_0_0.js",
"OpenLayers/Format/Text.js",
"OpenLayers/Format/JSON.js",
"OpenLayers/Format/GeoJSON.js",
diff --git a/lib/OpenLayers/Format/SLD.js b/lib/OpenLayers/Format/SLD.js
index db12e335be..eff1adff34 100644
--- a/lib/OpenLayers/Format/SLD.js
+++ b/lib/OpenLayers/Format/SLD.js
@@ -21,59 +21,23 @@
OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML, {
/**
- * APIProperty: sldns
- * Namespace used for sld.
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
*/
- sldns: "http://www.opengis.net/sld",
+ defaultVersion: "1.0.0",
/**
- * APIProperty: ogcns
- * Namespace used for ogc.
+ * APIProperty: version
+ * {String} Specify a version string if one is known.
*/
- ogcns: "http://www.opengis.net/ogc",
+ version: null,
/**
- * APIProperty: gmlns
- * Namespace used for gml.
+ * Property: parser
+ * {Object} Instance of the versioned parser. Cached for multiple read and
+ * write calls of the same version.
*/
- gmlns: "http://www.opengis.net/gml",
-
- /**
- * APIProperty: defaultStyle.
- * {Object}
- * A simple style, preset with the SLD defaults.
- */
- defaultStyle: {
- fillColor: "#808080",
- fillOpacity: 1,
- strokeColor: "#000000",
- strokeOpacity: 1,
- strokeWidth: 1,
- pointRadius: 6
- },
-
- /**
- * Property: withNamedLayer
- * {Boolean} Option set during . Default is false. If true, the
- * return from will be a two item array ([styles, namedLayer]):
- * - styles - {Array()}
- * - namedLayer - {Object} hash of userStyles, keyed by
- * sld:NamedLayer/Name, each again keyed by
- * sld:UserStyle/Name. Each entry of namedLayer is a
- * StyleMap for a layer, with the userStyle names as style
- * keys.
- */
- withNamedLayer: false,
-
- /**
- * APIProperty: overrideDefaultStyleKey
- * {Boolean} Store styles with key of "default" instead of user style name.
- * If true, userStyles with sld:IsDefault==1 will be stored with
- * key "default" instead of the sld:UserStyle/Name in the style map.
- * Default is true.
- */
- overrideDefaultStyleKey: true,
-
+ parser: null,
/**
* Constructor: OpenLayers.Format.SLD
@@ -88,517 +52,68 @@ OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML, {
},
/**
- * APIMethod: read
- * Read data from a string, and return a list of features.
- *
+ * APIMethod: write
+ * Write a SLD document given a list of styles.
+ *
* Parameters:
- * data - {String} or {XMLNode} data to read/parse.
- * options - {Object} Object that sets optional read configuration values.
- * These include , and .
+ * sld - {Object} An object representing the SLD.
+ * options - {Object} Optional configuration object.
*
* Returns:
- * {Array()} List of styles. If is
- * true, return will be a two item array where the first item is
- * a list of styles and the second is the namedLayer object.
+ * {String} An SLD document string.
*/
- read: function(data, options) {
- if (typeof data == "string") {
+ write: function(sld, options) {
+ var version = (options && options.version) ||
+ this.version || this.defaultVersion;
+ if(!this.parser || this.parser.VERSION != version) {
+ var format = OpenLayers.Format.SLD[
+ "v" + version.replace(/\./g, "_")
+ ];
+ if(!format) {
+ throw "Can't find a SLD parser for version " +
+ version;
+ }
+ this.parser = new format(this.options);
+ }
+ var root = this.parser.write(sld);
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+ /**
+ * APIMethod: read
+ * Read and SLD doc and return an object representing the SLD.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ *
+ * Returns:
+ * {Object} An object representing the SLD.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
}
-
- options = options || {};
- OpenLayers.Util.applyDefaults(options, {
- withNamedLayer: false,
- overrideDefaultStyleKey: true
- });
-
- var userStyles = this.getElementsByTagNameNS(
- data, this.sldns, "UserStyle"
- );
- var result = {};
- if (userStyles.length > 0) {
- var namedLayer = {};
- var styles = new Array(userStyles.length);
- var styleName, userStyle, style;
- for (var i=0; i}
- */
- parseUserStyle: function(xmlNode, name) {
- var userStyle = new OpenLayers.Style(this.defaultStyle, {name: name});
-
- userStyle.isDefault = (
- this.parseProperty(xmlNode, this.sldns, "IsDefault") == 1
- );
-
- // get the name of the layer if we have a NamedLayer
- var namedLayerNode = xmlNode.parentNode;
- var nameNodes = this.getElementsByTagNameNS(
- namedLayerNode, this.sldns, "Name"
- );
- if (namedLayerNode.nodeName.indexOf("NamedLayer") != -1 &&
- nameNodes &&
- nameNodes.length > 0 &&
- nameNodes[0].parentNode == namedLayerNode) {
- userStyle.layerName = this.getChildValue(nameNodes[0]);
- }
-
- var ruleNodes = this.getElementsByTagNameNS(
- xmlNode, this.sldns, "Rule"
- );
-
- if (ruleNodes.length > 0) {
- var rules = userStyle.rules;
- var ruleName;
- for (var i=0; i}
- *
- * Returns:
- * {Object} Hash of rule properties
- */
- parseRule: function(xmlNode, name) {
-
- // FILTERS
-
- var filter = this.getElementsByTagNameNS(xmlNode, this.ogcns, "Filter");
- if (filter && filter.length > 0) {
- var rule = this.parseFilter(filter[0]);
- } else {
- // start with an empty rule that always applies
- var rule = new OpenLayers.Rule();
- // and check if the rule is an ElseFilter
- var elseFilter = this.getElementsByTagNameNS(xmlNode, this.ogcns,
- "ElseFilter");
- if (elseFilter && elseFilter.length > 0) {
- rule.elseFilter = true;
- }
- }
-
- rule.name = name;
-
- // SCALE DENOMINATORS
-
- // MinScaleDenominator
- var minScale = this.getElementsByTagNameNS(
- xmlNode, this.sldns, "MinScaleDenominator"
- );
- if (minScale && minScale.length > 0) {
- rule.minScaleDenominator =
- parseFloat(this.getChildValue(minScale[0]));
- }
-
- // MaxScaleDenominator
- var maxScale = this.getElementsByTagNameNS(
- xmlNode, this.sldns, "MaxScaleDenominator"
- );
- if (maxScale && maxScale.length > 0) {
- rule.maxScaleDenominator =
- parseFloat(this.getChildValue(maxScale[0]));
- }
-
- // STYLES
-
- // walk through all symbolizers
- var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES;
- for (var s=0; s 0) {
-
- var style = {};
-
- // externalGraphic
- var graphic = this.getElementsByTagNameNS(
- symbolizer[0], this.sldns, "Graphic"
- );
- if (graphic && graphic.length > 0) {
- style.externalGraphic = this.parseProperty(
- graphic[0], this.sldns, "OnlineResource", "xlink:href"
- );
- style.pointRadius = this.parseProperty(
- graphic[0], this.sldns, "Size"
- );
- style.graphicOpacity = this.parseProperty(
- graphic[0], this.sldns, "Opacity"
- );
- }
-
- // fill
- var fill = this.getElementsByTagNameNS(
- symbolizer[0], this.sldns, "Fill"
- );
- if (fill && fill.length > 0) {
- style.fillColor = this.parseProperty(
- fill[0], this.sldns, "CssParameter", "name", "fill"
- );
- style.fillOpacity = this.parseProperty(
- fill[0], this.sldns, "CssParameter",
- "name", "fill-opacity"
- ) || 1;
- }
-
- // stroke
- var stroke = this.getElementsByTagNameNS(
- symbolizer[0], this.sldns, "Stroke"
- );
- if (stroke && stroke.length > 0) {
- style.strokeColor = this.parseProperty(
- stroke[0], this.sldns, "CssParameter", "name", "stroke"
- );
- style.strokeOpacity = this.parseProperty(
- stroke[0], this.sldns, "CssParameter",
- "name", "stroke-opacity"
- ) || 1;
- style.strokeWidth = this.parseProperty(
- stroke[0], this.sldns, "CssParameter",
- "name", "stroke-width"
- );
- style.strokeLinecap = this.parseProperty(
- stroke[0], this.sldns, "CssParameter",
- "name", "stroke-linecap"
- );
- }
-
- // set the [point|line|polygon]Symbolizer property of the rule
- rule.symbolizer[prefixes[s]] = style;
- }
- }
-
- return rule;
- },
-
- /**
- * Method: parseFilter
- * Parses ogc fiters.
- *
- * Parameters:
- * xmlNode - {}
- *
- * Returns:
- * {} rule representing the filter
- */
- parseFilter: function(xmlNode) {
- // ogc:FeatureId filter
- var filter = this.getNodeOrChildrenByTagName(xmlNode, "FeatureId");
- if (filter) {
- var rule = new OpenLayers.Rule.FeatureId();
- for (var i=0; i)} or null if no matching content is found
- */
- getNodeOrChildrenByTagName: function(xmlNode, tagName) {
- var nodeName = (xmlNode.prefix) ?
- xmlNode.nodeName.split(":")[1] :
- xmlNode.nodeName;
-
- if (nodeName == tagName) {
- return [xmlNode];
- } else {
- var nodelist = this.getElementsByTagNameNS(
- xmlNode, this.ogcns, tagName);
- }
-
- // make a new list which only contains matching child nodes
- if (nodelist.length > 0) {
- var node;
- var list = [];
- for (var i=0; i 0 ? list : null;
- }
-
- return null;
- },
-
- /**
- * Method: parseProperty
- * Convenience method to parse the different kinds of properties
- * found in the sld and ogc namespace.
- *
- * Parses an ogc node that can either contain a value directly,
- * or inside a property. The parsing can also be limited
- * to nodes with certain attribute names and/or values.
- *
- * Parameters:
- * xmlNode - {}
- * namespace - {String} namespace of the node to find
- * propertyName - {String} name of the property to parse
- * attributeName - {String} optional name of the property to match
- * attributeValue - {String} optional value of the specified attribute
- *
- * Returns:
- * {String} The value for the requested property.
- */
- parseProperty: function(xmlNode, namespace, propertyName, attributeName,
- attributeValue) {
- var result = null;
- var propertyNodeList = this.getElementsByTagNameNS(
- xmlNode, namespace, propertyName);
-
- if (propertyNodeList && propertyNodeList.length > 0) {
- var propertyNode = attributeName ?
- this.getNodeWithAttribute(propertyNodeList,
- attributeName) :
- propertyNodeList[0];
-
- // strip namespace from attribute name for Opera browsers
- if (window.opera && attributeName) {
- var nsDelimiterPos = attributeName.indexOf(":");
- if (nsDelimiterPos != -1) {
- attributeName = attributeName.substring(++nsDelimiterPos);
- }
- }
-
- // get the property value from the node matching attributeName
- // and attributeValue, eg.:
- //
- // red
- //
- // or:
- // red
- if (attributeName && attributeValue) {
- propertyNode = this.getNodeWithAttribute(propertyNodeList,
- attributeName, attributeValue);
- result = this.parseParameter(propertyNode);
- }
-
- // get the attribute value and use it as result, eg.:
- //
- if (attributeName && !attributeValue) {
- var propertyNode = this.getNodeWithAttribute(propertyNodeList,
- attributeName);
- result = propertyNode.getAttribute(attributeName);
- }
-
- // get the property value directly or from an ogc:propertyName,
- // ogc:Literal or any other property at the level of the property
- // node, eg.:
- // 0.5
- if (!attributeName) {
- var result = this.parseParameter(propertyNode);
- }
- }
-
- // adjust the result to be a trimmed string or a number
- if (result) {
- result = OpenLayers.String.trim(result);
- if (!isNaN(result)) {
- result = parseFloat(result);
- }
- }
-
- return result;
- },
-
- /**
- * Method: parseParameter
- * parses a property for propertyNames, Literals and textContent and
- * creates the according value string.
- *
- * Parameters:
- * xmlNode - {}
- *
- * Returns:
- * {String} a string holding a value suitable for OpenLayers.Style.value
- */
- parseParameter: function(xmlNode) {
- if (!xmlNode) {
- return null;
- }
- var childNodes = xmlNode.childNodes;
- if (!childNodes) {
- return null;
- }
-
- var value = new Array(childNodes.length);
- for (var i=0; i)} list to search
- * attributeName - {String} name of the attribute to match
- * attributeValue - {String} optional value of the attribute
- */
- getNodeWithAttribute: function(xmlNodeList, attributeName, attributeValue) {
- for (var i=0; i
+ */
+OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ sld: "http://www.opengis.net/sld",
+ ogc: "http://www.opengis.net/ogc",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "sld",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: null,
+
+ /**
+ * APIProperty: defaultSymbolizer.
+ * {Object} A symbolizer with the SLD defaults.
+ */
+ defaultSymbolizer: {
+ fillColor: "#808080",
+ fillOpacity: 1,
+ strokeColor: "#000000",
+ strokeOpacity: 1,
+ strokeWidth: 1,
+ pointRadius: 6
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.SLD.v1
+ * Instances of this class are not created directly. Use the
+ * constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement} An SLD document element.
+ *
+ * Returns:
+ * {Object} An object representing the SLD.
+ */
+ read: function(data) {
+ var sld = {
+ namedLayers: {}
+ };
+ this.readChildNodes(data, sld);
+ return sld;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "sld": {
+ "StyledLayerDescriptor": function(node, sld) {
+ sld.version = node.getAttribute("version");
+ this.readChildNodes(node, sld);
+ },
+ "Name": function(node, obj) {
+ obj.name = this.getChildValue(node);
+ },
+ "Title": function(node, obj) {
+ obj.title = this.getChildValue(node);
+ },
+ "Abstract": function(node, obj) {
+ obj.description = this.getChildValue(node);
+ },
+ "NamedLayer": function(node, sld) {
+ var layer = {
+ userStyles: [],
+ namedStyles: []
+ };
+ this.readChildNodes(node, layer);
+ // give each of the user styles this layer name
+ for(var i=0; i 0) {
+ rule.rules.push(new OpenLayers.Rule.FeatureId({
+ fids: filter.fids
+ }));
+ }
+ if(filter.rules.length > 0) {
+ rule.rules = rule.rules.concat(filter.rules);
+ }
+ },
+ "FeatureId": function(node, filter) {
+ var fid = node.getAttribute("fid");
+ if(fid) {
+ filter.fids.push(fid);
+ }
+ },
+ "And": function(node, filter) {
+ var rule = new OpenLayers.Rule.Logical({
+ type: OpenLayers.Rule.Logical.AND
+ });
+ // since FeatureId rules may be nested here, make room for them
+ rule.fids = [];
+ this.readChildNodes(node, rule);
+ if(rule.fids.length > 0) {
+ rule.rules.push(new OpenLayers.Rule.FeatureId({
+ fids: rule.fids
+ }));
+ }
+ delete rule.fids;
+ filter.rules.push(rule);
+ },
+ "Or": function(node, filter) {
+ var rule = new OpenLayers.Rule.Logical({
+ type: OpenLayers.Rule.Logical.OR
+ });
+ // since FeatureId rules may be nested here, make room for them
+ rule.fids = [];
+ this.readChildNodes(node, rule);
+ if(rule.fids.length > 0) {
+ rule.rules.push(new OpenLayers.Rule.FeatureId({
+ fids: rule.fids
+ }));
+ }
+ delete rule.fids;
+ filter.rules.push(rule);
+ },
+ "Not": function(node, filter) {
+ var rule = new OpenLayers.Rule.Logical({
+ type: OpenLayers.Rule.Logical.NOT
+ });
+ // since FeatureId rules may be nested here, make room for them
+ rule.fids = [];
+ this.readChildNodes(node, rule);
+ if(rule.fids.length > 0) {
+ rule.rules.push(new OpenLayers.Rule.FeatureId({
+ fids: rule.fids
+ }));
+ }
+ delete rule.fids;
+ filter.rules.push(rule);
+ },
+ "PropertyIsEqualTo": function(node, filter) {
+ var rule = new OpenLayers.Rule.Comparison({
+ type: OpenLayers.Rule.Comparison.EQUAL_TO
+ });
+ this.readChildNodes(node, rule);
+ filter.rules.push(rule);
+ },
+ "PropertyIsNotEqualTo": function(node, filter) {
+ var rule = new OpenLayers.Rule.Comparison({
+ type: OpenLayers.Rule.Comparison.NOT_EQUAL_TO
+ });
+ this.readChildNodes(node, rule);
+ filter.rules.push(rule);
+ },
+ "PropertyIsLessThan": function(node, filter) {
+ var rule = new OpenLayers.Rule.Comparison({
+ type: OpenLayers.Rule.Comparison.LESS_THAN
+ });
+ this.readChildNodes(node, rule);
+ filter.rules.push(rule);
+ },
+ "PropertyIsGreaterThan": function(node, filter) {
+ var rule = new OpenLayers.Rule.Comparison({
+ type: OpenLayers.Rule.Comparison.GREATER_THAN
+ });
+ this.readChildNodes(node, rule);
+ filter.rules.push(rule);
+ },
+ "PropertyIsLessThanOrEqualTo": function(node, filter) {
+ var rule = new OpenLayers.Rule.Comparison({
+ type: OpenLayers.Rule.Comparison.LESS_THAN_OR_EQUAL_TO
+ });
+ this.readChildNodes(node, rule);
+ filter.rules.push(rule);
+ },
+ "PropertyIsGreaterThanOrEqualTo": function(node, filter) {
+ var rule = new OpenLayers.Rule.Comparison({
+ type: OpenLayers.Rule.Comparison.GREATER_THAN_OR_EQUAL_TO
+ });
+ this.readChildNodes(node, rule);
+ filter.rules.push(rule);
+ },
+ "PropertyIsBetween": function(node, filter) {
+ var rule = new OpenLayers.Rule.Comparison({
+ type: OpenLayers.Rule.Comparison.BETWEEN
+ });
+ this.readChildNodes(node, rule);
+ filter.rules.push(rule);
+ },
+ "PropertyIsLike": function(node, filter) {
+ var rule = new OpenLayers.Rule.Comparison({
+ type: OpenLayers.Rule.Comparison.LIKE
+ });
+ this.readChildNodes(node, rule);
+ var wildCard = node.getAttribute("wildCard");
+ var singleChar = node.getAttribute("singleChar");
+ var esc = node.getAttribute("escape");
+ rule.value2regex(wildCard, singleChar, esc);
+ filter.rules.push(rule);
+ },
+ "Literal": function(node, obj) {
+ obj.value = this.getChildValue(node);
+ },
+ "PropertyName": function(node, rule) {
+ rule.property = this.getChildValue(node);
+ },
+ "LowerBoundary": function(node, rule) {
+ rule.lowerBoundary = this.readOgcExpression(node);
+ },
+ "UpperBoundary": function(node, rule) {
+ rule.upperBoundary = this.readOgcExpression(node);
+ }
+ }
+ },
+
+ /**
+ * Method: readOgcExpression
+ * Limited support for OGC expressions.
+ *
+ * Parameters:
+ * node - {DOMElement} A DOM element that contains an ogc:expression.
+ *
+ * Returns:
+ * {String} A value to be used in a symbolizer.
+ */
+ readOgcExpression: function(node) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ var value = obj.value;
+ if(!value) {
+ value = this.getChildValue(node);
+ }
+ return value;
+ },
+
+ /**
+ * Property: cssMap
+ * {Object} Object mapping supported css property names to OpenLayers
+ * symbolizer property names.
+ */
+ cssMap: {
+ "stroke": "strokeColor",
+ "stroke-opacity": "strokeOpacity",
+ "stroke-width": "strokeWidth",
+ "stroke-linecap": "strokeLinecap",
+ "fill": "fillColor",
+ "fill-opacity": "fillOpacity"
+ },
+
+ /**
+ * Method: getCssProperty
+ * Given a symbolizer property, get the corresponding CSS property
+ * from the .
+ *
+ * Parameters:
+ * sym - {String} A symbolizer property name.
+ *
+ * Returns:
+ * {String} A CSS property name or null if none found.
+ */
+ getCssProperty: function(sym) {
+ var css = null;
+ for(var prop in this.cssMap) {
+ if(this.cssMap[prop] == sym) {
+ css = prop;
+ break
+ }
+ }
+ return css;
+ },
+
+ /**
+ * Method: getGraphicFormat
+ * Given a href for an external graphic, try to determine the mime-type.
+ * This method doesn't try too hard, and will fall back to
+ * if one of the known is not
+ * the file extension of the provided href.
+ *
+ * Parameters:
+ * href - {String}
+ *
+ * Returns:
+ * {String} The graphic format.
+ */
+ getGraphicFormat: function(href) {
+ var format, regex;
+ for(var key in this.graphicFormats) {
+ if(this.graphicFormats[key].test(href)) {
+ format = key;
+ break
+ }
+ }
+ return format || this.defautlGraphicFormat;
+ },
+
+ /**
+ * Property: defaultGraphicFormat
+ * {String} If none other can be determined from , this
+ * default will be returned.
+ */
+ defaultGraphicFormat: "image/png",
+
+ /**
+ * Property: graphicFormats
+ * {Object} Mapping of image mime-types to regular extensions matching
+ * well-known file extensions.
+ */
+ graphicFormats: {
+ "image/jpeg": /\.jpe?g$/i,
+ "image/gif": /\.gif$/i,
+ "image/png": /\.png$/i
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * sld - {Object} An object representing the SLD.
+ *
+ * Returns:
+ * {DOMElement} The root of an SLD document.
+ */
+ write: function(sld) {
+ return this.writers.sld.StyledLayerDescriptor.apply(this, [sld])
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "sld": {
+ "StyledLayerDescriptor": function(sld) {
+ var root = this.createElementNSPlus(
+ "StyledLayerDescriptor",
+ {attributes: {
+ "version": this.VERSION,
+ "xsi:schemaLocation": this.schemaLocation
+ }}
+ );
+ // add in optional name
+ if(sld.name) {
+ this.writeNode(root, "Name", sld.name);
+ }
+ // add in optional title
+ if(sld.title) {
+ this.writeNode(root, "Title", sld.title);
+ }
+ // add in optional description
+ if(sld.description) {
+ this.writeNode(root, "Abstract", sld.description);
+ }
+ // add in named layers
+ for(var name in sld.namedLayers) {
+ this.writeNode(root, "NamedLayer", sld.namedLayers[name]);
+ }
+ return root;
+ },
+ "Name": function(name) {
+ return this.createElementNSPlus("Name", {value: name});
+ },
+ "Title": function(title) {
+ return this.createElementNSPlus("Title", {value: title});
+ },
+ "Abstract": function(description) {
+ return this.createElementNSPlus(
+ "Abstract", {value: description}
+ );
+ },
+ "NamedLayer": function(layer) {
+ var node = this.createElementNSPlus("NamedLayer");
+
+ // add in required name
+ this.writeNode(node, "Name", layer.name);
+
+ // optional sld:LayerFeatureConstraints here
+
+ // add in named styles
+ if(layer.namedStyles) {
+ for(var i=0; i": "PropertyIsGreaterThan",
+ "<=": "PropertyIsLessThanOrEqualTo",
+ ">=": "PropertyIsGreaterThanOrEqualTo",
+ "..": "PropertyIsBetween",
+ "~": "PropertyIsLike"
+ },
+
+
+ /**
+ * Methods below this point are of general use for versioned XML parsers.
+ * These are candidates for an abstract class.
+ */
+
+ /**
+ * Method: getNamespacePrefix
+ * Get the namespace prefix for a given uri from the object.
+ *
+ * Returns:
+ * {String} A namespace prefix or null if none found.
+ */
+ getNamespacePrefix: function(uri) {
+ var prefix = null;
+ if(uri == null) {
+ prefix = this.namespaces[this.defaultPrefix];
+ } else {
+ var gotPrefix = false;
+ for(prefix in this.namespaces) {
+ if(this.namespaces[prefix] == uri) {
+ gotPrefix = true;
+ break;
+ }
+ }
+ if(!gotPrefix) {
+ prefix = null;
+ }
+ }
+ return prefix;
+ },
+
+
+ /**
+ * Method: readChildNodes
+ */
+ readChildNodes: function(node, obj) {
+ var children = node.childNodes;
+ var child, group, reader, prefix, local;
+ for(var i=0; i group. If a local name is used (e.g. "Name") then
+ * the namespace of the parent is assumed.
+ * obj - {Object} Structure containing data for the writer.
+ *
+ * Returns:
+ * {DOMElement} The child node.
+ */
+ writeNode: function(parent, name, obj) {
+ var prefix, local;
+ var split = name.indexOf(":");
+ if(split > 0) {
+ prefix = name.substring(0, split);
+ local = name.substring(split + 1);
+ } else {
+ prefix = this.getNamespacePrefix(parent.namespaceURI);
+ local = name;
+ }
+ var child = this.writers[prefix][local].apply(this, [obj]);
+ parent.appendChild(child);
+ return child;
+ },
+
+ /**
+ * Method: createElementNSPlus
+ * Shorthand for creating namespaced elements with optional attributes and
+ * child text nodes.
+ *
+ * Parameters:
+ * name - {String} The qualified node name.
+ * options - {Object} Optional object for node configuration.
+ *
+ * Returns:
+ * {Element} An element node.
+ */
+ createElementNSPlus: function(name, options) {
+ options = options || {};
+ var loc = name.indexOf(":");
+ // order of prefix preference
+ // 1. in the uri option
+ // 2. in the prefix option
+ // 3. in the qualified name
+ // 4. from the defaultPrefix
+ var uri = options.uri || this.namespaces[options.prefix]
+ if(!uri) {
+ loc = name.indexOf(":");
+ uri = this.namespaces[name.substring(0, loc)];
+ }
+ if(!uri) {
+ uri = this.namespaces[this.defaultPrefix];
+ }
+ var node = this.createElementNS(uri, name);
+ if(options.attributes) {
+ this.setAttributes(node, options.attributes);
+ }
+ if(options.value) {
+ node.appendChild(this.createTextNode(options.value));
+ }
+ return node;
+ },
+
+ /**
+ * Method: setAttributes
+ * Set multiple attributes given key value pairs from an object.
+ *
+ * Parameters:
+ * node - {Element} An element node.
+ * obj - {Object || Array} An object whose properties represent attribute
+ * names and values represent attribute values. If an attribute name
+ * is a qualified name ("prefix:local"), the prefix will be looked up
+ * in the parsers {namespaces} object. If the prefix is found,
+ * setAttributeNS will be used instead of setAttribute.
+ */
+ setAttributes: function(node, obj) {
+ var value, loc, alias, uri;
+ for(var name in obj) {
+ value = obj[name].toString();
+ // check for qualified attribute name ("prefix:local")
+ uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null;
+ this.setAttributeNS(node, uri, name, value);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.SLD.v1"
+
+});
diff --git a/lib/OpenLayers/Format/SLD/v1_0_0.js b/lib/OpenLayers/Format/SLD/v1_0_0.js
new file mode 100644
index 0000000000..cba89936af
--- /dev/null
+++ b/lib/OpenLayers/Format/SLD/v1_0_0.js
@@ -0,0 +1,49 @@
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/SLD/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD.v1_0_0
+ * Write SLD version 1.0.0.
+ *
+ * Inherits from:
+ * -
+ */
+OpenLayers.Format.SLD.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.SLD.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/sld
+ * http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd
+ */
+ schemaLocation: "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.SLD.v1_0_0
+ * Instances of this class are not created directly. Use the
+ * constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.SLD.v1.prototype.initialize.apply(
+ this, [options]
+ );
+ },
+
+ CLASS_NAME: "OpenLayers.Format.SLD.v1_0_0"
+
+});
\ No newline at end of file
diff --git a/lib/OpenLayers/Rule.js b/lib/OpenLayers/Rule.js
index 439de1be16..26ab152d9a 100644
--- a/lib/OpenLayers/Rule.js
+++ b/lib/OpenLayers/Rule.js
@@ -14,12 +14,30 @@
*/
OpenLayers.Rule = OpenLayers.Class({
+ /**
+ * Property: id
+ * {String} A unique id for this session.
+ */
+ id: null,
+
/**
* APIProperty: name
* {String} name of this rule
*/
name: 'default',
+ /**
+ * Property: title
+ * {String} Title of this rule (set if included in SLD)
+ */
+ title: null,
+
+ /**
+ * Property: description
+ * {String} Description of this rule (set if abstract is included in SLD)
+ */
+ description: null,
+
/**
* Property: context
* {Object} An optional object with properties that the rule should be
@@ -73,6 +91,7 @@ OpenLayers.Rule = OpenLayers.Class({
* {}
*/
initialize: function(options) {
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
this.symbolizer = {};
OpenLayers.Util.extend(this, options);
diff --git a/lib/OpenLayers/Rule/Comparison.js b/lib/OpenLayers/Rule/Comparison.js
index 431010f2aa..334fe3b4b3 100644
--- a/lib/OpenLayers/Rule/Comparison.js
+++ b/lib/OpenLayers/Rule/Comparison.js
@@ -107,9 +107,9 @@ OpenLayers.Rule.Comparison = OpenLayers.Class(OpenLayers.Rule, {
case OpenLayers.Rule.Comparison.BETWEEN:
var result =
- context[this.property] > this.lowerBoundary;
+ context[this.property] >= this.lowerBoundary;
result = result &&
- context[this.property] < this.upperBoundary;
+ context[this.property] <= this.upperBoundary;
return result;
case OpenLayers.Rule.Comparison.LIKE:
var regexp = new RegExp(this.value,
@@ -163,6 +163,41 @@ OpenLayers.Rule.Comparison = OpenLayers.Class(OpenLayers.Rule, {
return this.value;
},
+ /**
+ * Method: regex2value
+ * Convert the value of this rule from a regular expression string into an
+ * ogc literal string using a wildCard of *, a singleChar of ., and an
+ * escape of !. Leaves the property unmodified.
+ *
+ * Returns:
+ * {String} A string value.
+ */
+ regex2value: function() {
+
+ var value = this.value;
+
+ // replace ! with !!
+ value = value.replace(/!/g, "!!");
+
+ // replace \. with !. (watching out for \\.)
+ value = value.replace(/(\\)?\\\./g, function($0, $1) {
+ return $1 ? $0 : "!.";
+ });
+
+ // replace \* with #* (watching out for \\*)
+ value = value.replace(/(\\)?\\\*/g, function($0, $1) {
+ return $1 ? $0 : "!*";
+ });
+
+ // replace \\ with \
+ value = value.replace(/\\\\/g, "\\");
+
+ // convert .* to * (the sequence #.* is not allowed)
+ value = value.replace(/\.\*/g, "*");
+
+ return value;
+ },
+
/**
* Function: binaryCompare
* Compares a feature property to a rule value
diff --git a/lib/OpenLayers/Style.js b/lib/OpenLayers/Style.js
index 6750ec693d..28b9bb6fc9 100644
--- a/lib/OpenLayers/Style.js
+++ b/lib/OpenLayers/Style.js
@@ -21,6 +21,18 @@ OpenLayers.Style = OpenLayers.Class({
*/
name: null,
+ /**
+ * Property: title
+ * {String} Title of this style (set if included in SLD)
+ */
+ title: null,
+
+ /**
+ * Property: description
+ * {String} Description of this style (set if abstract is included in SLD)
+ */
+ description: null,
+
/**
* APIProperty: layerName
* {} name of the layer that this style belongs to, usually
diff --git a/tests/Format/test_SLD.html b/tests/Format/test_SLD.html
index f6b94d8d70..d63798b31d 100644
--- a/tests/Format/test_SLD.html
+++ b/tests/Format/test_SLD.html
@@ -17,17 +17,16 @@
}
function test_Format_SLD_read(t) {
- t.plan(5);
- var styles = new OpenLayers.Format.SLD().read(this.test_content,
- {withNamedLayer: true});
+ t.plan(4);
+ var sld = new OpenLayers.Format.SLD().read(this.test_content);
- var testLayer = styles[1].TestLayer;
+ var testLayer = sld.namedLayers["TestLayer"];
+ var userStyles = testLayer.userStyles;
- t.ok(testLayer.foo != undefined, "SLD correctly reads a UserStyle named \"foo\"");
- t.eq(testLayer.foo.rules.length, 1, "The number of rules for the UserStyle is correct");
- t.eq(testLayer.foo.rules[0].name, "bar", "The first rule's name is \"bar\"");
- t.eq(testLayer.foo.rules[0].symbolizer.Polygon.fillColor, "blue", "The fillColor for the Polygon symbolizer is correct");
- t.eq(testLayer.foo.name, styles[0][0].name, "The content hash of the Format contains the correct rules.");
+ t.eq(userStyles[0].name, "foo", "SLD correctly reads a UserStyle named 'foo'");
+ t.eq(userStyles[0].rules.length, 1, "The number of rules for the UserStyle is correct");
+ t.eq(userStyles[0].rules[0].name, "bar", "The first rule's name is 'bar'");
+ t.eq(userStyles[0].rules[0].symbolizer.Polygon.fillColor, "blue", "The fillColor for the Polygon symbolizer is correct");
}
diff --git a/tests/Rule/test_Comparison.html b/tests/Rule/test_Comparison.html
index 018f281c4c..62af049673 100644
--- a/tests/Rule/test_Comparison.html
+++ b/tests/Rule/test_Comparison.html
@@ -37,26 +37,50 @@
t.eq(rule.value, ".*b.r\\%\\..*", "Regular expression with different wildcard and escape chars generated correctly.");
}
+ function test_regex2value(t) {
+ t.plan(8);
+
+ function r2v(regex) {
+ return OpenLayers.Rule.Comparison.prototype.regex2value.call(
+ {value: regex}
+ );
+ }
+
+ t.eq(r2v("foo"), "foo", "doesn't change string without special chars");
+ t.eq(r2v("foo.*foo"), "foo*foo", "wildCard replaced");
+ t.eq(r2v("foo.foo"), "foo.foo", "singleChar replaced");
+ t.eq(r2v("foo\\\\foo"), "foo\\foo", "escape removed");
+ t.eq(r2v("foo!foo"), "foo!!foo", "escapes !");
+ t.eq(r2v("foo\\*foo"), "foo!*foo", "replaces escape on *");
+ t.eq(r2v("foo\\.foo"), "foo!.foo", "replaces escape on .");
+ t.eq(r2v("foo\\\\.foo"), "foo\\.foo", "unescapes only \\ before .");
+
+ }
+
function test_Comparison_evaluate(t) {
- t.plan(4);
+ t.plan(5);
var rule = new OpenLayers.Rule.Comparison({
property: "area",
lowerBoundary: 1000,
- upperBoundary: 5000,
+ upperBoundary: 4999,
type: OpenLayers.Rule.Comparison.BETWEEN});
var features = [
new OpenLayers.Feature.Vector(null, {
- area: 2000}),
+ area: 999}),
new OpenLayers.Feature.Vector(null, {
- area: 6000}),
+ area: 1000}),
new OpenLayers.Feature.Vector(null, {
- area: 4999})];
+ area: 4999}),
+ new OpenLayers.Feature.Vector(null, {
+ area: 5000})];
+ // PropertyIsBetween filter: lower and upper boundary are inclusive
var ruleResults = {
- 0: true,
- 1: false,
- 2: true};
+ 0: false,
+ 1: true,
+ 2: true,
+ 3: false};
for (var i in ruleResults) {
var result = rule.evaluate(features[i]);
t.eq(result, ruleResults[i], "feature "+i+