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