From 45860f4552f4f6d366fdb12ee24f6859480cfb02 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 21 Feb 2014 07:18:36 +0100 Subject: [PATCH 01/11] Framework for serializing structures to XML This adds several helper functions for serializing to XML: * ol.xml.serialize: Counterpart to ol.xml.parse. By splitting the serialization process up into a node factory and a node writer, note writers can easily be used for different namespaces. * ol.xml.pushSerializeAndPop: Counterpart to ol.xml.pushParseAndPop. * ol.xml.makeStructureNS: Works like ol.xml.createParsersNS, but works for arbitrary structures. * ol.xml.makeChildAppender: If the top item of the stack has the new ol.xml.NodeStackItem type, this helper function can be used to create a serializer that appends the current node to its designated parent. * ol.xml.makeChildNodeFactory: Creates a node factory which produces child nodes from an array of node names which are passed to ol.xml.serialize. * ol.xml.makeSequence: A convenience function for creating xsd:sequence structures. Takes an object literal and an ordered list of the keys, and returns an array that can be passed as values to ol.xml.serialize. * ol.xml.makeSimpleTypeWriter: Using e.g. the new write*TextNode functions from ol.format.XSD, this function creates a node writer that writes simple type nodes for values like strings or numbers. The following commits will be using this new framework for implementing ol.format.GPX.writeFeatures, and prose documentation with instructions based on what was said above will be added. --- src/ol/format/xsdformat.js | 49 ++++++++ src/ol/xml.js | 241 ++++++++++++++++++++++++++++++++++++- 2 files changed, 286 insertions(+), 4 deletions(-) diff --git a/src/ol/format/xsdformat.js b/src/ol/format/xsdformat.js index 058cedd1f9..f60fa4cc4f 100644 --- a/src/ol/format/xsdformat.js +++ b/src/ol/format/xsdformat.js @@ -1,5 +1,6 @@ goog.provide('ol.format.XSD'); +goog.require('goog.asserts'); goog.require('goog.string'); goog.require('ol.xml'); @@ -123,3 +124,51 @@ ol.format.XSD.readString = function(node) { var s = ol.xml.getAllTextContent(node, false); return goog.string.trim(s); }; + + +/** + * @param {Node} node Node to append a TextNode with the dateTime to. + * @param {number} dateTime DateTime in seconds. + */ +ol.format.XSD.writeDateTimeTextNode = function(node, dateTime) { + var date = new Date(dateTime * 1000); + var string = date.getUTCFullYear() + '-' + + goog.string.padNumber(date.getUTCMonth() + 1, 2) + '-' + + goog.string.padNumber(date.getUTCDate(), 2) + 'T' + + goog.string.padNumber(date.getUTCHours(), 2) + ':' + + goog.string.padNumber(date.getUTCMinutes(), 2) + ':' + + goog.string.padNumber(date.getUTCSeconds(), 2) + 'Z'; + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; + + +/** + * @param {Node} node Node to append a TextNode with the decimal to. + * @param {number} decimal Decimal. + */ +ol.format.XSD.writeDecimalTextNode = function(node, decimal) { + var string = decimal.toPrecision(); + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; + + +/** + * @param {Node} node Node to append a TextNode with the decimal to. + * @param {number} nonNegativeInteger Non negative integer. + */ +ol.format.XSD.writeNonNegativeIntegerTextNode = + function(node, nonNegativeInteger) { + goog.asserts.assert(nonNegativeInteger >= 0); + goog.asserts.assert(nonNegativeInteger == (nonNegativeInteger | 0)); + var string = nonNegativeInteger.toString(); + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; + + +/** + * @param {Node} node Node to append a TextNode with the string to. + * @param {string} string String. + */ +ol.format.XSD.writeStringTextNode = function(node, string) { + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; diff --git a/src/ol/xml.js b/src/ol/xml.js index c21e526ea6..71a0da6fca 100644 --- a/src/ol/xml.js +++ b/src/ol/xml.js @@ -1,18 +1,75 @@ +// FIXME Remove ol.xml.makeParsersNS, and use ol.xml.makeStructureNS instead. + goog.provide('ol.xml'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.dom.NodeType'); +goog.require('goog.dom.xml'); goog.require('goog.object'); goog.require('goog.userAgent'); +/** + * @typedef {{node:Node, context}} + */ +ol.xml.NodeStackItem; + + /** * @typedef {function(Node, Array.<*>)} */ ol.xml.Parser; +/** + * @typedef {function(Node, *, Array.<*>)} + */ +ol.xml.Serializer; + + +/** + * @const + * @type {Document} + */ +ol.xml.DOCUMENT = goog.dom.xml.createDocument(); + + +/** + * @param {string} namespaceURI Namespace URI. + * @param {string} qualifiedName Qualified name. + * @return {Node} Node. + * @private + */ +ol.xml.createElementNS_ = function(namespaceURI, qualifiedName) { + return ol.xml.DOCUMENT.createElementNS(namespaceURI, qualifiedName); +}; + + +/** + * @param {string} namespaceURI Namespace URI. + * @param {string} qualifiedName Qualified name. + * @return {Node} Node. + * @private + */ +ol.xml.createElementNSActiveX_ = function(namespaceURI, qualifiedName) { + if (!goog.isDef(namespaceURI)) { + namespaceURI = ''; + } + return ol.xml.DOCUMENT.createNode(1, qualifiedName, namespaceURI); +}; + + +/** + * @param {string} namespaceURI Namespace URI. + * @param {string} qualifiedName Qualified name. + * @return {Node} Node. + */ +ol.xml.createElementNS = + (document.implementation && document.implementation.createDocument) ? + ol.xml.createElementNS_ : ol.xml.createElementNSActiveX_; + + /** * @param {Node} node Node. * @param {boolean} normalizeWhitespace Normalize whitespace. @@ -138,6 +195,47 @@ ol.xml.isNodeIE_ = function(value) { ol.xml.isNode = goog.userAgent.IE ? ol.xml.isNodeIE_ : ol.xml.isNode_; +/** + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @param {string|number} value Value. + * @private + */ +ol.xml.setAttributeNS_ = function(node, namespaceURI, name, value) { + node.setAttributeNS(namespaceURI, name, value); +}; + + +/** + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @param {string|number} value Value. + * @private + */ +ol.xml.setAttributeNSActiveX_ = function(node, namespaceURI, name, value) { + if (!goog.isNull(namespaceURI)) { + var attribute = node.ownerDocument.createNode(2, name, namespaceURI); + attribute.nodeValue = value; + node.setAttributeNode(attribute); + } else { + node.setAttribute(name, value); + } +}; + + +/** + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @param {string|number} value Value. + */ +ol.xml.setAttributeNS = + (document.implementation && document.implementation.createDocument) ? + ol.xml.setAttributeNS_ : ol.xml.setAttributeNSActiveX_; + + /** * @param {string} xml XML. * @return {Document} Document. @@ -254,13 +352,93 @@ ol.xml.makeObjectPropertySetter = * @return {Object.>} Parsers NS. */ ol.xml.makeParsersNS = function(namespaceURIs, parsers, opt_parsersNS) { - /** @type {Object.>} */ - var parsersNS = goog.isDef(opt_parsersNS) ? opt_parsersNS : {}; + return /** @type {Object.>} */ ( + ol.xml.makeStructureNS(namespaceURIs, parsers, opt_parsersNS)); +}; + + +/** + * @param {function(this: T, Node, V, Array.<*>)} + * nodeWriter Node writer. + * @param {T=} opt_this The object to use as `this` in `nodeWriter`. + * @return {ol.xml.Serializer} Serializer. + * @template T, V + */ +ol.xml.makeChildAppender = function(nodeWriter, opt_this) { + return function(node, value, objectStack) { + nodeWriter.call(opt_this, node, value, objectStack); + var parent = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(parent)); + var parentNode = parent.node; + goog.asserts.assert(ol.xml.isNode(parentNode) || + ol.xml.isDocument(parentNode)); + parentNode.appendChild(node); + }; +}; + + +/** + * @param {string=} opt_namespaceURI Namespace URI which will be used + * for all created nodes. If not provided, the namespace of the parent node + * will be used. + * @return {function(*, Array.<*>, (string|undefined)): Node} Node factory. + */ +ol.xml.makeChildNodeFactory = function(opt_namespaceURI) { + var namespaceURI = /** @type {string} */ + (goog.isDef(opt_namespaceURI) ? opt_namespaceURI : null); + return function(value, objectStack, nodeName) { + return ol.xml.createElementNS(namespaceURI, nodeName); + }; +}; + + +/** + * @param {Object.} object Key-value pairs for the sequence. + * @param {Array.} orderedKeys Keys in the order of the sequence. + * @return {Array.} Values in the order of the sequence. + * @template V + */ +ol.xml.makeSequence = function(object, orderedKeys) { + var length = orderedKeys.length; + var sequence = new Array(length); + for (var i = 0; i < length; ++i) { + sequence[i] = object[orderedKeys[i]]; + } + return sequence; +}; + + +/** + * @param {function(this: T, Node, V)} nodeValueSetter Function that sets + * the node value, like the `write*TextNode` functions from `ol.format.XSD`. + * @param {T=} opt_this `this` object for the node writer. + * @return {ol.xml.Serializer} Serializer. + * @template T, V + */ +ol.xml.makeSimpleTypeWriter = function(nodeValueSetter, opt_this) { + return function(node, value) { + nodeValueSetter.call(opt_this, node, value); + }; +}; + + +/** + * @param {Array.} namespaceURIs Namespace URIs. + * @param {T} structure Structure. + * @param {Object.=} opt_structureNS Namespaced structure to add to. + * @return {Object.} Namespaced structure. + * @template T + */ +ol.xml.makeStructureNS = function(namespaceURIs, structure, opt_structureNS) { + /** + * @type {Object.} + */ + var structureNS = goog.isDef(opt_structureNS) ? opt_structureNS : {}; var i, ii; for (i = 0, ii = namespaceURIs.length; i < ii; ++i) { - parsersNS[namespaceURIs[i]] = parsers; + structureNS[namespaceURIs[i]] = structure; } - return parsersNS; + return structureNS; }; @@ -301,3 +479,58 @@ ol.xml.pushParseAndPop = function( ol.xml.parse(parsersNS, node, objectStack, opt_this); return objectStack.pop(); }; + + +/** + * @param {Object.>} serializersNS + * Namespaced serializers. + * @param {function(this: T, *, Array.<*>, (string|undefined)): Node|undefined} nodeFactory + * Node factory. + * @param {Array.<*>} values Values. + * @param {Array.<*>} objectStack Node stack. + * @param {Array.=} opt_keys Keys of the `values`, will be passed to the + * `nodeFactory`. + * @param {T=} opt_this The object to use as `this` for the node factory and + * serializers. + * @template T + */ +ol.xml.serialize = function( + serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) { + var length = (goog.isDef(opt_keys) ? opt_keys : values).length; + var value, node; + for (var i = 0; i < length; ++i) { + value = values[i]; + if (goog.isDef(value)) { + node = nodeFactory.call(opt_this, value, objectStack, + goog.isDef(opt_keys) ? opt_keys[i] : undefined); + if (goog.isDef(node)) { + serializersNS[node.namespaceURI][node.localName] + .call(opt_this, node, value, objectStack); + } + } + } +}; + + +/** + * @param {O} object Object. + * @param {Object.>} serializersNS + * Namespaced serializers. + * @param {function(this: T, *, Array.<*>, (string|undefined)): Node|undefined} nodeFactory + * Node factory. + * @param {Array.<*>} values Values. + * @param {Array.<*>} objectStack Node stack. + * @param {Array.=} opt_keys Keys of the `values`, will be passed to the + * `nodeFactory`. + * @param {T=} opt_this The object to use as `this` for the node factory and + * serializers. + * @return {O|undefined} Object. + * @template O, T + */ +ol.xml.pushSerializeAndPop = function(object, + serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) { + objectStack.push(object); + ol.xml.serialize( + serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this); + return objectStack.pop(); +}; From b4043398ebe24379483e4a6f812bec9008ef2644 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 21 Feb 2014 07:18:42 +0100 Subject: [PATCH 02/11] Added wpt serialization for ol.format.GPX --- src/ol/format/gpxformat.js | 214 ++++++++++++++++++++++++++ test/spec/ol/format/gpxformat.test.js | 25 ++- 2 files changed, 234 insertions(+), 5 deletions(-) diff --git a/src/ol/format/gpxformat.js b/src/ol/format/gpxformat.js index ded218bcaa..b649df728b 100644 --- a/src/ol/format/gpxformat.js +++ b/src/ol/format/gpxformat.js @@ -1,4 +1,5 @@ goog.provide('ol.format.GPX'); +goog.provide('ol.format.GPX.v1_1'); goog.require('goog.array'); goog.require('goog.asserts'); @@ -420,3 +421,216 @@ ol.format.GPX.prototype.readProjectionFromDocument = function(doc) { ol.format.GPX.prototype.readProjectionFromNode = function(node) { return ol.proj.get('EPSG:4326'); }; + + +/** + * @const + * @type {Array.} + * @private + */ +ol.format.GPX.LINK_SEQUENCE_ = ['text', 'type']; + + +/** + * @type {Object.>} + * @private + */ +ol.format.GPX.LINK_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'text': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'type': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)) + }); + + +/** + * @param {Node} node Node. + * @param {string} value Value for the link's `href` attribute. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GPX.writeLink_ = function(node, value, objectStack) { + node.setAttribute('href', value); + var properties = objectStack[objectStack.length - 1].context; + var link = [ + goog.object.get(properties, 'linkText'), + goog.object.get(properties, 'linkType') + ]; + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ ({node: node}), + ol.format.GPX.LINK_SERIALIZERS_, + ol.xml.makeChildNodeFactory(node.namespaceURI), link, objectStack, + ol.format.GPX.LINK_SEQUENCE_); +}; + + +/** + * @const + * @type {Object.>} + * @private + */ +ol.format.GPX.WPT_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'ele', 'time', 'magvar', 'geoidheight', 'name', 'cmt', 'desc', 'src', + 'link', 'sym', 'type', 'fix', 'sat', 'hdop', 'vdop', 'pdop', + 'ageofdgpsdata', 'dgpsid' + ]); + + +/** + * @type {Object.>} + * @private + */ +ol.format.GPX.WPT_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'ele': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeDecimalTextNode)), + 'time': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeDateTimeTextNode)), + 'magvar': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeDecimalTextNode)), + 'geoidheight': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeDecimalTextNode)), + 'name': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'cmt': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'desc': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'src': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), + 'sym': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'type': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'fix': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'sat': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeNonNegativeIntegerTextNode)), + 'hdop': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeDecimalTextNode)), + 'vdop': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeDecimalTextNode)), + 'pdop': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeDecimalTextNode)), + 'ageofdgpsdata': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeDecimalTextNode)), + 'dgpsid': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeNonNegativeIntegerTextNode)) + }); + + +/** + * @const + * @type {Object.} + * @private + */ +ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = { + 'Point': 'wpt', + 'LineString': 'rte', + 'MultiLineString': 'trk' +}; + + +/** + * @const + * @param {*} feature Feature. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node} Node. + * @private + */ +ol.format.GPX.GPX_NODE_FACTORY_ = function(feature, objectStack, opt_nodeName) { + goog.asserts.assertInstanceof(feature, ol.Feature); + var geometry = feature.getGeometry(); + if (goog.isDef(geometry)) { + var parentNode = objectStack[objectStack.length - 1].node; + goog.asserts.assert(ol.xml.isNode(parentNode)); + return ol.xml.createElementNS(parentNode.namespaceURI, + ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_[geometry.getType()]); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Object stack. + * @private + * @template T + */ +ol.format.GPX.writeWpt_ = function(node, feature, objectStack) { + var parentNode = objectStack[objectStack.length - 1].node; + goog.asserts.assert(ol.xml.isNode(parentNode)); + var namespaceURI = parentNode.namespaceURI; + var properties = feature.getProperties(); + var geometry = feature.getGeometry(); + if (goog.isDef(geometry)) { + goog.asserts.assert(geometry instanceof ol.geom.Point); + var coordinates = geometry.getCoordinates(); + //FIXME Projection handling + ol.xml.setAttributeNS(node, null, 'lat', coordinates[1]); + ol.xml.setAttributeNS(node, null, 'lon', coordinates[0]); + var geometryLayout = geometry.getLayout(); + switch (geometryLayout) { + case ol.geom.GeometryLayout.XYZM: + if (coordinates[3] !== 0) { + goog.object.set(properties, 'time', coordinates[3]); + } + case ol.geom.GeometryLayout.XYZ: + if (coordinates[2] !== 0) { + goog.object.set(properties, 'ele', coordinates[2]); + } + break; + case ol.geom.GeometryLayout.XYM: + if (coordinates[2] !== 0) { + goog.object.set(properties, 'time', coordinates[2]); + } + } + } + var orderedKeys = ol.format.GPX.WPT_SEQUENCE_[namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ + ({node: node, context: properties}), ol.format.GPX.WPT_SERIALIZERS_, + ol.xml.makeChildNodeFactory(namespaceURI), values, objectStack, + orderedKeys); +}; + + +/** + * @const + * @type {Object.>} + * @private + */ +ol.format.GPX.GPX_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + //'LineString': ol.xml.makeChildAppender(ol.format.GPX.writeRte_), + //'MultiLineString': ol.xml.makeChildAppender(ol.format.GPX.writeTrk_), + 'wpt': ol.xml.makeChildAppender(ol.format.GPX.writeWpt_) + }); + + + +/** + * @constructor + * @extends {ol.format.GPX} + * @todo stability experimental + */ +ol.format.GPX.V1_1 = function() { + goog.base(this); +}; +goog.inherits(ol.format.GPX.V1_1, ol.format.GPX); + + +/** + * @inheritDoc + */ +ol.format.GPX.V1_1.prototype.writeFeaturesNode = function(features) { + //FIXME Serialize metadata + var gpx = ol.xml.createElementNS(ol.format.GPX.NAMESPACE_URIS_[2], 'gpx'); + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ + ({node: gpx}), ol.format.GPX.GPX_SERIALIZERS_, + ol.format.GPX.GPX_NODE_FACTORY_, features, []); + return gpx; +}; diff --git a/test/spec/ol/format/gpxformat.test.js b/test/spec/ol/format/gpxformat.test.js index b146289a61..4da5a8d58c 100644 --- a/test/spec/ol/format/gpxformat.test.js +++ b/test/spec/ol/format/gpxformat.test.js @@ -214,7 +214,11 @@ describe('ol.format.GPX', function() { describe('wpt', function() { - it('can read a wpt', function() { + beforeEach(function() { + format = new ol.format.GPX.V1_1(); + }); + + it('can read and write a wpt', function() { var text = '' + ' ' + @@ -227,9 +231,11 @@ describe('ol.format.GPX', function() { expect(g).to.be.an(ol.geom.Point); expect(g.getCoordinates()).to.eql([2, 1, 0, 0]); expect(g.getLayout()).to.be(ol.geom.GeometryLayout.XYZM); + var serialized = format.writeFeatures(fs); + expect(serialized).to.xmleql(ol.xml.load(text)); }); - it('can read a wpt with ele', function() { + it('can read and write a wpt with ele', function() { var text = '' + ' ' + @@ -244,9 +250,11 @@ describe('ol.format.GPX', function() { expect(g).to.be.an(ol.geom.Point); expect(g.getCoordinates()).to.eql([2, 1, 3, 0]); expect(g.getLayout()).to.be(ol.geom.GeometryLayout.XYZM); + var serialized = format.writeFeatures(fs); + expect(serialized).to.xmleql(ol.xml.load(text)); }); - it('can read a wpt with time', function() { + it('can read and write a wpt with time', function() { var text = '' + ' ' + @@ -261,9 +269,11 @@ describe('ol.format.GPX', function() { expect(g).to.be.an(ol.geom.Point); expect(g.getCoordinates()).to.eql([2, 1, 0, 1263115752]); expect(g.getLayout()).to.be(ol.geom.GeometryLayout.XYZM); + var serialized = format.writeFeatures(fs); + expect(serialized).to.xmleql(ol.xml.load(text)); }); - it('can read a wpt with ele and time', function() { + it('can read and write a wpt with ele and time', function() { var text = '' + ' ' + @@ -279,9 +289,11 @@ describe('ol.format.GPX', function() { expect(g).to.be.an(ol.geom.Point); expect(g.getCoordinates()).to.eql([2, 1, 3, 1263115752]); expect(g.getLayout()).to.be(ol.geom.GeometryLayout.XYZM); + var serialized = format.writeFeatures(fs); + expect(serialized).to.xmleql(ol.xml.load(text)); }); - it('can read various wpt attributes', function() { + it('can read and write various wpt attributes', function() { var text = '' + ' ' + @@ -327,6 +339,8 @@ describe('ol.format.GPX', function() { expect(f.get('pdop')).to.be(8); expect(f.get('ageofdgpsdata')).to.be(9); expect(f.get('dgpsid')).to.be(10); + var serialized = format.writeFeatures(fs); + expect(serialized).to.xmleql(ol.xml.load(text)); }); }); @@ -378,3 +392,4 @@ goog.require('ol.format.GPX'); goog.require('ol.geom.LineString'); goog.require('ol.geom.MultiLineString'); goog.require('ol.geom.Point'); +goog.require('ol.xml'); From 13b4f07cd9f805e3c43c1dc31480d26e368dce84 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 21 Feb 2014 16:55:05 +0100 Subject: [PATCH 03/11] New makeChildrenAppender function and Node factory refactoring The makeChildAppender function is used for adding a node of a type with maxOccurs=1. For adding nodes of a type with maxOccurs>1, the new makeChildrenAppender function was added. With this new function, it turned out that more convenience for creating node factories is required. The makeChildNodeFactory function was renamed to makeSimpleNodeFactory, and it now can create node factories where not only the namespace, but also the node name can be fixed. --- src/ol/xml.js | 76 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/src/ol/xml.js b/src/ol/xml.js index 71a0da6fca..c5dd382f52 100644 --- a/src/ol/xml.js +++ b/src/ol/xml.js @@ -11,7 +11,7 @@ goog.require('goog.userAgent'); /** - * @typedef {{node:Node, context}} + * @typedef {{node:Node}} */ ol.xml.NodeStackItem; @@ -358,6 +358,9 @@ ol.xml.makeParsersNS = function(namespaceURIs, parsers, opt_parsersNS) { /** + * Creates a serializer that appends its `nodeWriter`'s to its designated + * parent. The parent node is the `node` of the {@link ol.xml.NodeStackItem} at + * the top of the `objectStack`. * @param {function(this: T, Node, V, Array.<*>)} * nodeWriter Node writer. * @param {T=} opt_this The object to use as `this` in `nodeWriter`. @@ -378,20 +381,75 @@ ol.xml.makeChildAppender = function(nodeWriter, opt_this) { /** - * @param {string=} opt_namespaceURI Namespace URI which will be used - * for all created nodes. If not provided, the namespace of the parent node - * will be used. - * @return {function(*, Array.<*>, (string|undefined)): Node} Node factory. + * Creates a serializer that creates multiple nodes of the same name and appends + * them to the designated parent. The parent node is the `node` of the + * {@link ol.xml.NodeStackItem} at the top of the `objectStack`. + * @param {function(this: T, Node, V, Array.<*>)} + * nodeWriter Node writer. + * @param {T=} opt_this The object to use as `this` in `nodeWriter`. + * @return {ol.xml.Serializer} Serializer. + * @template T, V */ -ol.xml.makeChildNodeFactory = function(opt_namespaceURI) { - var namespaceURI = /** @type {string} */ - (goog.isDef(opt_namespaceURI) ? opt_namespaceURI : null); - return function(value, objectStack, nodeName) { +ol.xml.makeChildrenAppender = function(nodeWriter, opt_this) { + var writer = ol.xml.makeChildAppender(nodeWriter); + var serializersNS, nodeFactory; + return function(node, value, objectStack) { + if (!goog.isDef(serializersNS)) { + serializersNS = {}; + var serializers = {}; + goog.object.set(serializers, node.localName, writer); + goog.object.set(serializersNS, node.namespaceURI, serializers); + nodeFactory = ol.xml.makeSimpleNodeFactory(node.localName); + } + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ + (objectStack[objectStack.length - 1]), serializersNS, nodeFactory, + value, objectStack); + }; +}; + + +/** + * @param {string=} opt_nodeName Fixed node name which will be used for all + * created nodes. If not provided, the nodeName will be the 3rd argument to + * the resulting node factory. + * @param {string=} opt_namespaceURI Fixed namespace URI which will be used for + * all created nodes. If not provided, the namespace of the parent node will + * be used. + * @return {function(*, Array.<*>, string=): Node} Node factory. + */ +ol.xml.makeSimpleNodeFactory = function(opt_nodeName, opt_namespaceURI) { + var fixedNodeName = opt_nodeName; + /** + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node} Node. + */ + return function(value, objectStack, opt_nodeName) { + var context = objectStack[objectStack.length - 1]; + var node = context.node; + goog.asserts.assert(ol.xml.isNode(node) || ol.xml.isDocument(node)); + var nodeName = fixedNodeName; + if (!goog.isDef(nodeName)) { + nodeName = opt_nodeName; + } + var namespaceURI = opt_namespaceURI; + if (!goog.isDef(opt_namespaceURI)) { + namespaceURI = node.namespaceURI; + } + goog.asserts.assert(goog.isDef(nodeName)); return ol.xml.createElementNS(namespaceURI, nodeName); }; }; +/** + * @const + * @type {function(*, Array.<*>, string=): Node} + */ +ol.xml.OBJECT_PROPERTY_NODE_FACTORY = ol.xml.makeSimpleNodeFactory(); + + /** * @param {Object.} object Key-value pairs for the sequence. * @param {Array.} orderedKeys Keys in the order of the sequence. From 1354d8ce78613a63e53818c7cb0fa57776d7b921 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 21 Feb 2014 16:55:48 +0100 Subject: [PATCH 04/11] Added rte serialization for ol.format.GPX --- src/ol/format/gpxformat.js | 204 ++++++++++++++++++-------- test/spec/ol/format/gpxformat.test.js | 12 +- 2 files changed, 151 insertions(+), 65 deletions(-) diff --git a/src/ol/format/gpxformat.js b/src/ol/format/gpxformat.js index b649df728b..85644ddb8f 100644 --- a/src/ol/format/gpxformat.js +++ b/src/ol/format/gpxformat.js @@ -423,6 +423,114 @@ ol.format.GPX.prototype.readProjectionFromNode = function(node) { }; +/** + * @param {Node} node Node. + * @param {string} value Value for the link's `href` attribute. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GPX.writeLink_ = function(node, value, objectStack) { + node.setAttribute('href', value); + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context)); + var properties = goog.object.get(context, 'properties'); + var link = [ + goog.object.get(properties, 'linkText'), + goog.object.get(properties, 'linkType') + ]; + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ ({node: node}), + ol.format.GPX.LINK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + link, objectStack, ol.format.GPX.LINK_SEQUENCE_); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {Array.<*>} objectStack Object stack. + * @private + * @template T + */ +ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context)); + var parentNode = context.node; + goog.asserts.assert(ol.xml.isNode(parentNode)); + var namespaceURI = parentNode.namespaceURI; + var properties = goog.object.get(context, 'properties'); + //FIXME Projection handling + ol.xml.setAttributeNS(node, null, 'lat', coordinate[1]); + ol.xml.setAttributeNS(node, null, 'lon', coordinate[0]); + var geometryLayout = goog.object.get(context, 'geometryLayout'); + switch (geometryLayout) { + case ol.geom.GeometryLayout.XYZM: + if (coordinate[3] !== 0) { + goog.object.set(properties, 'time', coordinate[3]); + } + case ol.geom.GeometryLayout.XYZ: + if (coordinate[2] !== 0) { + goog.object.set(properties, 'ele', coordinate[2]); + } + break; + case ol.geom.GeometryLayout.XYM: + if (coordinate[2] !== 0) { + goog.object.set(properties, 'time', coordinate[2]); + } + } + var orderedKeys = ol.format.GPX.WPT_TYPE_SEQUENCE_[namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ + ({node: node, 'properties': properties}), + ol.format.GPX.WPT_TYPE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Object stack. + * @private + * @template T + */ +ol.format.GPX.writeRte_ = function(node, feature, objectStack) { + var properties = feature.getProperties(); + var context = {node: node, 'properties': properties}; + var geometry = feature.getGeometry(); + if (goog.isDef(geometry)) { + goog.asserts.assertInstanceof(geometry, ol.geom.LineString); + goog.object.set(context, 'geometryLayout', geometry.getLayout()); + goog.object.set(properties, 'rtept', geometry.getCoordinates()); + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.GPX.RTE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context), + ol.format.GPX.RTE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Object stack. + * @private + * @template T + */ +ol.format.GPX.writeWpt_ = function(node, feature, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context)); + goog.object.set(context, 'properties', feature.getProperties()); + var geometry = feature.getGeometry(); + if (goog.isDef(geometry)) { + goog.asserts.assertInstanceof(geometry, ol.geom.Point); + goog.object.set(context, 'geometryLayout', geometry.getLayout()); + ol.format.GPX.writeWptType_(node, geometry.getCoordinates(), objectStack); + } +}; + + /** * @const * @type {Array.} @@ -445,23 +553,38 @@ ol.format.GPX.LINK_SERIALIZERS_ = ol.xml.makeStructureNS( /** - * @param {Node} node Node. - * @param {string} value Value for the link's `href` attribute. - * @param {Array.<*>} objectStack Node stack. + * @const + * @type {Object.>} * @private */ -ol.format.GPX.writeLink_ = function(node, value, objectStack) { - node.setAttribute('href', value); - var properties = objectStack[objectStack.length - 1].context; - var link = [ - goog.object.get(properties, 'linkText'), - goog.object.get(properties, 'linkType') - ]; - ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ ({node: node}), - ol.format.GPX.LINK_SERIALIZERS_, - ol.xml.makeChildNodeFactory(node.namespaceURI), link, objectStack, - ol.format.GPX.LINK_SEQUENCE_); -}; +ol.format.GPX.RTE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'rtept' + ]); + + +/** + * @const + * @type {Object.>} + * @private + */ +ol.format.GPX.RTE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'cmt': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'desc': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'src': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), + 'number': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeNonNegativeIntegerTextNode)), + 'type': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'rtept': ol.xml.makeChildrenAppender(ol.format.GPX.writeWptType_) + }); /** @@ -469,7 +592,7 @@ ol.format.GPX.writeLink_ = function(node, value, objectStack) { * @type {Object.>} * @private */ -ol.format.GPX.WPT_SEQUENCE_ = ol.xml.makeStructureNS( +ol.format.GPX.WPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS( ol.format.GPX.NAMESPACE_URIS_, [ 'ele', 'time', 'magvar', 'geoidheight', 'name', 'cmt', 'desc', 'src', 'link', 'sym', 'type', 'fix', 'sat', 'hdop', 'vdop', 'pdop', @@ -481,7 +604,7 @@ ol.format.GPX.WPT_SEQUENCE_ = ol.xml.makeStructureNS( * @type {Object.>} * @private */ -ol.format.GPX.WPT_SERIALIZERS_ = ol.xml.makeStructureNS( +ol.format.GPX.WPT_TYPE_SERIALIZERS_ = ol.xml.makeStructureNS( ol.format.GPX.NAMESPACE_URIS_, { 'ele': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( ol.format.XSD.writeDecimalTextNode)), @@ -553,51 +676,6 @@ ol.format.GPX.GPX_NODE_FACTORY_ = function(feature, objectStack, opt_nodeName) { }; -/** - * @param {Node} node Node. - * @param {ol.Feature} feature Feature. - * @param {Array.<*>} objectStack Object stack. - * @private - * @template T - */ -ol.format.GPX.writeWpt_ = function(node, feature, objectStack) { - var parentNode = objectStack[objectStack.length - 1].node; - goog.asserts.assert(ol.xml.isNode(parentNode)); - var namespaceURI = parentNode.namespaceURI; - var properties = feature.getProperties(); - var geometry = feature.getGeometry(); - if (goog.isDef(geometry)) { - goog.asserts.assert(geometry instanceof ol.geom.Point); - var coordinates = geometry.getCoordinates(); - //FIXME Projection handling - ol.xml.setAttributeNS(node, null, 'lat', coordinates[1]); - ol.xml.setAttributeNS(node, null, 'lon', coordinates[0]); - var geometryLayout = geometry.getLayout(); - switch (geometryLayout) { - case ol.geom.GeometryLayout.XYZM: - if (coordinates[3] !== 0) { - goog.object.set(properties, 'time', coordinates[3]); - } - case ol.geom.GeometryLayout.XYZ: - if (coordinates[2] !== 0) { - goog.object.set(properties, 'ele', coordinates[2]); - } - break; - case ol.geom.GeometryLayout.XYM: - if (coordinates[2] !== 0) { - goog.object.set(properties, 'time', coordinates[2]); - } - } - } - var orderedKeys = ol.format.GPX.WPT_SEQUENCE_[namespaceURI]; - var values = ol.xml.makeSequence(properties, orderedKeys); - ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ - ({node: node, context: properties}), ol.format.GPX.WPT_SERIALIZERS_, - ol.xml.makeChildNodeFactory(namespaceURI), values, objectStack, - orderedKeys); -}; - - /** * @const * @type {Object.>} @@ -605,7 +683,7 @@ ol.format.GPX.writeWpt_ = function(node, feature, objectStack) { */ ol.format.GPX.GPX_SERIALIZERS_ = ol.xml.makeStructureNS( ol.format.GPX.NAMESPACE_URIS_, { - //'LineString': ol.xml.makeChildAppender(ol.format.GPX.writeRte_), + 'rte': ol.xml.makeChildAppender(ol.format.GPX.writeRte_), //'MultiLineString': ol.xml.makeChildAppender(ol.format.GPX.writeTrk_), 'wpt': ol.xml.makeChildAppender(ol.format.GPX.writeWpt_) }); diff --git a/test/spec/ol/format/gpxformat.test.js b/test/spec/ol/format/gpxformat.test.js index 4da5a8d58c..8a2c14c1e5 100644 --- a/test/spec/ol/format/gpxformat.test.js +++ b/test/spec/ol/format/gpxformat.test.js @@ -12,6 +12,10 @@ describe('ol.format.GPX', function() { describe('rte', function() { + beforeEach(function() { + format = new ol.format.GPX.V1_1(); + }); + it('can read an empty rte', function() { var text = '' + @@ -27,7 +31,7 @@ describe('ol.format.GPX', function() { expect(g.getLayout()).to.be(ol.geom.GeometryLayout.XYZM); }); - it('can read various rte attributes', function() { + it('can read and write various rte attributes', function() { var text = '' + ' ' + @@ -56,9 +60,11 @@ describe('ol.format.GPX', function() { expect(f.get('linkType')).to.be('Link type'); expect(f.get('number')).to.be(1); expect(f.get('type')).to.be('Type'); + var serialized = format.writeFeatures(fs); + expect(serialized).to.xmleql(ol.xml.load(text)); }); - it('can read a rte with multiple rtepts', function() { + it('can read and write a rte with multiple rtepts', function() { var text = '' + ' ' + @@ -74,6 +80,8 @@ describe('ol.format.GPX', function() { expect(g).to.be.an(ol.geom.LineString); expect(g.getCoordinates()).to.eql([[2, 1, 0, 0], [4, 3, 0, 0]]); expect(g.getLayout()).to.be(ol.geom.GeometryLayout.XYZM); + var serialized = format.writeFeatures(fs); + expect(serialized).to.xmleql(ol.xml.load(text)); }); }); From 791cb2415b6fc5d691483e20a5444b43e587d0ce Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 21 Feb 2014 20:22:44 +0100 Subject: [PATCH 05/11] Simplify annotations --- src/ol/format/gpxformat.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ol/format/gpxformat.js b/src/ol/format/gpxformat.js index 85644ddb8f..08532fbd45 100644 --- a/src/ol/format/gpxformat.js +++ b/src/ol/format/gpxformat.js @@ -449,7 +449,6 @@ ol.format.GPX.writeLink_ = function(node, value, objectStack) { * @param {ol.Coordinate} coordinate Coordinate. * @param {Array.<*>} objectStack Object stack. * @private - * @template T */ ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) { var context = objectStack[objectStack.length - 1]; @@ -491,7 +490,6 @@ ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) { * @param {ol.Feature} feature Feature. * @param {Array.<*>} objectStack Object stack. * @private - * @template T */ ol.format.GPX.writeRte_ = function(node, feature, objectStack) { var properties = feature.getProperties(); @@ -658,15 +656,15 @@ ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = { /** * @const - * @param {*} feature Feature. + * @param {*} value Value. * @param {Array.<*>} objectStack Object stack. * @param {string=} opt_nodeName Node name. * @return {Node} Node. * @private */ -ol.format.GPX.GPX_NODE_FACTORY_ = function(feature, objectStack, opt_nodeName) { - goog.asserts.assertInstanceof(feature, ol.Feature); - var geometry = feature.getGeometry(); +ol.format.GPX.GPX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { + goog.asserts.assertInstanceof(value, ol.Feature); + var geometry = value.getGeometry(); if (goog.isDef(geometry)) { var parentNode = objectStack[objectStack.length - 1].node; goog.asserts.assert(ol.xml.isNode(parentNode)); From 480a6a85ba22f725e520610e7038edba8bcfc805 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 21 Feb 2014 20:27:04 +0100 Subject: [PATCH 06/11] Add trk serialization for ol.format.GPX --- src/ol/format/gpxformat.js | 98 ++++++++++++++++++++++++++- test/spec/ol/format/gpxformat.test.js | 30 ++++---- 2 files changed, 113 insertions(+), 15 deletions(-) diff --git a/src/ol/format/gpxformat.js b/src/ol/format/gpxformat.js index 08532fbd45..248e3cf104 100644 --- a/src/ol/format/gpxformat.js +++ b/src/ol/format/gpxformat.js @@ -514,7 +514,44 @@ ol.format.GPX.writeRte_ = function(node, feature, objectStack) { * @param {ol.Feature} feature Feature. * @param {Array.<*>} objectStack Object stack. * @private - * @template T + */ +ol.format.GPX.writeTrk_ = function(node, feature, objectStack) { + var properties = feature.getProperties(); + var context = {node: node, 'properties': properties}; + var geometry = feature.getGeometry(); + if (goog.isDef(geometry)) { + goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString); + goog.object.set(properties, 'trkseg', geometry.getLineStrings()); + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.GPX.TRK_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context), + ol.format.GPX.TRK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LineString} lineString LineString. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.writeTrkSeg_ = function(node, lineString, objectStack) { + var context = {node: node, 'geometryLayout': lineString.getLayout(), + 'properties': {}}; + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context), + ol.format.GPX.TRKSEG_SERIALIZERS_, ol.format.GPX.TRKSEG_NODE_FACTORY_, + lineString.getCoordinates(), objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Object stack. + * @private */ ol.format.GPX.writeWpt_ = function(node, feature, objectStack) { var context = objectStack[objectStack.length - 1]; @@ -585,6 +622,63 @@ ol.format.GPX.RTE_SERIALIZERS_ = ol.xml.makeStructureNS( }); +/** + * @const + * @type {Object.>} + * @private + */ +ol.format.GPX.TRK_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'trkseg' + ]); + + +/** + * @const + * @type {Object.>} + * @private + */ +ol.format.GPX.TRK_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'cmt': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'desc': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'src': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), + 'number': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeNonNegativeIntegerTextNode)), + 'type': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( + ol.format.XSD.writeStringTextNode)), + 'trkseg': ol.xml.makeChildrenAppender(ol.format.GPX.writeTrkSeg_) + }); + + +/** + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node} Node. + * @private + */ +ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt'); + + +/** + * @const + * @type {Object.>} + * @private + */ +ol.format.GPX.TRKSEG_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'trkpt': ol.xml.makeChildAppender(ol.format.GPX.writeWptType_) + }); + + /** * @const * @type {Object.>} @@ -682,7 +776,7 @@ ol.format.GPX.GPX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { ol.format.GPX.GPX_SERIALIZERS_ = ol.xml.makeStructureNS( ol.format.GPX.NAMESPACE_URIS_, { 'rte': ol.xml.makeChildAppender(ol.format.GPX.writeRte_), - //'MultiLineString': ol.xml.makeChildAppender(ol.format.GPX.writeTrk_), + 'trk': ol.xml.makeChildAppender(ol.format.GPX.writeTrk_), 'wpt': ol.xml.makeChildAppender(ol.format.GPX.writeWpt_) }); diff --git a/test/spec/ol/format/gpxformat.test.js b/test/spec/ol/format/gpxformat.test.js index 8a2c14c1e5..bb9fa2bc22 100644 --- a/test/spec/ol/format/gpxformat.test.js +++ b/test/spec/ol/format/gpxformat.test.js @@ -5,17 +5,13 @@ describe('ol.format.GPX', function() { var format; beforeEach(function() { - format = new ol.format.GPX(); + format = new ol.format.GPX.V1_1(); }); describe('readFeatures', function() { describe('rte', function() { - beforeEach(function() { - format = new ol.format.GPX.V1_1(); - }); - it('can read an empty rte', function() { var text = '' + @@ -103,7 +99,7 @@ describe('ol.format.GPX', function() { expect(g.getLayout()).to.be(ol.geom.GeometryLayout.XYZM); }); - it('can read various trk attributes', function() { + it('can read and write various trk attributes', function() { var text = '' + ' ' + @@ -132,9 +128,11 @@ describe('ol.format.GPX', function() { expect(f.get('linkType')).to.be('Link type'); expect(f.get('number')).to.be(1); expect(f.get('type')).to.be('Type'); + var serialized = format.writeFeatures(fs); + expect(serialized).to.xmleql(ol.xml.load(text)); }); - it('can read a trk with an empty trkseg', function() { + it('can read and write a trk with an empty trkseg', function() { var text = '' + ' ' + @@ -149,9 +147,11 @@ describe('ol.format.GPX', function() { expect(g).to.be.an(ol.geom.MultiLineString); expect(g.getCoordinates()).to.eql([[]]); expect(g.getLayout()).to.be(ol.geom.GeometryLayout.XYZM); + var serialized = format.writeFeatures(fs); + expect(serialized).to.xmleql(ol.xml.load(text)); }); - it('can read a trk with a trkseg with multiple trkpts', function() { + it('can read/write a trk with a trkseg with multiple trkpts', function() { var text = '' + ' ' + @@ -177,9 +177,11 @@ describe('ol.format.GPX', function() { [[2, 1, 3, 1263115752], [6, 5, 7, 1263115812]] ]); expect(g.getLayout()).to.be(ol.geom.GeometryLayout.XYZM); + var serialized = format.writeFeatures(fs); + expect(serialized).to.xmleql(ol.xml.load(text)); }); - it('can read a trk with multiple trksegs', function() { + it('can read and write a trk with multiple trksegs', function() { var text = '' + ' ' + @@ -216,16 +218,14 @@ describe('ol.format.GPX', function() { [[9, 8, 10, 1263115872], [12, 11, 13, 1263115932]] ]); expect(g.getLayout()).to.be(ol.geom.GeometryLayout.XYZM); + var serialized = format.writeFeatures(fs); + expect(serialized).to.xmleql(ol.xml.load(text)); }); }); describe('wpt', function() { - beforeEach(function() { - format = new ol.format.GPX.V1_1(); - }); - it('can read and write a wpt', function() { var text = '' + @@ -355,6 +355,10 @@ describe('ol.format.GPX', function() { describe('XML namespace support', function() { + beforeEach(function() { + format = new ol.format.GPX(); + }); + it('can read features with a version 1.0 namespace', function() { var text = '' + From 696425af369a683228fa347e39fd471fadcde209 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 21 Feb 2014 22:49:33 +0100 Subject: [PATCH 07/11] Documentation and cleanup --- src/ol/format/gpxformat.js | 105 +++++++++++++++---------------------- src/ol/xml.js | 101 +++++++++++++++++++++-------------- 2 files changed, 104 insertions(+), 102 deletions(-) diff --git a/src/ol/format/gpxformat.js b/src/ol/format/gpxformat.js index 248e3cf104..0c16ddbdbf 100644 --- a/src/ol/format/gpxformat.js +++ b/src/ol/format/gpxformat.js @@ -580,10 +580,8 @@ ol.format.GPX.LINK_SEQUENCE_ = ['text', 'type']; */ ol.format.GPX.LINK_SERIALIZERS_ = ol.xml.makeStructureNS( ol.format.GPX.NAMESPACE_URIS_, { - 'text': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'type': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)) + 'text': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) }); @@ -605,20 +603,16 @@ ol.format.GPX.RTE_SEQUENCE_ = ol.xml.makeStructureNS( */ ol.format.GPX.RTE_SERIALIZERS_ = ol.xml.makeStructureNS( ol.format.GPX.NAMESPACE_URIS_, { - 'name': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'cmt': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'desc': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'src': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), - 'number': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeNonNegativeIntegerTextNode)), - 'type': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'rtept': ol.xml.makeChildrenAppender(ol.format.GPX.writeWptType_) + 'number': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'rtept': ol.xml.makeArraySerializer(ol.xml.makeChildAppender( + ol.format.GPX.writeWptType_)) }); @@ -640,20 +634,16 @@ ol.format.GPX.TRK_SEQUENCE_ = ol.xml.makeStructureNS( */ ol.format.GPX.TRK_SERIALIZERS_ = ol.xml.makeStructureNS( ol.format.GPX.NAMESPACE_URIS_, { - 'name': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'cmt': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'desc': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'src': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), - 'number': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeNonNegativeIntegerTextNode)), - 'type': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'trkseg': ol.xml.makeChildrenAppender(ol.format.GPX.writeTrkSeg_) + 'number': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'trkseg': ol.xml.makeArraySerializer(ol.xml.makeChildAppender( + ol.format.GPX.writeTrkSeg_)) }); @@ -698,41 +688,28 @@ ol.format.GPX.WPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS( */ ol.format.GPX.WPT_TYPE_SERIALIZERS_ = ol.xml.makeStructureNS( ol.format.GPX.NAMESPACE_URIS_, { - 'ele': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeDecimalTextNode)), - 'time': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeDateTimeTextNode)), - 'magvar': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeDecimalTextNode)), - 'geoidheight': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeDecimalTextNode)), - 'name': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'cmt': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'desc': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'src': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), + 'ele': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'time': ol.xml.makeChildAppender(ol.format.XSD.writeDateTimeTextNode), + 'magvar': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'geoidheight': ol.xml.makeChildAppender( + ol.format.XSD.writeDecimalTextNode), + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), - 'sym': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'type': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'fix': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeStringTextNode)), - 'sat': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeNonNegativeIntegerTextNode)), - 'hdop': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeDecimalTextNode)), - 'vdop': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeDecimalTextNode)), - 'pdop': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeDecimalTextNode)), - 'ageofdgpsdata': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeDecimalTextNode)), - 'dgpsid': ol.xml.makeChildAppender(ol.xml.makeSimpleTypeWriter( - ol.format.XSD.writeNonNegativeIntegerTextNode)) + 'sym': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'fix': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'sat': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode), + 'hdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'vdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'pdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'ageofdgpsdata': ol.xml.makeChildAppender( + ol.format.XSD.writeDecimalTextNode), + 'dgpsid': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode) }); diff --git a/src/ol/xml.js b/src/ol/xml.js index c5dd382f52..e8034e8bfc 100644 --- a/src/ol/xml.js +++ b/src/ol/xml.js @@ -11,6 +11,9 @@ goog.require('goog.userAgent'); /** + * When using {@link ol.xml.makeChildAppender} or + * {@link ol.xml.makeSimpleNodeFactory}, the top `objectStack` item needs to + * have this structure. * @typedef {{node:Node}} */ ol.xml.NodeStackItem; @@ -29,6 +32,9 @@ ol.xml.Serializer; /** + * This document should be used when creating nodes for XML serializations. This + * document is also used by {@link ol.xml.createElementNS} and + * {@link ol.xml.setAttributeNS} * @const * @type {Document} */ @@ -358,9 +364,9 @@ ol.xml.makeParsersNS = function(namespaceURIs, parsers, opt_parsersNS) { /** - * Creates a serializer that appends its `nodeWriter`'s to its designated - * parent. The parent node is the `node` of the {@link ol.xml.NodeStackItem} at - * the top of the `objectStack`. + * Creates a serializer that appends nodes written by its `nodeWriter` to its + * designated parent. The parent is the `node` of the + * {@link ol.xml.NodeStackItem} at the top of the `objectStack`. * @param {function(this: T, Node, V, Array.<*>)} * nodeWriter Node writer. * @param {T=} opt_this The object to use as `this` in `nodeWriter`. @@ -381,37 +387,41 @@ ol.xml.makeChildAppender = function(nodeWriter, opt_this) { /** - * Creates a serializer that creates multiple nodes of the same name and appends - * them to the designated parent. The parent node is the `node` of the - * {@link ol.xml.NodeStackItem} at the top of the `objectStack`. + * Creates a serializer that calls the provided `nodeWriter` from + * {@link ol.xml.serialize}. This can be used by the parent writer to have the + * 'nodeWriter' called with an array of values when the `nodeWriter` was + * designed to serialize a single item. An example would be a LineString + * geometry writer, which could be reused for writing MultiLineString + * geometries. * @param {function(this: T, Node, V, Array.<*>)} * nodeWriter Node writer. * @param {T=} opt_this The object to use as `this` in `nodeWriter`. * @return {ol.xml.Serializer} Serializer. * @template T, V */ -ol.xml.makeChildrenAppender = function(nodeWriter, opt_this) { - var writer = ol.xml.makeChildAppender(nodeWriter); +ol.xml.makeArraySerializer = function(nodeWriter, opt_this) { var serializersNS, nodeFactory; return function(node, value, objectStack) { if (!goog.isDef(serializersNS)) { serializersNS = {}; var serializers = {}; - goog.object.set(serializers, node.localName, writer); + goog.object.set(serializers, node.localName, nodeWriter); goog.object.set(serializersNS, node.namespaceURI, serializers); nodeFactory = ol.xml.makeSimpleNodeFactory(node.localName); } - ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ - (objectStack[objectStack.length - 1]), serializersNS, nodeFactory, - value, objectStack); + ol.xml.serialize(serializersNS, nodeFactory, value, objectStack); }; }; /** + * Creates a node factory which can use the `opt_keys` passed to + * {@link ol.xml.serialize} or {@link ol.xml.pushSerializeAndPop} as node names, + * or a fixed node name. The namespace of the created nodes can either be fixed, + * or the parent namespace will be used. * @param {string=} opt_nodeName Fixed node name which will be used for all - * created nodes. If not provided, the nodeName will be the 3rd argument to - * the resulting node factory. + * created nodes. If not provided, the 3rd argument to the resulting node + * factory needs to be provided and will be the nodeName. * @param {string=} opt_namespaceURI Fixed namespace URI which will be used for * all created nodes. If not provided, the namespace of the parent node will * be used. @@ -444,6 +454,9 @@ ol.xml.makeSimpleNodeFactory = function(opt_nodeName, opt_namespaceURI) { /** + * A node factory that creates a node using the parent's `namespaceURI` and the + * `nodeName` passed by {@link ol.xml.serialize} or + * {@link ol.xml.pushSerializeAndPop} to the node factory. * @const * @type {function(*, Array.<*>, string=): Node} */ @@ -451,9 +464,15 @@ ol.xml.OBJECT_PROPERTY_NODE_FACTORY = ol.xml.makeSimpleNodeFactory(); /** - * @param {Object.} object Key-value pairs for the sequence. + * Creates an array of `values` to be used with {@link ol.xml.serialize} or + * {@link ol.xml.pushSerializeAndPop}, where `orderedKeys` has to be provided as + * `opt_key` argument. + * @param {Object.} object Key-value pairs for the sequence. Keys can + * be a subset of the `orderedKeys`. * @param {Array.} orderedKeys Keys in the order of the sequence. - * @return {Array.} Values in the order of the sequence. + * @return {Array.} Values in the order of the sequence. The resulting array + * has the same length as the `orderedKeys` array. Values that are not + * present in `object` will be `undefined` in the resulting array. * @template V */ ol.xml.makeSequence = function(object, orderedKeys) { @@ -467,20 +486,9 @@ ol.xml.makeSequence = function(object, orderedKeys) { /** - * @param {function(this: T, Node, V)} nodeValueSetter Function that sets - * the node value, like the `write*TextNode` functions from `ol.format.XSD`. - * @param {T=} opt_this `this` object for the node writer. - * @return {ol.xml.Serializer} Serializer. - * @template T, V - */ -ol.xml.makeSimpleTypeWriter = function(nodeValueSetter, opt_this) { - return function(node, value) { - nodeValueSetter.call(opt_this, node, value); - }; -}; - - -/** + * Creates a namespaced structure, using the same values for each namespace. + * This can be used as a starting point for versioned parsers, when only a few + * values are version specific. * @param {Array.} namespaceURIs Namespace URIs. * @param {T} structure Structure. * @param {Object.=} opt_structureNS Namespaced structure to add to. @@ -540,14 +548,23 @@ ol.xml.pushParseAndPop = function( /** + * Walks through an array of `values` and calls a serializer for each value. * @param {Object.>} serializersNS * Namespaced serializers. * @param {function(this: T, *, Array.<*>, (string|undefined)): Node|undefined} nodeFactory - * Node factory. - * @param {Array.<*>} values Values. + * Node factory. The `nodeFactory` creates the node whose namespace and name + * will be used to choose a node writer from `serializersNS`. This + * separation allows us to decide what kind of node to create, depending on + * the value we want to serialize. An example for this would be different + * geometry writers based on the geometry type. + * @param {Array.<*>} values Values to serialize. An example would be an array + * of {@link ol.Feature} instances. * @param {Array.<*>} objectStack Node stack. - * @param {Array.=} opt_keys Keys of the `values`, will be passed to the - * `nodeFactory`. + * @param {Array.=} opt_keys Keys of the `values`. Will be passed to the + * `nodeFactory`. This is used for serializing object literals where the + * node name relates to the property key. The array length of `opt_keys` has + * to match the length of `values`. For serializing a sequence, `opt_keys` + * determines the order of the sequence. * @param {T=} opt_this The object to use as `this` for the node factory and * serializers. * @template T @@ -575,11 +592,19 @@ ol.xml.serialize = function( * @param {Object.>} serializersNS * Namespaced serializers. * @param {function(this: T, *, Array.<*>, (string|undefined)): Node|undefined} nodeFactory - * Node factory. - * @param {Array.<*>} values Values. + * Node factory. The `nodeFactory` creates the node whose namespace and name + * will be used to choose a node writer from `serializersNS`. This + * separation allows us to decide what kind of node to create, depending on + * the value we want to serialize. An example for this would be different + * geometry writers based on the geometry type. + * @param {Array.<*>} values Values to serialize. An example would be an array + * of {@link ol.Feature} instances. * @param {Array.<*>} objectStack Node stack. - * @param {Array.=} opt_keys Keys of the `values`, will be passed to the - * `nodeFactory`. + * @param {Array.=} opt_keys Keys of the `values`. Will be passed to the + * `nodeFactory`. This is used for serializing object literals where the + * node name relates to the property key. The array length of `opt_keys` has + * to match the length of `values`. For serializing a sequence, `opt_keys` + * determines the order of the sequence. * @param {T=} opt_this The object to use as `this` for the node factory and * serializers. * @return {O|undefined} Object. From 7b56abdac7304c12063640b9cb04ba3cf863dd7f Mon Sep 17 00:00:00 2001 From: ahocevar Date: Sun, 23 Feb 2014 01:50:59 +0100 Subject: [PATCH 08/11] Get rid of compiler warnings --- src/ol/format/gpxformat.js | 6 +++--- src/ol/xml.js | 8 ++++---- test/spec/ol/format/gpxformat.test.js | 1 + 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ol/format/gpxformat.js b/src/ol/format/gpxformat.js index 0c16ddbdbf..034c1caf60 100644 --- a/src/ol/format/gpxformat.js +++ b/src/ol/format/gpxformat.js @@ -1,5 +1,5 @@ goog.provide('ol.format.GPX'); -goog.provide('ol.format.GPX.v1_1'); +goog.provide('ol.format.GPX.V1_1'); goog.require('goog.array'); goog.require('goog.asserts'); @@ -652,7 +652,7 @@ ol.format.GPX.TRK_SERIALIZERS_ = ol.xml.makeStructureNS( * @param {*} value Value. * @param {Array.<*>} objectStack Object stack. * @param {string=} opt_nodeName Node name. - * @return {Node} Node. + * @return {Node|undefined} Node. * @private */ ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt'); @@ -730,7 +730,7 @@ ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = { * @param {*} value Value. * @param {Array.<*>} objectStack Object stack. * @param {string=} opt_nodeName Node name. - * @return {Node} Node. + * @return {Node|undefined} Node. * @private */ ol.format.GPX.GPX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { diff --git a/src/ol/xml.js b/src/ol/xml.js index e8034e8bfc..f682ec64fc 100644 --- a/src/ol/xml.js +++ b/src/ol/xml.js @@ -425,7 +425,7 @@ ol.xml.makeArraySerializer = function(nodeWriter, opt_this) { * @param {string=} opt_namespaceURI Fixed namespace URI which will be used for * all created nodes. If not provided, the namespace of the parent node will * be used. - * @return {function(*, Array.<*>, string=): Node} Node factory. + * @return {function(*, Array.<*>, string=): (Node|undefined)} Node factory. */ ol.xml.makeSimpleNodeFactory = function(opt_nodeName, opt_namespaceURI) { var fixedNodeName = opt_nodeName; @@ -458,7 +458,7 @@ ol.xml.makeSimpleNodeFactory = function(opt_nodeName, opt_namespaceURI) { * `nodeName` passed by {@link ol.xml.serialize} or * {@link ol.xml.pushSerializeAndPop} to the node factory. * @const - * @type {function(*, Array.<*>, string=): Node} + * @type {function(*, Array.<*>, string=): (Node|undefined)} */ ol.xml.OBJECT_PROPERTY_NODE_FACTORY = ol.xml.makeSimpleNodeFactory(); @@ -551,7 +551,7 @@ ol.xml.pushParseAndPop = function( * Walks through an array of `values` and calls a serializer for each value. * @param {Object.>} serializersNS * Namespaced serializers. - * @param {function(this: T, *, Array.<*>, (string|undefined)): Node|undefined} nodeFactory + * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory * Node factory. The `nodeFactory` creates the node whose namespace and name * will be used to choose a node writer from `serializersNS`. This * separation allows us to decide what kind of node to create, depending on @@ -591,7 +591,7 @@ ol.xml.serialize = function( * @param {O} object Object. * @param {Object.>} serializersNS * Namespaced serializers. - * @param {function(this: T, *, Array.<*>, (string|undefined)): Node|undefined} nodeFactory + * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory * Node factory. The `nodeFactory` creates the node whose namespace and name * will be used to choose a node writer from `serializersNS`. This * separation allows us to decide what kind of node to create, depending on diff --git a/test/spec/ol/format/gpxformat.test.js b/test/spec/ol/format/gpxformat.test.js index bb9fa2bc22..5428bae0ba 100644 --- a/test/spec/ol/format/gpxformat.test.js +++ b/test/spec/ol/format/gpxformat.test.js @@ -401,6 +401,7 @@ describe('ol.format.GPX', function() { goog.require('ol.Feature'); goog.require('ol.format.GPX'); +goog.require('ol.format.GPX.V1_1'); goog.require('ol.geom.LineString'); goog.require('ol.geom.MultiLineString'); goog.require('ol.geom.Point'); From c1a966bb6fc24f7966a1bad4b080adffc1252150 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 24 Feb 2014 13:55:20 +0100 Subject: [PATCH 09/11] namespaceURI can be null, but not undefined --- src/ol/xml.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/xml.js b/src/ol/xml.js index f682ec64fc..6f3728b0c5 100644 --- a/src/ol/xml.js +++ b/src/ol/xml.js @@ -59,7 +59,7 @@ ol.xml.createElementNS_ = function(namespaceURI, qualifiedName) { * @private */ ol.xml.createElementNSActiveX_ = function(namespaceURI, qualifiedName) { - if (!goog.isDef(namespaceURI)) { + if (goog.isNull(namespaceURI)) { namespaceURI = ''; } return ol.xml.DOCUMENT.createNode(1, qualifiedName, namespaceURI); From bb38771096fd7f4f6d2992df4424c09e7f9f1706 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 24 Feb 2014 14:13:41 +0100 Subject: [PATCH 10/11] No magic for namespace URI --- src/ol/format/gpxformat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/format/gpxformat.js b/src/ol/format/gpxformat.js index 034c1caf60..6e67111182 100644 --- a/src/ol/format/gpxformat.js +++ b/src/ol/format/gpxformat.js @@ -775,7 +775,7 @@ goog.inherits(ol.format.GPX.V1_1, ol.format.GPX); */ ol.format.GPX.V1_1.prototype.writeFeaturesNode = function(features) { //FIXME Serialize metadata - var gpx = ol.xml.createElementNS(ol.format.GPX.NAMESPACE_URIS_[2], 'gpx'); + var gpx = ol.xml.createElementNS('http://www.topografix.com/GPX/1/1', 'gpx'); ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ ({node: gpx}), ol.format.GPX.GPX_SERIALIZERS_, ol.format.GPX.GPX_NODE_FACTORY_, features, []); From 3a560c9843309e2d37f8b0d22fc416c3d8ab2dfe Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 24 Feb 2014 14:14:13 +0100 Subject: [PATCH 11/11] Put jsdoc comment into effect --- src/ol/xml.js | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/ol/xml.js b/src/ol/xml.js index 6f3728b0c5..a0d9dde9c5 100644 --- a/src/ol/xml.js +++ b/src/ol/xml.js @@ -429,27 +429,29 @@ ol.xml.makeArraySerializer = function(nodeWriter, opt_this) { */ ol.xml.makeSimpleNodeFactory = function(opt_nodeName, opt_namespaceURI) { var fixedNodeName = opt_nodeName; - /** - * @param {*} value Value. - * @param {Array.<*>} objectStack Object stack. - * @param {string=} opt_nodeName Node name. - * @return {Node} Node. - */ - return function(value, objectStack, opt_nodeName) { - var context = objectStack[objectStack.length - 1]; - var node = context.node; - goog.asserts.assert(ol.xml.isNode(node) || ol.xml.isDocument(node)); - var nodeName = fixedNodeName; - if (!goog.isDef(nodeName)) { - nodeName = opt_nodeName; - } - var namespaceURI = opt_namespaceURI; - if (!goog.isDef(opt_namespaceURI)) { - namespaceURI = node.namespaceURI; - } - goog.asserts.assert(goog.isDef(nodeName)); - return ol.xml.createElementNS(namespaceURI, nodeName); - }; + return ( + /** + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node} Node. + */ + function(value, objectStack, opt_nodeName) { + var context = objectStack[objectStack.length - 1]; + var node = context.node; + goog.asserts.assert(ol.xml.isNode(node) || ol.xml.isDocument(node)); + var nodeName = fixedNodeName; + if (!goog.isDef(nodeName)) { + nodeName = opt_nodeName; + } + var namespaceURI = opt_namespaceURI; + if (!goog.isDef(opt_namespaceURI)) { + namespaceURI = node.namespaceURI; + } + goog.asserts.assert(goog.isDef(nodeName)); + return ol.xml.createElementNS(namespaceURI, nodeName); + } + ); };