Skip to content

Commit

Permalink
[WFS provider] Add NAMESPACES= parameter to GetFeature requests when …
Browse files Browse the repository at this point in the history
…needed (fixes #14685)
  • Loading branch information
rouault committed Nov 13, 2017
1 parent fe4f150 commit 94fa450
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/providers/wfs/CMakeLists.txt
Expand Up @@ -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)
Expand Down
70 changes: 70 additions & 0 deletions src/providers/wfs/qgswfscapabilities.cpp
Expand Up @@ -22,6 +22,8 @@
#include "qgsogcutils.h"
#include "qgssettings.h"

#include <cpl_minixml.h>

#include <QDomDocument>
#include <QStringList>

Expand Down Expand Up @@ -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;
Expand All @@ -98,6 +129,8 @@ void QgsWfsCapabilities::capabilitiesReplyFinished()
return;
}

CPLXMLTreeUniquePointer oCPLXML( CPLParseXMLString( buffer.constData() ) );

QDomElement doc = capabilitiesDocument.documentElement();

// handle exceptions
Expand Down Expand Up @@ -303,19 +336,56 @@ 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
// <wfs:FeatureType xmlns:ci="http://www.interactive-instruments.de/namespaces/demo/cities/4.0/cities">
// <wfs:Name>ci:City</wfs:Name>
// 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 <FeatureType> elements
QDomNodeList featureTypeList = featureTypeListElem.elementsByTagName( QStringLiteral( "FeatureType" ) );
for ( int i = 0; i < featureTypeList.size(); ++i )
{
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 )
Expand Down
1 change: 1 addition & 0 deletions src/providers/wfs/qgswfscapabilities.h
Expand Up @@ -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<QString> crslist; // first is default
Expand Down
23 changes: 23 additions & 0 deletions src/providers/wfs/qgswfsfeatureiterator.cpp
Expand Up @@ -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
{
Expand Down Expand Up @@ -351,6 +368,12 @@ QUrl QgsWFSFeatureDownloader::buildURL( int startIndex, int maxFeatures, bool fo
}
}

if ( !namespaces.isEmpty() )
{
getFeatureUrl.addQueryItem( QStringLiteral( "NAMESPACES" ),
namespaces );
}

return getFeatureUrl;
}

Expand Down
59 changes: 59 additions & 0 deletions tests/src/python/test_provider_wfs.py
Expand Up @@ -2571,6 +2571,65 @@ def testDeprecatedGML2GeometryDeclaration(self):
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("""
<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">
<wfs:FeatureTypeList>
<wfs:FeatureType xmlns:my="http://my">
<wfs:Name>my:typename</wfs:Name>
<wfs:Title>Title</wfs:Title>
<wfs:Abstract>Abstract</wfs:Abstract>
<wfs:SRS>EPSG:32631</wfs:SRS>
<ows:WGS84BoundingBox>
<ows:LowerCorner>0 40</ows:LowerCorner>
<ows:UpperCorner>15 50</ows:UpperCorner>
</ows:WGS84BoundingBox>
</wfs:FeatureType>
</wfs:FeatureTypeList>
</wfs:WFS_Capabilities>""".encode('UTF-8'))

with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f:
f.write("""
<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">
<xsd:import namespace="http://www.opengis.net/gml"/>
<xsd:complexType name="typenameType">
<xsd:complexContent>
<xsd:extension base="gml:AbstractFeatureType">
<xsd:sequence>
<xsd:element maxOccurs="1" minOccurs="0" name="intfield" nillable="true" type="xsd:int"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:element name="typename" substitutionGroup="gml:_Feature" type="my:typenameType"/>
</xsd:schema>
""".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("""
<wfs:FeatureCollection
xmlns:wfs="http://www.opengis.net/wfs/2.0"
xmlns:gml="http://www.opengis.net/gml/3.2"
xmlns:my="http://my">
<gml:featureMember>
<my:typename fid="typename.0">
<my:intfield>1</my:intfield>
</my:typename>
</gml:featureMember>
</wfs:FeatureCollection>""".encode('UTF-8'))

values = [f['intfield'] for f in vl.getFeatures()]
self.assertEqual(values, [1])


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

0 comments on commit 94fa450

Please sign in to comment.