[WFS provider] Add NAMESPACES= parameter to GetFeature requests when needed (fixes #14685)

This commit is contained in:
Even Rouault 2017-11-13 21:23:57 +01:00
parent fe4f1500d9
commit 94fa450efd
5 changed files with 154 additions and 0 deletions

View File

@ -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)

View File

@ -22,6 +22,8 @@
#include "qgsogcutils.h"
#include "qgssettings.h"
#include <cpl_minixml.h>
#include <QDomDocument>
#include <QStringList>
@ -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
// <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 )
@ -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 )

View File

@ -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

View File

@ -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;
}

View File

@ -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("""
<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()