diff --git a/src/server/services/wfs/qgswfsgetfeature.cpp b/src/server/services/wfs/qgswfsgetfeature.cpp index 0ffc3a05a25..cac1a5c3a2b 100644 --- a/src/server/services/wfs/qgswfsgetfeature.cpp +++ b/src/server/services/wfs/qgswfsgetfeature.cpp @@ -69,6 +69,8 @@ namespace QgsWfs QString createFeatureGeoJSON( const QgsFeature &feature, const createFeatureParams ¶ms, const QgsAttributeList &pkAttributes ); + QDomElement createFieldElement( const QgsField &field, const QVariant &value, QDomDocument &doc ); + QString encodeValueToText( const QVariant &value, const QgsEditorWidgetSetup &setup ); QDomElement createFeatureGML2( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams ¶ms, const QgsProject *project, const QgsAttributeList &pkAttributes ); @@ -1412,8 +1414,8 @@ namespace QgsWfs } //read all attribute values from the feature - QgsAttributes featureAttributes = feature.attributes(); - QgsFields fields = feature.fields(); + const QgsAttributes featureAttributes = feature.attributes(); + const QgsFields fields = feature.fields(); for ( int i = 0; i < params.attributeIndexes.count(); ++i ) { int idx = params.attributeIndexes[i]; @@ -1421,17 +1423,8 @@ namespace QgsWfs { continue; } - const QgsField field = fields.at( idx ); - const QgsEditorWidgetSetup setup = field.editorWidgetSetup(); - QString attributeName = field.name(); - QDomElement fieldElem = doc.createElement( "qgs:" + attributeName.replace( ' ', '_' ).replace( cleanTagNameRegExp, QString() ) ); - QDomText fieldText = doc.createTextNode( encodeValueToText( featureAttributes[idx], setup ) ); - if ( featureAttributes[idx].isNull() ) - { - fieldElem.setAttribute( QStringLiteral( "xsi:nil" ), QStringLiteral( "true" ) ); - } - fieldElem.appendChild( fieldText ); + const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc ); typeNameElement.appendChild( fieldElem ); } @@ -1444,7 +1437,7 @@ namespace QgsWfs QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ ); //qgs:%TYPENAME% - QDomElement typeNameElement = doc.createElement( "qgs:" + params.typeName /*qgs:%TYPENAME%*/ ); + QDomElement typeNameElement = doc.createElement( QStringLiteral( "qgs:" ) + params.typeName /*qgs:%TYPENAME%*/ ); QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) ); typeNameElement.setAttribute( QStringLiteral( "gml:id" ), id ); featureElement.appendChild( typeNameElement ); @@ -1518,8 +1511,8 @@ namespace QgsWfs } //read all attribute values from the feature - QgsAttributes featureAttributes = feature.attributes(); - QgsFields fields = feature.fields(); + const QgsAttributes featureAttributes = feature.attributes(); + const QgsFields fields = feature.fields(); for ( int i = 0; i < params.attributeIndexes.count(); ++i ) { int idx = params.attributeIndexes[i]; @@ -1528,24 +1521,38 @@ namespace QgsWfs continue; } - const QgsField field = fields.at( idx ); - const QgsEditorWidgetSetup setup = field.editorWidgetSetup(); - - QString attributeName = field.name(); - - QDomElement fieldElem = doc.createElement( "qgs:" + attributeName.replace( ' ', '_' ).replace( cleanTagNameRegExp, QString() ) ); - QDomText fieldText = doc.createTextNode( encodeValueToText( featureAttributes[idx], setup ) ); - if ( featureAttributes[idx].isNull() ) - { - fieldElem.setAttribute( QStringLiteral( "xsi:nil" ), QStringLiteral( "true" ) ); - } - fieldElem.appendChild( fieldText ); + const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc ); typeNameElement.appendChild( fieldElem ); } return featureElement; } + QDomElement createFieldElement( const QgsField &field, const QVariant &value, QDomDocument &doc ) + { + const QgsEditorWidgetSetup setup = field.editorWidgetSetup(); + const QString attributeName = field.name().replace( ' ', '_' ).replace( cleanTagNameRegExp, QString() ); + QDomElement fieldElem = doc.createElement( QStringLiteral( "qgs:" ) + attributeName ); + if ( value.isNull() ) + { + fieldElem.setAttribute( QStringLiteral( "xsi:nil" ), QStringLiteral( "true" ) ); + } + else + { + const QString fieldText = encodeValueToText( value, setup ); + //do we need CDATA + if ( fieldText.indexOf( '<' ) != -1 || fieldText.indexOf( '&' ) != -1 ) + { + fieldElem.appendChild( doc.createCDATASection( fieldText ) ); + } + else + { + fieldElem.appendChild( doc.createTextNode( fieldText ) ); + } + } + return fieldElem; + } + QString encodeValueToText( const QVariant &value, const QgsEditorWidgetSetup &setup ) { if ( value.isNull() ) @@ -1590,27 +1597,11 @@ namespace QgsWfs case QVariant::StringList: case QVariant::List: case QVariant::Map: - { - QString v = QgsJsonUtils::encodeValue( value ); - - //do we need CDATA - if ( v.indexOf( '<' ) != -1 || v.indexOf( '&' ) != -1 ) - v.prepend( QStringLiteral( "" ) ); - - return v; - } + return QgsJsonUtils::encodeValue( value ); default: case QVariant::String: - { - QString v = value.toString(); - - //do we need CDATA - if ( v.indexOf( '<' ) != -1 || v.indexOf( '&' ) != -1 ) - v.prepend( QStringLiteral( "" ) ); - - return v; - } + return value.toString(); } } diff --git a/tests/src/python/test_qgsserver.py b/tests/src/python/test_qgsserver.py index f7fdde153fe..3194fa28ce3 100644 --- a/tests/src/python/test_qgsserver.py +++ b/tests/src/python/test_qgsserver.py @@ -77,7 +77,10 @@ class QgsServerTestBase(unittest.TestCase): for diff in difflib.unified_diff([l.decode('utf8') for l in expected_lines], [l.decode('utf8') for l in response_lines]): diffs.append(diff) - self.assertEqual(len(expected_lines), len(response_lines), "Expected and response have different number of lines!\n{}\n{}".format(msg, '\n'.join(diffs))) + self.assertEqual( + len(expected_lines), + len(response_lines), + "Expected and response have different number of lines!\n{}\n{}\nWe got :\n{}".format(msg, '\n'.join(diffs), '\n'.join([i.decode("utf-8") for i in response_lines]))) for expected_line in expected_lines: expected_line = expected_line.strip() response_line = response_lines[line_no - 1].strip() diff --git a/tests/src/python/test_qgsserver_wfs.py b/tests/src/python/test_qgsserver_wfs.py index 0f46ec2e8ce..a2c33723a9f 100644 --- a/tests/src/python/test_qgsserver_wfs.py +++ b/tests/src/python/test_qgsserver_wfs.py @@ -599,6 +599,15 @@ class TestQgsServerWFS(QgsServerTestBase): self.wfs_request_compare("DescribeFeatureType", '1.1.0', "TYPENAME=does_not_exist&", 'wfs_describeFeatureType_1_1_0_typename_wrong', project_file=project_file) + def test_GetFeature_with_cdata(self): + """ Test GetFeature with CDATA.""" + self.wfs_request_compare( + "GetFeature", + "1.0.0", + "TYPENAME=test_layer_wfs_cdata_lines&", + 'wfs_getfeature_cdata', + project_file="test_layer_wfs_cdata.qgs") + def test_describeFeatureTypeVirtualFields(self): """Test DescribeFeatureType with virtual fields: bug GH-29767""" diff --git a/tests/testdata/qgis_server/test_layer_wfs_cdata.geojson b/tests/testdata/qgis_server/test_layer_wfs_cdata.geojson new file mode 100644 index 00000000000..3ba2c1f184b --- /dev/null +++ b/tests/testdata/qgis_server/test_layer_wfs_cdata.geojson @@ -0,0 +1,11 @@ +{ +"type": "FeatureCollection", +"name": "test_lines", +"features": [ +{ "type": "Feature", "properties": { "id": 1, "name": "éù%@ > 1", "comment": "Accents, sup" }, "geometry": { "type": "LineString", "coordinates": [ [ 3.8, 43.5 ], [ 3.8, 43.6 ] ] } }, +{ "type": "Feature", "properties": { "id": 2, "name": "Line > 2", "comment": "Normal, sup" }, "geometry": { "type": "LineString", "coordinates": [ [ 3.8, 43.6 ], [ 3.9, 43.6 ] ] } }, +{ "type": "Feature", "properties": { "id": 3, "name": "Line < 3", "comment": "Normal, inf" }, "geometry": { "type": "LineString", "coordinates": [ [ 3.9, 43.6 ], [ 3.9, 43.5 ] ] } }, +{ "type": "Feature", "properties": { "id": 4, "name": "05200", "comment": "Trailing 0" }, "geometry": { "type": "LineString", "coordinates": [ [ 3.9, 43.5 ], [ 3.8, 43.5 ] ] } }, +{ "type": "Feature", "properties": { "id": 5, "name": "Line & 2", "comment": "And sign" }, "geometry": { "type": "LineString", "coordinates": [ [ 3.8, 43.5 ], [ 3.9, 43.6 ] ] } } +] +} diff --git a/tests/testdata/qgis_server/test_layer_wfs_cdata.qgs b/tests/testdata/qgis_server/test_layer_wfs_cdata.qgs new file mode 100644 index 00000000000..2ab31b1a1d6 --- /dev/null +++ b/tests/testdata/qgis_server/test_layer_wfs_cdata.qgs @@ -0,0 +1,749 @@ + + + + + + + + + + GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["unknown"],AREA["World"],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + + + + + + + test_layer_wfs_cdata_87717b19_220a_4b1f_929b_f235a2684e04 + + + + + + + + + + degrees + + 3.73648648648648551 + 43.42950540540540061 + 3.94648648648648637 + 43.63950540540540857 + + 0 + + + GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["unknown"],AREA["World"],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + 0 + + + + + + + + + + + + degrees + + 0 + 0 + 0 + 0 + + 0 + + + GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["unknown"],AREA["World"],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + 0 + + + + degrees + + 0 + 0 + 0 + 0 + + 0 + + + GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["unknown"],AREA["World"],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + 0 + + + + degrees + + 0 + 0 + 0 + 0 + + 0 + + + GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["unknown"],AREA["World"],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + 0 + + + + Annotations_2b1f373f_e430_478b_8daf_29bbfec5b911 + + + + + Annotations + + + + + 0 + 0 + + + + + false + + + + + + + + + + + + + + + + + 0 + 0 + + + + + false + + + + + + 1 + + + + + 3.79999999999999982 + 43.5 + 3.89999999999999991 + 43.60000000000000142 + + test_layer_wfs_cdata_87717b19_220a_4b1f_929b_f235a2684e04 + ./test_layer_wfs_cdata.geojson + + + + test_layer_wfs_cdata_lines + + + GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["unknown"],AREA["World"],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + + + + + dataset + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + false + + + + + + + + + + + + + ogr + + + + + + + + + + 1 + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "name" + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + generatedlayout + + + + + + + + + + + + + "name" + + + + + + + + + + + + + 1 + true + + + 0 + + + 255 + 255 + 255 + 255 + 0 + 255 + 255 + + + false + + + + + + EPSG:7030 + + + m2 + meters + + + 5 + 2.5 + false + false + 1 + 0 + false + false + true + 0 + 255,0,0,255 + + + false + + + true + 2 + MU + + + 1 + + + + + test_layer_wfs_cdata_87717b19_220a_4b1f_929b_f235a2684e04 + + + 8 + + + + + + + + None + false + + + + + + 1 + conditions unknown + 90 + + + + 1 + + 8 + testcdata + false + + true + + 0 + + false + + + + + + + + false + + + + + false + + 5000 + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + Etienne Trimaille + 2022-03-09T13:39:55 + + + + + + + + + GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["unknown"],AREA["World"],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + + + + + + + + diff --git a/tests/testdata/qgis_server/wfs_getfeature_cdata_1_0_0.txt b/tests/testdata/qgis_server/wfs_getfeature_cdata_1_0_0.txt new file mode 100644 index 00000000000..f57390b89a5 --- /dev/null +++ b/tests/testdata/qgis_server/wfs_getfeature_cdata_1_0_0.txt @@ -0,0 +1,94 @@ +Content-Type: text/xml; subtype=gml/2.1.2; charset=utf-8 + + + + + 3.8,43.5 3.9,43.6 + + + + + + + 3.8,43.5 3.8,43.6 + + + + + 3.8,43.5 3.8,43.6 + + + 1 + éù%@ > 1 + Accents, sup + + + + + + + 3.8,43.6 3.9,43.6 + + + + + 3.8,43.6 3.9,43.6 + + + 2 + Line > 2 + Normal, sup + + + + + + + 3.9,43.5 3.9,43.6 + + + + + 3.9,43.6 3.9,43.5 + + + 3 + + Normal, inf + + + + + + + 3.8,43.5 3.9,43.5 + + + + + 3.9,43.5 3.8,43.5 + + + 4 + 05200 + Trailing 0 + + + + + + + 3.8,43.5 3.9,43.6 + + + + + 3.8,43.5 3.9,43.6 + + + 5 + + And sign + + +