Skip to content

Commit 94fa450

Browse files
committedNov 13, 2017
[WFS provider] Add NAMESPACES= parameter to GetFeature requests when needed (fixes #14685)
1 parent fe4f150 commit 94fa450

File tree

5 files changed

+154
-0
lines changed

5 files changed

+154
-0
lines changed
 

‎src/providers/wfs/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ INCLUDE_DIRECTORIES(SYSTEM
6666
${QTKEYCHAIN_INCLUDE_DIR}
6767
${GDAL_INCLUDE_DIR} # needed by qgsvectorfilewriter.h
6868
${SQLITE3_INCLUDE_DIR}
69+
${GDAL_INCLUDE_DIR}
6970
)
7071

7172
IF (WITH_GUI)

‎src/providers/wfs/qgswfscapabilities.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
#include "qgsogcutils.h"
2323
#include "qgssettings.h"
2424

25+
#include <cpl_minixml.h>
26+
2527
#include <QDomDocument>
2628
#include <QStringList>
2729

@@ -81,6 +83,35 @@ QString QgsWfsCapabilities::Capabilities::addPrefixIfNeeded( const QString &name
8183
return mapUnprefixedTypenameToPrefixedTypename[name];
8284
}
8385

86+
class CPLXMLTreeUniquePointer
87+
{
88+
public:
89+
//! Constructor
90+
explicit CPLXMLTreeUniquePointer( CPLXMLNode *data ) { the_data_ = data; }
91+
92+
//! Destructor
93+
~CPLXMLTreeUniquePointer()
94+
{
95+
if ( the_data_ ) CPLDestroyXMLNode( the_data_ );
96+
}
97+
98+
/**
99+
* Returns the node pointer/
100+
* Modifying the contents pointed to by the return is allowed.
101+
* @return the node pointer */
102+
CPLXMLNode *get() const { return the_data_; }
103+
104+
/**
105+
* Returns the node pointer/
106+
* Modifying the contents pointed to by the return is allowed.
107+
* @return the node pointer */
108+
CPLXMLNode *operator->() const { return get(); }
109+
110+
private:
111+
CPLXMLNode *the_data_;
112+
};
113+
114+
84115
void QgsWfsCapabilities::capabilitiesReplyFinished()
85116
{
86117
const QByteArray &buffer = mResponse;
@@ -98,6 +129,8 @@ void QgsWfsCapabilities::capabilitiesReplyFinished()
98129
return;
99130
}
100131

132+
CPLXMLTreeUniquePointer oCPLXML( CPLParseXMLString( buffer.constData() ) );
133+
101134
QDomElement doc = capabilitiesDocument.documentElement();
102135

103136
// handle exceptions
@@ -303,19 +336,56 @@ void QgsWfsCapabilities::capabilitiesReplyFinished()
303336
}
304337
}
305338

339+
// This is messy, but there's apparently no way to get the xmlns:ci attribute value with QDom API
340+
// in snippets like
341+
// <wfs:FeatureType xmlns:ci="http://www.interactive-instruments.de/namespaces/demo/cities/4.0/cities">
342+
// <wfs:Name>ci:City</wfs:Name>
343+
// so fallback to using GDAL XML parser for that...
344+
345+
CPLXMLNode *psFeatureTypeIter = nullptr;
346+
if ( oCPLXML.get() )
347+
{
348+
psFeatureTypeIter = CPLGetXMLNode( oCPLXML.get(), "=wfs:WFS_Capabilities.wfs:FeatureTypeList" );
349+
if ( psFeatureTypeIter )
350+
psFeatureTypeIter = psFeatureTypeIter->psChild;
351+
}
352+
306353
// get the <FeatureType> elements
307354
QDomNodeList featureTypeList = featureTypeListElem.elementsByTagName( QStringLiteral( "FeatureType" ) );
308355
for ( int i = 0; i < featureTypeList.size(); ++i )
309356
{
310357
FeatureType featureType;
311358
QDomElement featureTypeElem = featureTypeList.at( i ).toElement();
312359

360+
for ( ; psFeatureTypeIter; psFeatureTypeIter = psFeatureTypeIter->psNext )
361+
{
362+
if ( psFeatureTypeIter->eType != CXT_Element )
363+
continue;
364+
break;
365+
}
366+
313367
//Name
314368
QDomNodeList nameList = featureTypeElem.elementsByTagName( QStringLiteral( "Name" ) );
315369
if ( nameList.length() > 0 )
316370
{
317371
featureType.name = nameList.at( 0 ).toElement().text();
372+
373+
QgsDebugMsg( QString( "featureType.name = %1" ) . arg( featureType.name ) );
374+
if ( featureType.name.contains( ':' ) )
375+
{
376+
QString prefixOfTypename = featureType.name.section( ':', 0, 0 );
377+
378+
// for some Deegree servers that requires a NAMESPACES parameter for GetFeature
379+
if ( psFeatureTypeIter )
380+
{
381+
featureType.nameSpace = CPLGetXMLValue( psFeatureTypeIter, ( "xmlns:" + prefixOfTypename ).toUtf8().constData(), "" );
382+
}
383+
}
318384
}
385+
386+
if ( psFeatureTypeIter )
387+
psFeatureTypeIter = psFeatureTypeIter->psNext;
388+
319389
//Title
320390
QDomNodeList titleList = featureTypeElem.elementsByTagName( QStringLiteral( "Title" ) );
321391
if ( titleList.length() > 0 )

‎src/providers/wfs/qgswfscapabilities.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class QgsWfsCapabilities : public QgsWfsRequest
3838
FeatureType() = default;
3939

4040
QString name;
41+
QString nameSpace; // for some Deegree servers that requires a NAMESPACES parameter for GetFeature
4142
QString title;
4243
QString abstract;
4344
QList<QString> crslist; // first is default

‎src/providers/wfs/qgswfsfeatureiterator.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,26 @@ QUrl QgsWFSFeatureDownloader::buildURL( int startIndex, int maxFeatures, bool fo
191191
getFeatureUrl.addQueryItem( QStringLiteral( "VERSION" ), mShared->mWFSVersion );
192192

193193
QString typenames;
194+
QString namespaces;
194195
if ( mShared->mLayerPropertiesList.isEmpty() )
195196
{
196197
typenames = mShared->mURI.typeName();
198+
199+
// Add NAMESPACES parameter for server that declare a namespace in the FeatureType of GetCapabilities response
200+
// See https://issues.qgis.org/issues/14685
201+
Q_FOREACH ( const QgsWfsCapabilities::FeatureType &f, mShared->mCaps.featureTypes )
202+
{
203+
if ( f.name == typenames )
204+
{
205+
if ( !f.nameSpace.isEmpty() && f.name.contains( ':' ) )
206+
{
207+
QString prefixOfTypename = f.name.section( ':', 0, 0 );
208+
namespaces = "xmlns(" + prefixOfTypename + "," + f.nameSpace + ")";
209+
}
210+
break;
211+
}
212+
}
213+
197214
}
198215
else
199216
{
@@ -351,6 +368,12 @@ QUrl QgsWFSFeatureDownloader::buildURL( int startIndex, int maxFeatures, bool fo
351368
}
352369
}
353370

371+
if ( !namespaces.isEmpty() )
372+
{
373+
getFeatureUrl.addQueryItem( QStringLiteral( "NAMESPACES" ),
374+
namespaces );
375+
}
376+
354377
return getFeatureUrl;
355378
}
356379

‎tests/src/python/test_provider_wfs.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2571,6 +2571,65 @@ def testDeprecatedGML2GeometryDeclaration(self):
25712571
got = got_f[0].geometry().constGet()
25722572
self.assertEqual((got.x(), got.y()), (426858.0, 5427937.0))
25732573

2574+
def testGetFeatureWithNamespaces(self):
2575+
''' test https://issues.qgis.org/issues/14685 '''
2576+
2577+
endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_getfeature_with_namespaces'
2578+
2579+
with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=2.0.0'), 'wb') as f:
2580+
f.write("""
2581+
<wfs:WFS_Capabilities version="2.0.0" xmlns:wfs="http://www.opengis.net/wfs/2.0" xmlns:ows="http://www.opengis.net/ows/1.1">
2582+
<wfs:FeatureTypeList>
2583+
<wfs:FeatureType xmlns:my="http://my">
2584+
<wfs:Name>my:typename</wfs:Name>
2585+
<wfs:Title>Title</wfs:Title>
2586+
<wfs:Abstract>Abstract</wfs:Abstract>
2587+
<wfs:SRS>EPSG:32631</wfs:SRS>
2588+
<ows:WGS84BoundingBox>
2589+
<ows:LowerCorner>0 40</ows:LowerCorner>
2590+
<ows:UpperCorner>15 50</ows:UpperCorner>
2591+
</ows:WGS84BoundingBox>
2592+
</wfs:FeatureType>
2593+
</wfs:FeatureTypeList>
2594+
</wfs:WFS_Capabilities>""".encode('UTF-8'))
2595+
2596+
with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f:
2597+
f.write("""
2598+
<xsd:schema xmlns:my="http://my" xmlns:gml="http://www.opengis.net/gml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://my">
2599+
<xsd:import namespace="http://www.opengis.net/gml"/>
2600+
<xsd:complexType name="typenameType">
2601+
<xsd:complexContent>
2602+
<xsd:extension base="gml:AbstractFeatureType">
2603+
<xsd:sequence>
2604+
<xsd:element maxOccurs="1" minOccurs="0" name="intfield" nillable="true" type="xsd:int"/>
2605+
</xsd:sequence>
2606+
</xsd:extension>
2607+
</xsd:complexContent>
2608+
</xsd:complexType>
2609+
<xsd:element name="typename" substitutionGroup="gml:_Feature" type="my:typenameType"/>
2610+
</xsd:schema>
2611+
""".encode('UTF-8'))
2612+
2613+
vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='2.0.0'", 'test', 'WFS')
2614+
self.assertTrue(vl.isValid())
2615+
self.assertEqual(len(vl.fields()), 1)
2616+
2617+
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:
2618+
f.write("""
2619+
<wfs:FeatureCollection
2620+
xmlns:wfs="http://www.opengis.net/wfs/2.0"
2621+
xmlns:gml="http://www.opengis.net/gml/3.2"
2622+
xmlns:my="http://my">
2623+
<gml:featureMember>
2624+
<my:typename fid="typename.0">
2625+
<my:intfield>1</my:intfield>
2626+
</my:typename>
2627+
</gml:featureMember>
2628+
</wfs:FeatureCollection>""".encode('UTF-8'))
2629+
2630+
values = [f['intfield'] for f in vl.getFeatures()]
2631+
self.assertEqual(values, [1])
2632+
25742633

25752634
if __name__ == '__main__':
25762635
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.