diff --git a/src/ol/format/kml.js b/src/ol/format/kml.js index 7e14f23dee..39dd52d934 100644 --- a/src/ol/format/kml.js +++ b/src/ol/format/kml.js @@ -2278,6 +2278,54 @@ ol.format.KML.writeCoordinatesTextNode_ = function(node, coordinates, objectStac }; +/** + * @param {Node} node Node. + * @param {{name: *, value: *}} pair Name value pair. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeDataNode_ = function(node, pair, objectStack) { + node.setAttribute('name', pair.name); + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var value = pair.value; + + if (typeof value == 'object') { + if (value !== null && value.displayName) { + ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value.displayName], objectStack, ['displayName']); + } + + if (value !== null && value.value) { + ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value.value], objectStack, ['value']); + } + } else { + ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value], objectStack, ['value']); + } +}; + + +/** + * @param {Node} node Node to append a TextNode with the name to. + * @param {string} name DisplayName. + * @private + */ +ol.format.KML.writeDataNodeName_ = function(node, name) { + ol.format.XSD.writeCDATASection(node, name); +}; + + +/** + * @param {Node} node Node to append a CDATA Section with the value to. + * @param {string} value Value. + * @private + */ +ol.format.KML.writeDataNodeValue_ = function(node, value) { + ol.format.XSD.writeStringTextNode(node, value); +}; + + /** * @param {Node} node Node. * @param {Array.} features Features. @@ -2293,6 +2341,24 @@ ol.format.KML.writeDocument_ = function(node, features, objectStack) { }; +/** + * @param {Node} node Node. + * @param {{names: Array, values: (Array<*>)}} namesAndValues Names and values. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeExtendedData_ = function(node, namesAndValues, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var names = namesAndValues.names, values = namesAndValues.values; + var length = names.length; + + for (var i = 0; i < length; i++) { + ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_, + ol.format.KML.DATA_NODE_FACTORY_, [{name: names[i], value: values[i]}], objectStack); + } +}; + + /** * @param {Node} node Node. * @param {Object} icon Icon object. @@ -2490,6 +2556,21 @@ ol.format.KML.writePlacemark_ = function(node, feature, objectStack) { // serialize properties (properties unknown to KML are not serialized) var properties = feature.getProperties(); + // don't export these to ExtendedData + var filter = {'address': 1, 'description': 1, 'name': 1, 'open': 1, + 'phoneNumber': 1, 'styleUrl': 1, 'visibility': 1}; + filter[feature.getGeometryName()] = 1; + var keys = Object.keys(properties || {}).sort().filter(function(v) { + return !filter[v]; + }); + + if (keys.length > 0) { + var sequence = ol.xml.makeSequence(properties, keys); + var namesAndValues = {names: keys, values: sequence}; + ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_, + ol.format.KML.EXTENDEDDATA_NODE_FACTORY_, [namesAndValues], objectStack); + } + var styleFunction = feature.getStyleFunction(); if (styleFunction) { // FIXME the styles returned by the style function are supposed to be @@ -2678,6 +2759,19 @@ ol.format.KML.DOCUMENT_SERIALIZERS_ = ol.xml.makeStructureNS( }); +/** + * @const + * @type {Object.>} + * @private + */ +ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Data': ol.xml.makeChildAppender(ol.format.KML.writeDataNode_), + 'value': ol.xml.makeChildAppender(ol.format.KML.writeDataNodeValue_), + 'displayName': ol.xml.makeChildAppender(ol.format.KML.writeDataNodeName_) + }); + + /** * @const * @type {Object.} @@ -2845,6 +2939,8 @@ ol.format.KML.PLACEMARK_SEQUENCE_ = ol.xml.makeStructureNS( */ ol.format.KML.PLACEMARK_SERIALIZERS_ = ol.xml.makeStructureNS( ol.format.KML.NAMESPACE_URIS_, { + 'ExtendedData': ol.xml.makeChildAppender( + ol.format.KML.writeExtendedData_), 'MultiGeometry': ol.xml.makeChildAppender( ol.format.KML.writeMultiGeometry_), 'LineString': ol.xml.makeChildAppender( @@ -3000,6 +3096,26 @@ ol.format.KML.COORDINATES_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('coordinates'); +/** + * A factory for creating Data nodes. + * @const + * @type {function(*, Array.<*>): (Node|undefined)} + * @private + */ +ol.format.KML.DATA_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('Data'); + + +/** + * A factory for creating ExtendedData nodes. + * @const + * @type {function(*, Array.<*>): (Node|undefined)} + * @private + */ +ol.format.KML.EXTENDEDDATA_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('ExtendedData'); + + /** * A factory for creating innerBoundaryIs nodes. * @const diff --git a/src/ol/format/xsd.js b/src/ol/format/xsd.js index 32bff8a168..8fc439f1d7 100644 --- a/src/ol/format/xsd.js +++ b/src/ol/format/xsd.js @@ -114,6 +114,15 @@ ol.format.XSD.writeBooleanTextNode = function(node, bool) { }; +/** + * @param {Node} node Node to append a CDATA Section with the string to. + * @param {string} string String. + */ +ol.format.XSD.writeCDATASection = function(node, string) { + node.appendChild(ol.xml.DOCUMENT.createCDATASection(string)); +}; + + /** * @param {Node} node Node to append a TextNode with the dateTime to. * @param {number} dateTime DateTime in seconds. diff --git a/test/spec/ol/format/kml.test.js b/test/spec/ol/format/kml.test.js index d899f84a68..035b51222a 100644 --- a/test/spec/ol/format/kml.test.js +++ b/test/spec/ol/format/kml.test.js @@ -1488,6 +1488,83 @@ describe('ol.format.KML', function() { describe('extended data', function() { + it('can write ExtendedData with no values', function() { + var feature = new ol.Feature(); + feature.set('foo', null); + feature.set('bar', undefined); + var features = [feature]; + var node = format.writeFeaturesNode(features); + var text = + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + expect(node).to.xmleql(ol.xml.parse(text)); + }); + + it('can write ExtendedData with values', function() { + var feature = new ol.Feature(); + feature.set('foo', 'bar'); + feature.set('aNumber', 1000); + var features = [feature]; + var node = format.writeFeaturesNode(features); + var text = + '' + + ' ' + + ' ' + + ' ' + + ' 1000' + + ' ' + + ' ' + + ' bar' + + ' ' + + ' ' + + ' ' + + ''; + expect(node).to.xmleql(ol.xml.parse(text)); + }); + + it('can write ExtendedData pair with displayName and value', function() { + var pair = { + value: 'bar', + displayName: 'display name' + }; + + var feature = new ol.Feature(); + feature.set('foo', pair); + + var features = [feature]; + var node = format.writeFeaturesNode(features); + var text = + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' bar' + + ' ' + + ' ' + + ' ' + + ''; + expect(node).to.xmleql(ol.xml.parse(text)); + }); + it('can read ExtendedData', function() { var text = '' +