diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 1f222add18..346688201a 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -346,6 +346,18 @@ * @property {ol.Extent|undefined} bbox Extent to use for the BBOX filter. */ +/** + * @typedef {Object} olx.format.WFSWriteTransactionOptions + * @property {string} featureNS The namespace URI used for features. + * @property {string} featurePrefix The prefix for the feature namespace. + * @property {string} featureType The feature type name. + * @property {string|undefined} srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @property {string|undefined} handle Handle. + * @property {Array.} nativeElements Native elements. Currently not + * supported. + */ + /** * @typedef {Object} olx.interaction.DoubleClickZoomOptions * @property {number|undefined} duration Animation duration in milliseconds. Default is `250`. diff --git a/src/ol/format/gmlformat.js b/src/ol/format/gmlformat.js index 291c65c772..d08b6e0e30 100644 --- a/src/ol/format/gmlformat.js +++ b/src/ol/format/gmlformat.js @@ -1419,9 +1419,8 @@ ol.format.GML.writeGeometry = function(node, geometry, objectStack) { * @param {Node} node Node. * @param {ol.Feature} feature Feature. * @param {Array.<*>} objectStack Node stack. - * @private */ -ol.format.GML.writeFeature_ = function(node, feature, objectStack) { +ol.format.GML.writeFeature = function(node, feature, objectStack) { var fid = feature.getId(); if (goog.isDef(fid)) { node.setAttribute('fid', fid); @@ -1437,17 +1436,20 @@ ol.format.GML.writeFeature_ = function(node, feature, objectStack) { var properties = feature.getProperties(); var keys = [], values = []; for (var key in properties) { - keys.push(key); - values.push(properties[key]); - if (key == geometryName) { - if (!(key in context.serializers[featureNS])) { - context.serializers[featureNS][key] = ol.xml.makeChildAppender( - ol.format.GML.writeGeometry); - } - } else { - if (!(key in context.serializers[featureNS])) { - context.serializers[featureNS][key] = ol.xml.makeChildAppender( - ol.format.XSD.writeStringTextNode); + var value = properties[key]; + if (!goog.isNull(value)) { + keys.push(key); + values.push(value); + if (key == geometryName) { + if (!(key in context.serializers[featureNS])) { + context.serializers[featureNS][key] = ol.xml.makeChildAppender( + ol.format.GML.writeGeometry); + } + } else { + if (!(key in context.serializers[featureNS])) { + context.serializers[featureNS][key] = ol.xml.makeChildAppender( + ol.format.XSD.writeStringTextNode); + } } } } @@ -1455,7 +1457,7 @@ ol.format.GML.writeFeature_ = function(node, feature, objectStack) { item.node = node; ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (item), context.serializers, - ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + ol.xml.makeSimpleNodeFactory(undefined, featureNS), values, objectStack, keys); }; @@ -1475,7 +1477,7 @@ ol.format.GML.writeFeatureMembers_ = function(node, features, objectStack) { var serializers = {}; serializers[featureNS] = {}; serializers[featureNS][featureType] = ol.xml.makeChildAppender( - ol.format.GML.writeFeature_); + ol.format.GML.writeFeature); var item = goog.object.clone(context); item.node = node; ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ diff --git a/src/ol/format/wfsformat.js b/src/ol/format/wfsformat.js index 72ee897470..2e9718c748 100644 --- a/src/ol/format/wfsformat.js +++ b/src/ol/format/wfsformat.js @@ -6,6 +6,7 @@ goog.require('goog.object'); goog.require('ol.format.GML'); goog.require('ol.format.XMLFeature'); goog.require('ol.format.XSD'); +goog.require('ol.geom.Geometry'); goog.require('ol.xml'); @@ -287,6 +288,124 @@ ol.format.WFS.QUERY_SERIALIZERS_ = { }; +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeFeature_ = function(node, feature, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context)); + var featureType = goog.object.get(context, 'featureType'); + var featureNS = goog.object.get(context, 'featureNS'); + var child = ol.xml.createElementNS(featureNS, featureType); + node.appendChild(child); + ol.format.GML.writeFeature(child, feature, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {number|string} fid Feature identifier. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeOgcFidFilter_ = function(node, fid, objectStack) { + var filter = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter'); + var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'FeatureId'); + filter.appendChild(child); + child.setAttribute('fid', fid); + node.appendChild(filter); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeDelete_ = function(node, feature, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context)); + var featureType = goog.object.get(context, 'featureType'); + var featurePrefix = goog.object.get(context, 'featurePrefix'); + node.setAttribute('typeName', featurePrefix + ':' + featureType); + var fid = feature.getId(); + if (goog.isDef(fid)) { + ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeUpdate_ = function(node, feature, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context)); + var featureType = goog.object.get(context, 'featureType'); + var featurePrefix = goog.object.get(context, 'featurePrefix'); + node.setAttribute('typeName', featurePrefix + ':' + featureType); + var fid = feature.getId(); + if (goog.isDef(fid)) { + var keys = feature.getKeys(); + var values = []; + for (var i = 0, ii = keys.length; i < ii; i++) { + var value = feature.get(keys[i]); + if (goog.isDef(value)) { + values.push({name: keys[i], value: value}); + } + } + ol.xml.pushSerializeAndPop({node: node}, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Property'), values, + objectStack); + ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {Object} pair Property name and value. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeProperty_ = function(node, pair, objectStack) { + var name = ol.xml.createElementNS('http://www.opengis.net/wfs', 'Name'); + node.appendChild(name); + ol.format.XSD.writeStringTextNode(name, pair.name); + if (goog.isDefAndNotNull(pair.value)) { + var value = ol.xml.createElementNS('http://www.opengis.net/wfs', 'Value'); + node.appendChild(value); + if (pair.value instanceof ol.geom.Geometry) { + ol.format.GML.writeGeometry(value, pair.value, objectStack); + } else { + ol.format.XSD.writeStringTextNode(value, pair.value); + } + } +}; + + +/** + * @type {Object.>} + * @private + */ +ol.format.WFS.TRANSACTION_SERIALIZERS_ = { + 'http://www.opengis.net/wfs': { + 'Insert': ol.xml.makeChildAppender(ol.format.WFS.writeFeature_), + 'Update': ol.xml.makeChildAppender(ol.format.WFS.writeUpdate_), + 'Delete': ol.xml.makeChildAppender(ol.format.WFS.writeDelete_), + 'Property': ol.xml.makeChildAppender(ol.format.WFS.writeProperty_) + } +}; + + /** * @param {Node} node Node. * @param {string} featureType Feature type. @@ -428,3 +547,48 @@ ol.format.WFS.prototype.writeGetFeature = function(options) { ol.format.WFS.writeGetFeature_(node, options.featureTypes, [context]); return node; }; + + +/** + * @param {Array.} inserts The features to insert. + * @param {Array.} updates The features to update. + * @param {Array.} deletes The features to delete. + * @param {olx.format.WFSWriteTransactionOptions} options Write options. + * @return {ArrayBuffer|Node|Object|string} Result. + */ +ol.format.WFS.prototype.writeTransaction = function(inserts, updates, deletes, + options) { + var node = ol.xml.createElementNS('http://www.opengis.net/wfs', + 'Transaction'); + node.setAttribute('service', 'WFS'); + node.setAttribute('version', '1.1.0'); + if (goog.isDef(options)) { + if (goog.isDef(options.handle)) { + node.setAttribute('handle', options.handle); + } + } + ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation', this.schemaLocation_); + if (goog.isDefAndNotNull(inserts)) { + ol.xml.pushSerializeAndPop({node: node, featureNS: options.featureNS, + featureType: options.featureType}, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Insert'), inserts, + []); + } + if (goog.isDefAndNotNull(updates)) { + ol.xml.pushSerializeAndPop({node: node, featureNS: options.featureNS, + featureType: options.featureType, featurePrefix: options.featurePrefix}, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Update'), updates, + []); + } + if (goog.isDefAndNotNull(deletes)) { + ol.xml.pushSerializeAndPop({node: node, featureNS: options.featureNS, + featureType: options.featureType, featurePrefix: options.featurePrefix}, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Delete'), deletes, + []); + } + return node; +}; diff --git a/old/test/spec/ol/parser/ogc/xml/wfs_v1/TransactionMulti.xml b/test/spec/ol/format/wfs/TransactionMulti.xml similarity index 84% rename from old/test/spec/ol/parser/ogc/xml/wfs_v1/TransactionMulti.xml rename to test/spec/ol/format/wfs/TransactionMulti.xml index 22c0886045..1154cb38c0 100644 --- a/old/test/spec/ol/parser/ogc/xml/wfs_v1/TransactionMulti.xml +++ b/test/spec/ol/format/wfs/TransactionMulti.xml @@ -1,11 +1,11 @@ - + - 1,2 + 1 2 @@ -20,7 +20,7 @@ - 1,2 + 1 2 @@ -42,4 +42,4 @@ - \ No newline at end of file + diff --git a/test/spec/ol/format/wfsformat.test.js b/test/spec/ol/format/wfsformat.test.js index cb500361c0..d6e13ef40b 100644 --- a/test/spec/ol/format/wfsformat.test.js +++ b/test/spec/ol/format/wfsformat.test.js @@ -176,9 +176,69 @@ describe('ol.format.WFS', function() { }); + describe('when writing out a Transaction request', function() { + + it('creates a handle', function() { + var text = + ''; + var serialized = new ol.format.WFS().writeTransaction(null, null, null, + {handle: 'handle_t'}); + expect(serialized).to.xmleql(ol.xml.load(text)); + }); + + }); + + describe('when writing out a Transaction request', function() { + var text; + before(function(done) { + afterLoadText('spec/ol/format/wfs/TransactionMulti.xml', function(xml) { + text = xml; + done(); + }); + }); + + it('creates the correct transaction body', function() { + var format = new ol.format.WFS(); + var insertFeature = new ol.Feature({ + the_geom: new ol.geom.MultiPoint([[1, 2]]), + foo: 'bar', + nul: null + }); + insertFeature.setGeometryName('the_geom'); + var inserts = [insertFeature]; + var updateFeature = new ol.Feature({ + the_geom: new ol.geom.MultiPoint([[1, 2]]), + foo: 'bar', + // null value gets Property element with no Value + nul: null, + // undefined value means don't create a Property element + unwritten: undefined + }); + updateFeature.setId('fid.42'); + var updates = [updateFeature]; + + var deleteFeature = new ol.Feature(); + deleteFeature.setId('fid.37'); + var deletes = [deleteFeature]; + var serialized = format.writeTransaction(inserts, updates, deletes, { + featureNS: 'http://www.openplans.org/topp', + featureType: 'states', + featurePrefix: 'topp' + }); + expect(serialized).to.xmleql(ol.xml.load(text)); + }); + + }); + }); goog.require('ol.xml'); +goog.require('ol.Feature'); +goog.require('ol.geom.MultiPoint'); goog.require('ol.geom.MultiPolygon'); goog.require('ol.format.WFS');