diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index 21864c7148..fa7a798a5b 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -203,6 +203,10 @@ "OpenLayers/Format/SLD.js", "OpenLayers/Format/SLD/v1.js", "OpenLayers/Format/SLD/v1_0_0.js", + "OpenLayers/Format/SLD/v1.js", + "OpenLayers/Format/Filter.js", + "OpenLayers/Format/Filter/v1.js", + "OpenLayers/Format/Filter/v1_0_0.js", "OpenLayers/Format/Text.js", "OpenLayers/Format/JSON.js", "OpenLayers/Format/GeoJSON.js", diff --git a/lib/OpenLayers/Format/Filter.js b/lib/OpenLayers/Format/Filter.js new file mode 100644 index 0000000000..6dca428787 --- /dev/null +++ b/lib/OpenLayers/Format/Filter.js @@ -0,0 +1,115 @@ +/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license. + * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt + * for the full text of the license. */ + +/** + * @requires OpenLayers/Format/XML.js + * @requires OpenLayers/Filter/FeatureId.js + * @requires OpenLayers/Filter/Logical.js + * @requires OpenLayers/Filter/Comparison.js + */ + +/** + * Class: OpenLayers.Format.Filter + * Read/Wite ogc:Filter. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.Filter = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * APIProperty: defaultVersion + * {String} Version number to assume if none found. Default is "1.0.0". + */ + defaultVersion: "1.0.0", + + /** + * APIProperty: version + * {String} Specify a version string if one is known. + */ + version: null, + + /** + * Property: parser + * {Object} Instance of the versioned parser. Cached for multiple read and + * write calls of the same version. + */ + parser: null, + + /** + * Constructor: OpenLayers.Format.Filter + * Create a new parser for Filter. + * + * 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]); + }, + + /** + * APIMethod: write + * Write an ogc:Filter given a filter object. + * + * Parameters: + * filter - {} An filter. + * options - {Object} Optional configuration object. + * + * Returns: + * {Elment} An ogc:Filter element node. + */ + write: function(filter, options) { + var version = (options && options.version) || + this.version || this.defaultVersion; + if(!this.parser || this.parser.VERSION != version) { + var format = OpenLayers.Format.Filter[ + "v" + version.replace(/\./g, "_") + ]; + if(!format) { + throw "Can't find a Filter parser for version " + + version; + } + this.parser = new format(this.options); + } + return this.parser.write(filter); + //return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * APIMethod: read + * Read and Filter doc and return an object representing the Filter. + * + * Parameters: + * data - {String | DOMElement} Data to read. + * + * Returns: + * {} A filter object. + */ + read: function(data) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + var root = data.nodeType == 9 ? data.documentElement : data; + var version = this.version; + if(!version) { + version = this.defaultVersion; + } + if(!this.parser || this.parser.VERSION != version) { + var format = OpenLayers.Format.Filter[ + "v" + version.replace(/\./g, "_") + ]; + if(!format) { + throw "Can't find a Filter parser for version " + + version; + } + this.parser = new format(this.options); + } + var filter = this.parser.read(data); + return filter; + }, + + CLASS_NAME: "OpenLayers.Format.Filter" +}); diff --git a/lib/OpenLayers/Format/Filter/v1.js b/lib/OpenLayers/Format/Filter/v1.js new file mode 100644 index 0000000000..8d2b09666b --- /dev/null +++ b/lib/OpenLayers/Format/Filter/v1.js @@ -0,0 +1,585 @@ +/* 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/Filter.js + */ + +/** + * Class: OpenLayers.Format.Filter.v1 + * Superclass for Filter version 1 parsers. + * + * Inherits from: + * - + */ +OpenLayers.Format.Filter.v1 = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + ogc: "http://www.opengis.net/ogc", + xlink: "http://www.w3.org/1999/xlink", + xsi: "http://www.w3.org/2001/XMLSchema-instance" + }, + + /** + * Property: defaultPrefix + */ + defaultPrefix: "ogc", + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. + */ + schemaLocation: null, + + /** + * Constructor: OpenLayers.Format.Filter.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} A Filter document element. + * + * Returns: + * {} A filter object. + */ + read: function(data) { + var obj = {} + var filter = this.readers.ogc["Filter"].apply(this, [data, obj]); + return obj.filter; + }, + + /** + * 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: { + "ogc": { + "Filter": function(node, parent) { + // Filters correspond to subclasses of OpenLayers.Filter. + // Since they contain information we don't persist, we + // create a temporary object and then pass on the filter + // (ogc:Filter) to the parent obj. + var obj = { + fids: [], + filters: [] + }; + this.readChildNodes(node, obj); + if(obj.fids.length > 0) { + parent.filter = new OpenLayers.Filter.FeatureId({ + fids: obj.fids + }); + } else if(obj.filters.length > 0) { + parent.filter = obj.filters[0]; + } + }, + "FeatureId": function(node, obj) { + var fid = node.getAttribute("fid"); + if(fid) { + obj.fids.push(fid); + } + }, + "And": function(node, obj) { + var filter = new OpenLayers.Filter.Logical({ + type: OpenLayers.Filter.Logical.AND + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "Or": function(node, obj) { + var filter = new OpenLayers.Filter.Logical({ + type: OpenLayers.Filter.Logical.OR + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "Not": function(node, obj) { + var filter = new OpenLayers.Filter.Logical({ + type: OpenLayers.Filter.Logical.NOT + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsEqualTo": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.EQUAL_TO + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsNotEqualTo": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsLessThan": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.LESS_THAN + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsGreaterThan": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.GREATER_THAN + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsLessThanOrEqualTo": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsGreaterThanOrEqualTo": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsBetween": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.BETWEEN + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsLike": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.LIKE + }); + this.readChildNodes(node, filter); + var wildCard = node.getAttribute("wildCard"); + var singleChar = node.getAttribute("singleChar"); + var esc = node.getAttribute("escape"); + filter.value2regex(wildCard, singleChar, esc); + obj.filters.push(filter); + }, + "Literal": function(node, obj) { + obj.value = this.getChildValue(node); + }, + "PropertyName": function(node, filter) { + filter.property = this.getChildValue(node); + }, + "LowerBoundary": function(node, filter) { + filter.lowerBoundary = this.readOgcExpression(node); + }, + "UpperBoundary": function(node, filter) { + filter.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; + }, + + /** + * Method: write + * + * Parameters: + * filter - {} A filter object. + * + * Returns: + * {DOMElement} An ogc:Filter element. + */ + write: function(filter) { + return this.writers.ogc["Filter"].apply(this, [filter]); + }, + + /** + * 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: { + "ogc": { + "Filter": function(filter) { + var node = this.createElementNSPlus("ogc:Filter"); + var sub = filter.CLASS_NAME.split(".").pop(); + if(sub == "FeatureId") { + for(var i=0; i": "PropertyIsGreaterThan", + "<=": "PropertyIsLessThanOrEqualTo", + ">=": "PropertyIsGreaterThanOrEqualTo", + "..": "PropertyIsBetween", + "~": "PropertyIsLike", + "BBOX": "BBOX", + "DWITHIN": "DWITHIN", + "INTERSECTS": "INTERSECTS" + }, + + + /** + * 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.Filter.v1" + +}); diff --git a/lib/OpenLayers/Format/Filter/v1_0_0.js b/lib/OpenLayers/Format/Filter/v1_0_0.js new file mode 100644 index 0000000000..9818cea9a2 --- /dev/null +++ b/lib/OpenLayers/Format/Filter/v1_0_0.js @@ -0,0 +1,48 @@ +/* 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/Filter/v1.js + */ + +/** + * Class: OpenLayers.Format.Filter.v1_0_0 + * Write ogc:Filter version 1.0.0. + * + * Inherits from: + * - + */ +OpenLayers.Format.Filter.v1_0_0 = OpenLayers.Class( + OpenLayers.Format.Filter.v1, { + + /** + * Constant: VERSION + * {String} 1.0.0 + */ + VERSION: "1.0.0", + + /** + * Property: schemaLocation + * {String} http://www.opengis.net/ogc/filter/1.0.0/filter.xsd + */ + schemaLocation: "http://www.opengis.net/ogc/filter/1.0.0/filter.xsd", + + /** + * Constructor: OpenLayers.Format.Filter.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.Filter.v1.prototype.initialize.apply( + this, [options] + ); + }, + + CLASS_NAME: "OpenLayers.Format.Filter.v1_0_0" + +}); \ No newline at end of file diff --git a/lib/OpenLayers/Format/SLD.js b/lib/OpenLayers/Format/SLD.js index 1d9b829ea6..5d5070612a 100644 --- a/lib/OpenLayers/Format/SLD.js +++ b/lib/OpenLayers/Format/SLD.js @@ -9,6 +9,7 @@ * @requires OpenLayers/Filter/FeatureId.js * @requires OpenLayers/Filter/Logical.js * @requires OpenLayers/Filter/Comparison.js + * @requires OpenLayers/Filter/Spatial.js */ /** diff --git a/lib/OpenLayers/Format/SLD/v1.js b/lib/OpenLayers/Format/SLD/v1.js index db2a88063d..eda406f1a3 100644 --- a/lib/OpenLayers/Format/SLD/v1.js +++ b/lib/OpenLayers/Format/SLD/v1.js @@ -66,6 +66,13 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.XML, { * this instance. */ initialize: function(options) { + // extend with ogc:Filter readers and writers + this.readers["ogc"] = OpenLayers.Format.Filter.v1.prototype.readers["ogc"]; + this.writers["ogc"] = OpenLayers.Format.Filter.v1.prototype.writers["ogc"]; + // extend with custom filter methods that may get changed + this.readOgcExpression = OpenLayers.Format.Filter.v1.prototype.readOgcExpression; + this.getFilterType = OpenLayers.Format.Filter.v1.prototype.getFilterType; + this.filterMap = OpenLayers.Format.Filter.v1.prototype.filterMap; OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); }, @@ -273,148 +280,9 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.XML, { "Format": function(node, graphic) { graphic.graphicFormat = this.getChildValue(node); } - }, - "ogc": { - "Filter": function(node, rule) { - // Filters correspond to subclasses of OpenLayers.Filter. - // Since they contain information we don't persist, we - // create a temporary object and then pass on the filter - // (ogc:Filter) to the parent rule (sld:Rule). - var obj = { - fids: [], - filters: [] - }; - this.readChildNodes(node, obj); - if(obj.fids.length > 0) { - rule.filter = new OpenLayers.Filter.FeatureId({ - fids: obj.fids - }); - } else if(obj.filters.length > 0) { - rule.filter = obj.filters[0]; - } - }, - "FeatureId": function(node, obj) { - var fid = node.getAttribute("fid"); - if(fid) { - obj.fids.push(fid); - } - }, - "And": function(node, obj) { - var filter = new OpenLayers.Filter.Logical({ - type: OpenLayers.Filter.Logical.AND - }); - this.readChildNodes(node, filter); - obj.filters.push(filter); - }, - "Or": function(node, obj) { - var filter = new OpenLayers.Filter.Logical({ - type: OpenLayers.Filter.Logical.OR - }); - this.readChildNodes(node, filter); - obj.filters.push(filter); - }, - "Not": function(node, obj) { - var filter = new OpenLayers.Filter.Logical({ - type: OpenLayers.Filter.Logical.NOT - }); - this.readChildNodes(node, filter); - obj.filters.push(filter); - }, - "PropertyIsEqualTo": function(node, obj) { - var filter = new OpenLayers.Filter.Comparison({ - type: OpenLayers.Filter.Comparison.EQUAL_TO - }); - this.readChildNodes(node, filter); - obj.filters.push(filter); - }, - "PropertyIsNotEqualTo": function(node, obj) { - var filter = new OpenLayers.Filter.Comparison({ - type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO - }); - this.readChildNodes(node, filter); - obj.filters.push(filter); - }, - "PropertyIsLessThan": function(node, obj) { - var filter = new OpenLayers.Filter.Comparison({ - type: OpenLayers.Filter.Comparison.LESS_THAN - }); - this.readChildNodes(node, filter); - obj.filters.push(filter); - }, - "PropertyIsGreaterThan": function(node, obj) { - var filter = new OpenLayers.Filter.Comparison({ - type: OpenLayers.Filter.Comparison.GREATER_THAN - }); - this.readChildNodes(node, filter); - obj.filters.push(filter); - }, - "PropertyIsLessThanOrEqualTo": function(node, obj) { - var filter = new OpenLayers.Filter.Comparison({ - type: OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO - }); - this.readChildNodes(node, filter); - obj.filters.push(filter); - }, - "PropertyIsGreaterThanOrEqualTo": function(node, obj) { - var filter = new OpenLayers.Filter.Comparison({ - type: OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO - }); - this.readChildNodes(node, filter); - obj.filters.push(filter); - }, - "PropertyIsBetween": function(node, obj) { - var filter = new OpenLayers.Filter.Comparison({ - type: OpenLayers.Filter.Comparison.BETWEEN - }); - this.readChildNodes(node, filter); - obj.filters.push(filter); - }, - "PropertyIsLike": function(node, obj) { - var filter = new OpenLayers.Filter.Comparison({ - type: OpenLayers.Filter.Comparison.LIKE - }); - this.readChildNodes(node, filter); - var wildCard = node.getAttribute("wildCard"); - var singleChar = node.getAttribute("singleChar"); - var esc = node.getAttribute("escape"); - filter.value2regex(wildCard, singleChar, esc); - obj.filters.push(filter); - }, - "Literal": function(node, obj) { - obj.value = this.getChildValue(node); - }, - "PropertyName": function(node, filter) { - filter.property = this.getChildValue(node); - }, - "LowerBoundary": function(node, filter) { - filter.lowerBoundary = this.readOgcExpression(node); - }, - "UpperBoundary": function(node, filter) { - filter.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 @@ -897,175 +765,9 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.XML, { value: format }); } - }, - "ogc": { - "Filter": function(filter) { - var node = this.createElementNSPlus("ogc:Filter"); - var sub = filter.CLASS_NAME.split(".").pop(); - if(sub == "FeatureId") { - for(var i=0, len=filter.fids.length; 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. diff --git a/tests/Format/Filter.html b/tests/Format/Filter.html new file mode 100644 index 0000000000..08866bd1c2 --- /dev/null +++ b/tests/Format/Filter.html @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/tests/Format/Filter/v1_0_0.html b/tests/Format/Filter/v1_0_0.html new file mode 100644 index 0000000000..3347129526 --- /dev/null +++ b/tests/Format/Filter/v1_0_0.html @@ -0,0 +1,61 @@ + + + + + + + + diff --git a/tests/list-tests.html b/tests/list-tests.html index 5e5c5cbbac..185a32aaca 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -48,6 +48,8 @@
  • Format/OSM.html
  • Format/SLD.html
  • Format/SLD/v1_0_0.html
  • +
  • Format/Filter.html
  • +
  • Format/Filter/v1_0_0.html
  • Format/WKT.html
  • Format/WMC.html
  • Format/WMC/v1_1_0.html