From 94fa450efdf6de2165f2c9c99dfba04c96b1380e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 13 Nov 2017 21:23:57 +0100 Subject: [PATCH] [WFS provider] Add NAMESPACES= parameter to GetFeature requests when needed (fixes #14685) --- src/providers/wfs/CMakeLists.txt | 1 + src/providers/wfs/qgswfscapabilities.cpp | 70 +++++++++++++++++++++ src/providers/wfs/qgswfscapabilities.h | 1 + src/providers/wfs/qgswfsfeatureiterator.cpp | 23 +++++++ tests/src/python/test_provider_wfs.py | 59 +++++++++++++++++ 5 files changed, 154 insertions(+) diff --git a/src/providers/wfs/CMakeLists.txt b/src/providers/wfs/CMakeLists.txt index d69e02eebb9..3c894827e0a 100644 --- a/src/providers/wfs/CMakeLists.txt +++ b/src/providers/wfs/CMakeLists.txt @@ -66,6 +66,7 @@ INCLUDE_DIRECTORIES(SYSTEM ${QTKEYCHAIN_INCLUDE_DIR} ${GDAL_INCLUDE_DIR} # needed by qgsvectorfilewriter.h ${SQLITE3_INCLUDE_DIR} + ${GDAL_INCLUDE_DIR} ) IF (WITH_GUI) diff --git a/src/providers/wfs/qgswfscapabilities.cpp b/src/providers/wfs/qgswfscapabilities.cpp index 49531f2d76b..cc5984f9caa 100644 --- a/src/providers/wfs/qgswfscapabilities.cpp +++ b/src/providers/wfs/qgswfscapabilities.cpp @@ -22,6 +22,8 @@ #include "qgsogcutils.h" #include "qgssettings.h" +#include + #include #include @@ -81,6 +83,35 @@ QString QgsWfsCapabilities::Capabilities::addPrefixIfNeeded( const QString &name return mapUnprefixedTypenameToPrefixedTypename[name]; } +class CPLXMLTreeUniquePointer +{ + public: + //! Constructor + explicit CPLXMLTreeUniquePointer( CPLXMLNode *data ) { the_data_ = data; } + + //! Destructor + ~CPLXMLTreeUniquePointer() + { + if ( the_data_ ) CPLDestroyXMLNode( the_data_ ); + } + + /** + * Returns the node pointer/ + * Modifying the contents pointed to by the return is allowed. + * @return the node pointer */ + CPLXMLNode *get() const { return the_data_; } + + /** + * Returns the node pointer/ + * Modifying the contents pointed to by the return is allowed. + * @return the node pointer */ + CPLXMLNode *operator->() const { return get(); } + + private: + CPLXMLNode *the_data_; +}; + + void QgsWfsCapabilities::capabilitiesReplyFinished() { const QByteArray &buffer = mResponse; @@ -98,6 +129,8 @@ void QgsWfsCapabilities::capabilitiesReplyFinished() return; } + CPLXMLTreeUniquePointer oCPLXML( CPLParseXMLString( buffer.constData() ) ); + QDomElement doc = capabilitiesDocument.documentElement(); // handle exceptions @@ -303,6 +336,20 @@ void QgsWfsCapabilities::capabilitiesReplyFinished() } } + // This is messy, but there's apparently no way to get the xmlns:ci attribute value with QDom API + // in snippets like + // + // ci:City + // so fallback to using GDAL XML parser for that... + + CPLXMLNode *psFeatureTypeIter = nullptr; + if ( oCPLXML.get() ) + { + psFeatureTypeIter = CPLGetXMLNode( oCPLXML.get(), "=wfs:WFS_Capabilities.wfs:FeatureTypeList" ); + if ( psFeatureTypeIter ) + psFeatureTypeIter = psFeatureTypeIter->psChild; + } + // get the elements QDomNodeList featureTypeList = featureTypeListElem.elementsByTagName( QStringLiteral( "FeatureType" ) ); for ( int i = 0; i < featureTypeList.size(); ++i ) @@ -310,12 +357,35 @@ void QgsWfsCapabilities::capabilitiesReplyFinished() FeatureType featureType; QDomElement featureTypeElem = featureTypeList.at( i ).toElement(); + for ( ; psFeatureTypeIter; psFeatureTypeIter = psFeatureTypeIter->psNext ) + { + if ( psFeatureTypeIter->eType != CXT_Element ) + continue; + break; + } + //Name QDomNodeList nameList = featureTypeElem.elementsByTagName( QStringLiteral( "Name" ) ); if ( nameList.length() > 0 ) { featureType.name = nameList.at( 0 ).toElement().text(); + + QgsDebugMsg( QString( "featureType.name = %1" ) . arg( featureType.name ) ); + if ( featureType.name.contains( ':' ) ) + { + QString prefixOfTypename = featureType.name.section( ':', 0, 0 ); + + // for some Deegree servers that requires a NAMESPACES parameter for GetFeature + if ( psFeatureTypeIter ) + { + featureType.nameSpace = CPLGetXMLValue( psFeatureTypeIter, ( "xmlns:" + prefixOfTypename ).toUtf8().constData(), "" ); + } + } } + + if ( psFeatureTypeIter ) + psFeatureTypeIter = psFeatureTypeIter->psNext; + //Title QDomNodeList titleList = featureTypeElem.elementsByTagName( QStringLiteral( "Title" ) ); if ( titleList.length() > 0 ) diff --git a/src/providers/wfs/qgswfscapabilities.h b/src/providers/wfs/qgswfscapabilities.h index f7c24026283..1bfd9f9e419 100644 --- a/src/providers/wfs/qgswfscapabilities.h +++ b/src/providers/wfs/qgswfscapabilities.h @@ -38,6 +38,7 @@ class QgsWfsCapabilities : public QgsWfsRequest FeatureType() = default; QString name; + QString nameSpace; // for some Deegree servers that requires a NAMESPACES parameter for GetFeature QString title; QString abstract; QList crslist; // first is default diff --git a/src/providers/wfs/qgswfsfeatureiterator.cpp b/src/providers/wfs/qgswfsfeatureiterator.cpp index 3e2f2d63fd6..0860113a128 100644 --- a/src/providers/wfs/qgswfsfeatureiterator.cpp +++ b/src/providers/wfs/qgswfsfeatureiterator.cpp @@ -191,9 +191,26 @@ QUrl QgsWFSFeatureDownloader::buildURL( int startIndex, int maxFeatures, bool fo getFeatureUrl.addQueryItem( QStringLiteral( "VERSION" ), mShared->mWFSVersion ); QString typenames; + QString namespaces; if ( mShared->mLayerPropertiesList.isEmpty() ) { typenames = mShared->mURI.typeName(); + + // Add NAMESPACES parameter for server that declare a namespace in the FeatureType of GetCapabilities response + // See https://issues.qgis.org/issues/14685 + Q_FOREACH ( const QgsWfsCapabilities::FeatureType &f, mShared->mCaps.featureTypes ) + { + if ( f.name == typenames ) + { + if ( !f.nameSpace.isEmpty() && f.name.contains( ':' ) ) + { + QString prefixOfTypename = f.name.section( ':', 0, 0 ); + namespaces = "xmlns(" + prefixOfTypename + "," + f.nameSpace + ")"; + } + break; + } + } + } else { @@ -351,6 +368,12 @@ QUrl QgsWFSFeatureDownloader::buildURL( int startIndex, int maxFeatures, bool fo } } + if ( !namespaces.isEmpty() ) + { + getFeatureUrl.addQueryItem( QStringLiteral( "NAMESPACES" ), + namespaces ); + } + return getFeatureUrl; } diff --git a/tests/src/python/test_provider_wfs.py b/tests/src/python/test_provider_wfs.py index 5ab06fcb9ea..0f3dce732db 100644 --- a/tests/src/python/test_provider_wfs.py +++ b/tests/src/python/test_provider_wfs.py @@ -2571,6 +2571,65 @@ class TestPyQgsWFSProvider(unittest.TestCase, ProviderTestCase): got = got_f[0].geometry().constGet() self.assertEqual((got.x(), got.y()), (426858.0, 5427937.0)) + def testGetFeatureWithNamespaces(self): + ''' test https://issues.qgis.org/issues/14685 ''' + + endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_getfeature_with_namespaces' + + with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=2.0.0'), 'wb') as f: + f.write(""" + + + + my:typename + Title + Abstract + EPSG:32631 + + 0 40 + 15 50 + + + +""".encode('UTF-8')) + + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: + f.write(""" + + + + + + + + + + + + + +""".encode('UTF-8')) + + vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='2.0.0'", 'test', 'WFS') + self.assertTrue(vl.isValid()) + self.assertEqual(len(vl.fields()), 1) + + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::32631&NAMESPACES=xmlns(my,http://my)'), 'wb') as f: + f.write(""" + + + + 1 + + +""".encode('UTF-8')) + + values = [f['intfield'] for f in vl.getFeatures()] + self.assertEqual(values, [1]) + if __name__ == '__main__': unittest.main()