diff --git a/examples/vector-formats.html b/examples/vector-formats.html index e01406048b..714f12d939 100644 --- a/examples/vector-formats.html +++ b/examples/vector-formats.html @@ -52,24 +52,38 @@ var in_options = { 'internalProjection': map.baseLayer.projection, 'externalProjection': new OpenLayers.Projection(OpenLayers.Util.getElement("inproj").value) - } + }; var out_options = { 'internalProjection': map.baseLayer.projection, 'externalProjection': new OpenLayers.Projection(OpenLayers.Util.getElement("outproj").value) - } + }; + var gmlOptions = { + featureType: "feature", + featureNS: "http://example.com/feature" + }; + var gmlOptionsIn = OpenLayers.Util.extend( + OpenLayers.Util.extend({}, gmlOptions), + in_options + ); + var gmlOptionsOut = OpenLayers.Util.extend( + OpenLayers.Util.extend({}, gmlOptions), + out_options + ); formats = { 'in': { wkt: new OpenLayers.Format.WKT(in_options), geojson: new OpenLayers.Format.GeoJSON(in_options), georss: new OpenLayers.Format.GeoRSS(in_options), - gml: new OpenLayers.Format.GML(in_options), + gml2: new OpenLayers.Format.GML.v2(gmlOptionsIn), + gml3: new OpenLayers.Format.GML.v3(gmlOptionsIn), kml: new OpenLayers.Format.KML(in_options) }, 'out': { wkt: new OpenLayers.Format.WKT(out_options), geojson: new OpenLayers.Format.GeoJSON(out_options), georss: new OpenLayers.Format.GeoRSS(out_options), - gml: new OpenLayers.Format.GML(out_options), + gml2: new OpenLayers.Format.GML.v2(gmlOptionsOut), + gml3: new OpenLayers.Format.GML.v3(gmlOptionsOut), kml: new OpenLayers.Format.KML(out_options) } }; @@ -169,7 +183,8 @@ - + +   diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index 483281b3e0..4beda022ad 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -206,6 +206,9 @@ "OpenLayers/Format.js", "OpenLayers/Format/XML.js", "OpenLayers/Format/GML.js", + "OpenLayers/Format/GML/Base.js", + "OpenLayers/Format/GML/v2.js", + "OpenLayers/Format/GML/v3.js", "OpenLayers/Format/KML.js", "OpenLayers/Format/GeoRSS.js", "OpenLayers/Format/WFS.js", diff --git a/lib/OpenLayers/Format/GML/Base.js b/lib/OpenLayers/Format/GML/Base.js new file mode 100644 index 0000000000..76c187c16d --- /dev/null +++ b/lib/OpenLayers/Format/GML/Base.js @@ -0,0 +1,525 @@ +/* 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/XMl.js + */ + +/** + * Eventually, this will require the OpenLayers.Format.GML. For now, since + * this parser can be included in a lib without the old GML parser, we + * declare the namespace if it doesn't exist. + */ +if(!OpenLayers.Format.GML) { + OpenLayers.Format.GML = {}; +} + +/** + * Class: OpenLayers.Format.GML.Base + * Superclass for GML parsers. + * + * Inherits from: + * - + */ +OpenLayers.Format.GML.Base = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + gml: "http://www.opengis.net/gml", + xlink: "http://www.w3.org/1999/xlink", + xsi: "http://www.w3.org/2001/XMLSchema-instance", + wfs: "http://www.opengis.net/wfs" // this is a convenience for reading wfs:FeatureCollection + }, + + /** + * Property: defaultPrefix + */ + defaultPrefix: "gml", + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. + */ + schemaLocation: null, + + /** + * APIProperty: featureType + * {String} The local (without prefix) feature typeName. + */ + featureType: null, + + /** + * APIProperty: featureNS + * {String} The feature namespace. Must be set in the options at + * construction. + */ + featureNS: null, + + /** + * APIProperty: geometry + * {String} Name of geometry element. Defaults to "geometry". + */ + geometryName: "geometry", + + /** + * APIProperty: extractAttributes + * {Boolean} Extract attributes from GML. Default is true. + */ + extractAttributes: true, + + /** + * APIProperty: srsName + * {String} URI for spatial reference system. This is optional for + * single part geometries and mandatory for collections and multis. + * If set, the srsName attribute will be written for all geometries. + * Default is null. + */ + srsName: null, + + /** + * APIProperty: xy + * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x) + * Changing is not recommended, a new Format should be instantiated. + */ + xy: true, + + /** + * Property: regExes + * Compiled regular expressions for manipulating strings. + */ + regExes: { + trimSpace: (/^\s*|\s*$/g), + removeSpace: (/\s*/g), + splitSpace: (/\s+/), + trimComma: (/\s*,\s*/g) + }, + + /** + * Constructor: OpenLayers.Format.GML.Base + * Instances of this class are not created directly. Use the + * or constructor + * instead. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + * + * Valid options properties: + * featureType - {String} Local (without prefix) feature typeName (required). + * featureNS - {String} Feature namespace (required). + * geometryName - {String} Geometry element name. + */ + initialize: function(options) { + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + this.setNamespace("feature", options.featureNS); + }, + + /** + * Method: read + * + * Parameters: + * data - {DOMElement} A gml:featureMember element, a gml:featureMembers + * element, or an element containing either of the above at any level. + * + * Returns: + * {Array()} An array of features. + */ + read: function(data) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + if(data && data.nodeType == 9) { + data = data.documentElement; + } + var features = []; + this.readNode(data, {features: features}); + if(features.length == 0) { + // look for gml:featureMember elements + var elements = this.getElementsByTagNameNS( + data, this.namespaces.gml, "featureMember" + ); + if(elements.length) { + for(var i=0, len=elements.length; i) | OpenLayers.Feature.Vector} + * An array of features or a single feature. + * + * Returns: + * {String} Given an array of features, a doc with a gml:featureMembers + * element will be returned. Given a single feature, a doc with a + * gml:featureMember element will be returned. + */ + write: function(features) { + var name; + if(features instanceof Array) { + name = "featureMembers"; + } else { + name = "featureMember"; + } + var root = this.writeNode("gml:" + name, features); + this.setAttributeNS( + root, this.namespaces["xsi"], + "xsi:schemaLocation", this.schemaLocation + ); + + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * 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: { + "gml": { + "featureMember": function(feature) { + var node = this.createElementNSPlus("gml:featureMember"); + this.writeNode("feature:_typeName", feature, node); + return node; + }, + "MultiPoint": function(geometry) { + var node = this.createElementNSPlus("gml:MultiPoint"); + for(var i=0; i + */ +OpenLayers.Format.GML.v2 = OpenLayers.Class(OpenLayers.Format.GML.Base, { + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. + */ + schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd", + + /** + * Constructor: OpenLayers.Format.GML.v2 + * Create a parser for GML v2. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + * + * Valid options properties: + * featureType - {String} Local (without prefix) feature typeName (required). + * featureNS - {String} Feature namespace (required). + * geometryName - {String} Geometry element name. + */ + initialize: function(options) { + OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]); + }, + + /** + * 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: { + "gml": OpenLayers.Util.applyDefaults({ + "outerBoundaryIs": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + container.outer = obj.components[0]; + }, + "innerBoundaryIs": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + container.inner.push(obj.components[0]); + }, + "Box": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + if(!container.components) { + container.components = []; + } + var min = obj.points[0]; + var max = obj.points[1]; + container.components.push( + new OpenLayers.Bounds(min.x, min.y, max.x, max.y) + ); + } + }, OpenLayers.Format.GML.Base.prototype.readers["gml"]), + "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"], + "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"] + }, + + /** + * Method: write + * + * Parameters: + * features - {Array() | OpenLayers.Feature.Vector} + * An array of features or a single feature. + * + * Returns: + * {String} Given an array of features, a doc with a gml:featureMembers + * element will be returned. Given a single feature, a doc with a + * gml:featureMember element will be returned. + */ + write: function(features) { + var name; + if(features instanceof Array) { + // GML2 only has abstract feature collections + // wfs provides a feature collection from a well-known schema + name = "wfs:FeatureCollection"; + } else { + name = "gml:featureMember"; + } + var root = this.writeNode(name, features); + this.setAttributeNS( + root, this.namespaces["xsi"], + "xsi:schemaLocation", this.schemaLocation + ); + + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * 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: { + "gml": OpenLayers.Util.applyDefaults({ + "Point": function(geometry) { + var node = this.createElementNSPlus("gml:Point"); + this.writeNode("coordinates", [geometry], node); + return node; + }, + "coordinates": function(points) { + var numPoints = points.length; + var parts = new Array(numPoints); + var point; + for(var i=0; i + */ +OpenLayers.Format.GML.v3 = OpenLayers.Class(OpenLayers.Format.GML.Base, { + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. The writers + * conform with the Simple Features Profile for GML. + */ + schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd", + + /** + * Constructor: OpenLayers.Format.GML.v3 + * Create a parser for GML v3. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + * + * Valid options properties: + * featureType - {String} Local (without prefix) feature typeName (required). + * featureNS - {String} Feature namespace (required). + * geometryName - {String} Geometry element name. + */ + initialize: function(options) { + OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]); + }, + + /** + * 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: { + "gml": OpenLayers.Util.applyDefaults({ + "featureMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "pos": function(node, obj) { + var str = this.getChildValue(node).replace( + this.regExes.trimSpace, "" + ); + var coords = str.split(this.regExes.splitSpace); + var point; + if(this.xy) { + point = new OpenLayers.Geometry.Point( + coords[0], coords[1], coords[2] + ); + } else { + point = new OpenLayers.Geometry.Point( + coords[1], coords[0], coords[2] + ); + } + obj.points = [point]; + }, + "posList": function(node, obj) { + var str = this.concatChildValues(node).replace( + this.regExes.trimSpace, "" + ); + var coords = str.split(this.regExes.splitSpace); + var dim = parseInt(node.getAttribute("dimension")) || 2; + var j, x, y, z; + var numPoints = coords.length / dim; + var points = new Array(numPoints); + for(var i=0, len=coords.length; i 0) { + container.components = [ + new OpenLayers.Geometry.MultiPolygon(obj.components) + ]; + } + }, + "surfaceMember": function(node, obj) { + this.readChildNodes(node, obj); + }, + "surfaceMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "pointMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "lineStringMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "polygonMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "geometryMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "Envelope": function(node, container) { + var obj = {points: new Array(2)}; + this.readChildNodes(node, obj); + if(!container.components) { + container.components = []; + } + var min = obj.points[0]; + var max = obj.points[1]; + container.components.push( + new OpenLayers.Bounds(min.x, min.y, max.x, max.y) + ); + }, + "lowerCorner": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj) + container.points[0] = obj.points[0]; + }, + "upperCorner": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj) + container.points[1] = obj.points[0]; + } + }, OpenLayers.Format.GML.Base.prototype.readers["gml"]), + "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"], + "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"] + }, + + /** + * Method: write + * + * Parameters: + * features - {Array() | OpenLayers.Feature.Vector} + * An array of features or a single feature. + * + * Returns: + * {String} Given an array of features, a doc with a gml:featureMembers + * element will be returned. Given a single feature, a doc with a + * gml:featureMember element will be returned. + */ + write: function(features) { + var name; + if(features instanceof Array) { + name = "featureMembers"; + } else { + name = "featureMember"; + } + var root = this.writeNode("gml:" + name, features); + this.setAttributeNS( + root, this.namespaces["xsi"], + "xsi:schemaLocation", this.schemaLocation + ); + + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * 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: { + "gml": OpenLayers.Util.applyDefaults({ + "featureMembers": function(features) { + var node = this.createElementNSPlus("gml:featureMembers"); + for(var i=0, len=features.length; i to add or set a namespace alias after construction. */ - namespaces: {}, + namespaces: null, /** - * Property: defaultNamespace + * Property: namespaceAlias + * {Object} Mapping of namespace URI to namespace alias. This object + * is read-only. Use to add or set a namespace alias. + */ + namespaceAlias: null, + + /** + * Property: defaultPrefix * {String} The default namespace alias for creating element nodes. */ - defaultNamespace: null, + defaultPrefix: null, /** * Property: readers @@ -75,6 +84,12 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { this.xmldom = new ActiveXObject("Microsoft.XMLDOM"); } OpenLayers.Format.prototype.initialize.apply(this, [options]); + // clone the namespace object and set all namespace aliases + this.namespaces = OpenLayers.Util.extend({}, this.namespaces); + this.namespaceAlias = {}; + for(var alias in this.namespaces) { + this.namespaceAlias[this.namespaces[alias]] = alias; + } }, /** @@ -85,6 +100,19 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { this.xmldom = null; OpenLayers.Format.prototype.destroy.apply(this, arguments); }, + + /** + * Method: setNamespace + * Set a namespace alias and URI for the format. + * + * Parameters: + * alias - {String} The namespace alias (prefix). + * uri - {String} The namespace URI. + */ + setNamespace: function(alias, uri) { + this.namespaces[alias] = uri; + this.namespaceAlias[uri] = alias; + }, /** * APIMethod: read @@ -329,11 +357,12 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { * {String} The value of the first child of the given node. */ getChildValue: function(node, def) { - var value; - if (node && node.firstChild && node.firstChild.nodeValue) { - value = node.firstChild.nodeValue; - } else { - value = (def != undefined) ? def : ""; + var value = def || ""; + if(node) { + var child = node.firstChild; + if(child) { + value = child.nodeValue || value; + } } return value; }, @@ -424,29 +453,6 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { } }, - /** - * 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.defaultPrefix; - } else { - var prefix = null; - for(var p in this.namespaces) { - if(this.namespaces[p] == uri) { - prefix = p; - break; - } - } - } - return prefix; - }, - /** * Method: createElementNSPlus * Shorthand for creating namespaced elements with optional attributes and @@ -534,10 +540,9 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { if(!obj) { obj = {}; } - var prefix = this.getNamespacePrefix(node.namespaceURI); - var local = node.nodeName.split(":").pop(); - var group = this.readers[prefix]; + var group = this.readers[this.namespaceAlias[node.namespaceURI]]; if(group) { + var local = node.localName || node.nodeName.split(":").pop(); var reader = group[local] || group["*"]; if(reader) { reader.apply(this, [node, obj]); @@ -564,7 +569,7 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { } var children = node.childNodes; var child; - for(var i=0; i + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/tests/Format/GML/v3.html b/tests/Format/GML/v3.html new file mode 100644 index 0000000000..e24a2fb0a8 --- /dev/null +++ b/tests/Format/GML/v3.html @@ -0,0 +1,551 @@ + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/tests/Format/XML.html b/tests/Format/XML.html index c9d3b9e3b1..e58dace6e4 100644 --- a/tests/Format/XML.html +++ b/tests/Format/XML.html @@ -291,35 +291,39 @@ t.ok(found === false, "returns false for bad attribute"); } - function test_getNamespacePrefix(t) { - t.plan(6); + function test_namespaces(t) { + t.plan(2); - // test that getNamespacePrefix returns null with no ns defined - var format = new OpenLayers.Format.XML(); - var got = format.getNamespacePrefix("http://example.com/foo"); - t.eq(got, null, "returns null when no namespaces are defined"); + var format = new OpenLayers.Format.XML({ + namespaces: { + "def": "http://example.com/default", + "foo": "http://example.com/foo", + "bar": "http://example.com/bar" + }, + defaultPrefix: "def" + }); - format.defaultPrefix = "def"; - format.namespaces = { - "def": "http://example.com/default", - "foo": "http://example.com/foo", - "bar": "http://example.com/bar" - }; + // test that prototype has not been altered + t.eq(OpenLayers.Format.XML.prototype.namespaces, null, + "setting namespaces at construction does not modify prototype"); - var cases = [ - {uri: null, expect: "def"}, - {uri: "http://example.com/default", expect: "def"}, - {uri: "http://example.com/foo", expect: "foo"}, - {uri: "http://example.com/bar", expect: "bar"}, - {uri: "http://example.com/nothing", expect: null} - ]; + // test that namespaceAlias has been set + t.eq(format.namespaceAlias["http://example.com/foo"], "foo", + "namespaceAlias mapping has been set"); - var test; - for(var i=0; i uri mapping set"); + t.eq(format.namespaceAlias["http://example.com/foo"], "foo", "uri -> alias mapping set"); } diff --git a/tests/list-tests.html b/tests/list-tests.html index 9e358f9c93..f237c6647d 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -43,6 +43,8 @@
  • Format/GeoJSON.html
  • Format/GeoRSS.html
  • Format/GML.html
  • +
  • Format/GML/v2.html
  • +
  • Format/GML/v3.html
  • Format/GPX.html
  • Format/JSON.html
  • Format/KML.html