Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit dadc4d5

Browse files
committedSep 14, 2021
WFS: set namespace prefix on geometry and attribute elements in FILTER when the typename has an explicit namespace (fixes #43957)
1 parent ce7bc16 commit dadc4d5

File tree

7 files changed

+187
-3
lines changed

7 files changed

+187
-3
lines changed
 

‎src/core/qgsogcutils.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1984,6 +1984,20 @@ QDomElement QgsOgcUtils::SQLStatementToOgcFilter( const QgsSQLStatement &stateme
19841984
attr.setValue( GML_NAMESPACE );
19851985
filterElem.setAttributeNode( attr );
19861986
}
1987+
1988+
QSet<QString> setNamespaceURI;
1989+
for ( const LayerProperties &props : layerProperties )
1990+
{
1991+
if ( !props.mNamespacePrefix.isEmpty() && !props.mNamespaceURI.isEmpty() &&
1992+
!setNamespaceURI.contains( props.mNamespaceURI ) )
1993+
{
1994+
setNamespaceURI.insert( props.mNamespaceURI );
1995+
QDomAttr attr = doc.createAttribute( QStringLiteral( "xmlns:" ) + props.mNamespacePrefix );
1996+
attr.setValue( props.mNamespaceURI );
1997+
filterElem.setAttributeNode( attr );
1998+
}
1999+
}
2000+
19872001
filterElem.appendChild( exprRootElem );
19882002
return filterElem;
19892003
}
@@ -2611,7 +2625,13 @@ QDomElement QgsOgcUtilsSQLStatementToFilter::toOgcFilter( const QgsSQLStatement:
26112625
{
26122626
QDomElement propElem = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
26132627
if ( node->tableName().isEmpty() || mLayerProperties.size() == 1 )
2614-
propElem.appendChild( mDoc.createTextNode( node->name() ) );
2628+
{
2629+
if ( mLayerProperties.size() == 1 && !mLayerProperties[0].mNamespacePrefix.isEmpty() )
2630+
propElem.appendChild( mDoc.createTextNode(
2631+
mLayerProperties[0].mNamespacePrefix + QStringLiteral( ":" ) + node->name() ) );
2632+
else
2633+
propElem.appendChild( mDoc.createTextNode( node->name() ) );
2634+
}
26152635
else
26162636
{
26172637
QString tableName( mMapTableAliasToNames[node->tableName()] );

‎src/core/qgsogcutils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ class CORE_EXPORT QgsOgcUtils
250250
QString mGeometryAttribute;
251251
//! SRS name
252252
QString mSRSName;
253+
//! Namespace prefix
254+
QString mNamespacePrefix;
255+
//! Namespace URI
256+
QString mNamespaceURI;
253257
};
254258
#endif
255259

‎src/providers/wfs/qgswfscapabilities.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ QString QgsWfsCapabilities::Capabilities::getNamespaceParameterValue( const QStr
109109
bool tryNameSpacing = ( !namespaces.isEmpty() && typeName.contains( ':' ) );
110110
if ( tryNameSpacing )
111111
{
112-
QString prefixOfTypename = typeName.section( ':', 0, 0 );
112+
QString prefixOfTypename = QgsWFSUtils::nameSpacePrefix( typeName );
113113
return "xmlns(" + prefixOfTypename +
114114
( WFSVersion.startsWith( QLatin1String( "2.0" ) ) ? "," : "=" ) +
115115
namespaces + ")";

‎src/providers/wfs/qgswfsfeatureiterator.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,20 @@ QUrl QgsWFSFeatureDownloaderImpl::buildURL( qint64 startIndex, int maxFeatures,
121121
}
122122
else
123123
{
124-
Q_FOREACH ( const QgsOgcUtils::LayerProperties layerProperties, mShared->mLayerPropertiesList )
124+
QSet<QString> setNamespaces;
125+
for ( const QgsOgcUtils::LayerProperties &layerProperties : qgis::as_const( mShared->mLayerPropertiesList ) )
125126
{
126127
if ( !typenames.isEmpty() )
127128
typenames += QLatin1Char( ',' );
128129
typenames += layerProperties.mName;
130+
const QString lNamespace = mShared->mCaps.getNamespaceParameterValue( mShared->mWFSVersion, layerProperties.mName );
131+
if ( !lNamespace.isEmpty() && !setNamespaces.contains( lNamespace ) )
132+
{
133+
if ( !namespaces.isEmpty() )
134+
namespaces += QLatin1Char( ',' );
135+
namespaces += lNamespace;
136+
setNamespaces.insert( lNamespace );
137+
}
129138
}
130139
}
131140
if ( mShared->mWFSVersion.startsWith( QLatin1String( "2.0" ) ) )
@@ -196,6 +205,8 @@ QUrl QgsWFSFeatureDownloaderImpl::buildURL( qint64 startIndex, int maxFeatures,
196205
QString geometryAttribute( mShared->mGeometryAttribute );
197206
if ( mShared->mLayerPropertiesList.size() > 1 )
198207
geometryAttribute = mShared->mURI.typeName() + "/" + geometryAttribute;
208+
else if ( mShared->mLayerPropertiesList.size() == 1 && !mShared->mLayerPropertiesList[0].mNamespacePrefix.isEmpty() )
209+
geometryAttribute = mShared->mLayerPropertiesList[0].mNamespacePrefix + QStringLiteral( ":" ) + geometryAttribute;
199210
QDomElement bboxElem = QgsOgcUtils::expressionToOgcFilter( bboxExp, doc,
200211
gmlVersion, filterVersion, geometryAttribute, mShared->srsName(),
201212
honourAxisOrientation, mShared->mURI.invertAxisOrientation() );
@@ -213,6 +224,19 @@ QUrl QgsWFSFeatureDownloaderImpl::buildURL( qint64 startIndex, int maxFeatures,
213224
andElem.appendChild( filterNode );
214225
doc.firstChildElement().appendChild( andElem );
215226

227+
QSet<QString> setNamespaceURI;
228+
for ( const QgsOgcUtils::LayerProperties &props : qgis::as_const( mShared->mLayerPropertiesList ) )
229+
{
230+
if ( !props.mNamespacePrefix.isEmpty() && !props.mNamespaceURI.isEmpty() &&
231+
!setNamespaceURI.contains( props.mNamespaceURI ) )
232+
{
233+
setNamespaceURI.insert( props.mNamespaceURI );
234+
QDomAttr attr = doc.createAttribute( QStringLiteral( "xmlns:" ) + props.mNamespacePrefix );
235+
attr.setValue( props.mNamespaceURI );
236+
doc.firstChildElement().setAttributeNode( attr );
237+
}
238+
}
239+
216240
query.addQueryItem( QStringLiteral( "FILTER" ), sanitizeFilter( doc.toString() ) );
217241
}
218242
else if ( !rect.isNull() )

‎src/providers/wfs/qgswfsprovider.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,12 @@ bool QgsWFSProvider::processSQL( const QString &sqlString, QString &errorMsg, QS
487487
if ( typeName == mShared->mURI.typeName() )
488488
layerProperties.mSRSName = mShared->srsName();
489489

490+
if ( typeName.contains( ':' ) )
491+
{
492+
layerProperties.mNamespaceURI = mShared->mCaps.getNamespaceForTypename( typeName );
493+
layerProperties.mNamespacePrefix = QgsWFSUtils::nameSpacePrefix( typeName );
494+
}
495+
490496
mShared->mLayerPropertiesList << layerProperties;
491497
}
492498

‎tests/src/core/testqgsogcutils.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,23 @@ void TestQgsOgcUtils::testSQLStatementToOgcFilter_data()
11921192
"</fes:PropertyIsEqualTo>"
11931193
"</fes:And>"
11941194
"</fes:Filter>" );
1195+
1196+
QList<QgsOgcUtils::LayerProperties> layerPropertiesWithNameSpace;
1197+
QgsOgcUtils::LayerProperties props;
1198+
props.mName = QStringLiteral( "prefix:mylayer" );
1199+
props.mNamespacePrefix = QStringLiteral( "prefix" );
1200+
props.mNamespaceURI = QStringLiteral( "http://example.com/prefix" );
1201+
layerPropertiesWithNameSpace << props;
1202+
1203+
QTest::newRow( "namespace" ) << QStringLiteral( "SELECT * FROM mylayer WHERE NAME = 'New York'" ) <<
1204+
QgsOgcUtils::GML_3_2_1 << QgsOgcUtils::FILTER_FES_2_0 << layerPropertiesWithNameSpace <<
1205+
QString(
1206+
"<fes:Filter xmlns:fes=\"http://www.opengis.net/fes/2.0\" xmlns:prefix=\"http://example.com/prefix\">"
1207+
"<fes:PropertyIsEqualTo>"
1208+
"<fes:ValueReference>prefix:NAME</fes:ValueReference>"
1209+
"<fes:Literal>New York</fes:Literal>"
1210+
"</fes:PropertyIsEqualTo>"
1211+
"</fes:Filter>" );
11951212
}
11961213

11971214
QGSTEST_MAIN( TestQgsOgcUtils )

‎tests/src/python/test_provider_wfs.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3301,6 +3301,119 @@ def testGetFeatureWithNamespaces(self):
33013301
values = [f['intfield'] for f in vl.getFeatures()]
33023302
self.assertEqual(values, [1])
33033303

3304+
def testGetFeatureWithNamespaceAndFilter(self):
3305+
''' test https://github.com/qgis/QGIS/issues/43957 '''
3306+
3307+
endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_getfeature_with_namespace_and_filter'
3308+
3309+
with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=2.0.0'), 'wb') as f:
3310+
f.write("""
3311+
<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">
3312+
<wfs:FeatureTypeList>
3313+
<wfs:FeatureType xmlns:my="http://my">
3314+
<wfs:Name>my:typename</wfs:Name>
3315+
<wfs:Title>Title</wfs:Title>
3316+
<wfs:Abstract>Abstract</wfs:Abstract>
3317+
<wfs:SRS>EPSG:32631</wfs:SRS>
3318+
<WGS84BoundingBox>
3319+
<LowerCorner>0 40</LowerCorner>
3320+
<UpperCorner>15 50</UpperCorner>
3321+
</WGS84BoundingBox>
3322+
</wfs:FeatureType>
3323+
</wfs:FeatureTypeList>
3324+
</wfs:WFS_Capabilities>""".encode('UTF-8'))
3325+
3326+
with open(sanitize(endpoint,
3327+
'?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&NAMESPACES=xmlns(my,http://my)&TYPENAME=my:typename&NAMESPACE=xmlns(my,http://my)'),
3328+
'wb') as f:
3329+
f.write("""
3330+
<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">
3331+
<xsd:import namespace="http://www.opengis.net/gml"/>
3332+
<xsd:complexType name="typenameType">
3333+
<xsd:complexContent>
3334+
<xsd:extension base="gml:AbstractFeatureType">
3335+
<xsd:sequence>
3336+
<xsd:element maxOccurs="1" minOccurs="0" name="geometryProperty" nillable="true" type="gml:PointPropertyType"/>
3337+
<xsd:element maxOccurs="1" minOccurs="0" name="intfield" nillable="true" type="xsd:int"/>
3338+
</xsd:sequence>
3339+
</xsd:extension>
3340+
</xsd:complexContent>
3341+
</xsd:complexType>
3342+
<xsd:element name="typename" substitutionGroup="gml:_Feature" type="my:typenameType"/>
3343+
</xsd:schema>
3344+
""".encode('UTF-8'))
3345+
3346+
# SQL query with type with namespace
3347+
vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='2.0.0' sql=SELECT * FROM \"my:typename\" WHERE intfield = 1", 'test', 'WFS')
3348+
self.assertTrue(vl.isValid())
3349+
self.assertEqual(len(vl.fields()), 1)
3350+
3351+
with open(sanitize(endpoint,
3352+
"""?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::32631&FILTER=<fes:Filter xmlns:fes="http://www.opengis.net/fes/2.0" xmlns:my="http://my">
3353+
<fes:PropertyIsEqualTo>
3354+
<fes:ValueReference>my:intfield</fes:ValueReference>
3355+
<fes:Literal>1</fes:Literal>
3356+
</fes:PropertyIsEqualTo>
3357+
</fes:Filter>
3358+
&NAMESPACES=xmlns(my,http://my)&NAMESPACE=xmlns(my,http://my)"""),
3359+
'wb') as f:
3360+
f.write("""
3361+
<wfs:FeatureCollection
3362+
xmlns:wfs="http://www.opengis.net/wfs/2.0"
3363+
xmlns:gml="http://www.opengis.net/gml/3.2"
3364+
xmlns:my="http://my">
3365+
<gml:featureMember>
3366+
<my:typename fid="typename.0">
3367+
<my:intfield>1</my:intfield>
3368+
</my:typename>
3369+
</gml:featureMember>
3370+
</wfs:FeatureCollection>""".encode('UTF-8'))
3371+
3372+
values = [f['intfield'] for f in vl.getFeatures()]
3373+
self.assertEqual(values, [1])
3374+
3375+
# SQL query with type with namespace and bounding box
3376+
vl = QgsVectorLayer(
3377+
"url='http://" + endpoint + "' typename='my:typename' version='2.0.0' restrictToRequestBBOX=1 sql=SELECT * FROM \"my:typename\" WHERE intfield > 0", 'test',
3378+
'WFS')
3379+
3380+
extent = QgsRectangle(400000.0, 5400000.0, 450000.0, 5500000.0)
3381+
request = QgsFeatureRequest().setFilterRect(extent)
3382+
3383+
with open(sanitize(endpoint,
3384+
"""?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::32631&FILTER=<fes:Filter xmlns:fes="http://www.opengis.net/fes/2.0" xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:my="http://my">
3385+
<fes:And>
3386+
<fes:BBOX>
3387+
<fes:ValueReference>my:geometryProperty</fes:ValueReference>
3388+
<gml:Envelope srsName="urn:ogc:def:crs:EPSG::32631">
3389+
<gml:lowerCorner>400000 5400000</gml:lowerCorner>
3390+
<gml:upperCorner>450000 5500000</gml:upperCorner>
3391+
</gml:Envelope>
3392+
</fes:BBOX>
3393+
<fes:PropertyIsGreaterThan xmlns:fes="http://www.opengis.net/fes/2.0">
3394+
<fes:ValueReference>my:intfield</fes:ValueReference>
3395+
<fes:Literal xmlns:fes="http://www.opengis.net/fes/2.0">0</fes:Literal>
3396+
</fes:PropertyIsGreaterThan>
3397+
</fes:And>
3398+
</fes:Filter>
3399+
&NAMESPACES=xmlns(my,http://my)&NAMESPACE=xmlns(my,http://my)"""),
3400+
'wb') as f:
3401+
f.write("""
3402+
<wfs:FeatureCollection
3403+
xmlns:wfs="http://www.opengis.net/wfs/2.0"
3404+
xmlns:gml="http://www.opengis.net/gml/3.2"
3405+
xmlns:my="http://my">
3406+
<gml:featureMember>
3407+
<my:typename fid="typename.0">
3408+
<my:geometryProperty><gml:Point srsName="urn:ogc:def:crs:EPSG::32631" gml:id="typename.geom.0"><gml:pos>426858 5427937</gml:pos></gml:Point></my:geometryProperty>
3409+
<my:intfield>1</my:intfield>
3410+
</my:typename>
3411+
</gml:featureMember>
3412+
</wfs:FeatureCollection>""".encode('UTF-8'))
3413+
3414+
values = [f['intfield'] for f in vl.getFeatures(request)]
3415+
self.assertEqual(values, [1])
3416+
33043417
def testExtentSubsetString(self):
33053418
# can't run the base provider test suite here - WFS/OAPIF extent handling is different
33063419
# to other providers

0 commit comments

Comments
 (0)
Please sign in to comment.