Merge branch 'master' into model-designer-update

This commit is contained in:
jonathanlurie 2025-05-27 09:15:58 +02:00
commit 5b7c964178
16 changed files with 507 additions and 88 deletions

4
debian/rules vendored
View File

@ -43,7 +43,7 @@ endif
QT_PLUGINS_DIR = lib/$(DEB_BUILD_MULTIARCH)/qt5/plugins
ifneq ($(DISTRIBUTION),$(findstring $(DISTRIBUTION),"bookworm jammy kinetic lunar mantic noble oracular"))
ifneq ($(DISTRIBUTION),$(findstring $(DISTRIBUTION),"bookworm trixie jammy kinetic lunar mantic noble oracular plucky"))
DISTRIBUTION := sid
endif
@ -115,7 +115,7 @@ ifneq (,$(findstring ;$(GRASSVER);, ";7;8;"))
-DGRASS_PREFIX$(GRASSVER)=/usr/lib/$(GRASS)
endif
ifneq (,$(findstring $(DISTRIBUTION),"sid kinetic lunar mantic noble oracular"))
ifneq (,$(findstring $(DISTRIBUTION),"trixie sid kinetic lunar mantic noble oracular plucky"))
CMAKE_OPTS += -DGDAL_LIBRARY=/usr/lib/$(DEB_BUILD_MULTIARCH)/libgdal.so
endif

View File

@ -138,7 +138,7 @@ def register_function(
"""
# Format the help text
helptemplate = string.Template("<h3>$name function</h3><br>$doc")
helptemplate = string.Template("<h3>function $name</h3>\n$doc")
name = kwargs.get("name", function.__name__)
helptext = kwargs.get("helpText") or function.__doc__ or ""
helptext = helptext.strip()

View File

@ -138,7 +138,7 @@ def register_function(
"""
# Format the help text
helptemplate = string.Template("<h3>$name function</h3><br>$doc")
helptemplate = string.Template("<h3>function $name</h3>\n$doc")
name = kwargs.get("name", function.__name__)
helptext = kwargs.get("helpText") or function.__doc__ or ""
helptext = helptext.strip()

View File

@ -2495,6 +2495,7 @@ if(NOT APPLE OR NOT QGIS_MACAPP_FRAMEWORK)
if(WITH_INTERNAL_NLOHMANN_JSON)
install(FILES ${CMAKE_SOURCE_DIR}/external/nlohmann/json_fwd.hpp DESTINATION ${QGIS_INCLUDE_DIR}/nlohmann)
install(FILES ${CMAKE_SOURCE_DIR}/external/nlohmann/detail/abi_macros.hpp DESTINATION ${QGIS_INCLUDE_DIR}/nlohmann/detail)
endif()
else()

View File

@ -144,7 +144,7 @@ bool QgsOgrProvider::convertField( QgsField &field, const QTextCodec &encoding )
}
else
{
// other lists are supported at this moment
// other lists are not supported at this moment
return false;
}
break;
@ -886,9 +886,9 @@ void QgsOgrProvider::loadFields()
QMutexLocker locker( datasetMutex );
#endif
for ( int i = 0; i < fdef.GetFieldCount(); ++i )
for ( int fieldIndex = 0; fieldIndex < fdef.GetFieldCount(); ++fieldIndex )
{
OGRFieldDefnH fldDef = fdef.GetFieldDefn( i );
OGRFieldDefnH fldDef = fdef.GetFieldDefn( fieldIndex );
const OGRFieldType ogrType = OGR_Fld_GetType( fldDef );
const OGRFieldSubType ogrSubType = OGR_Fld_GetSubType( fldDef );
@ -896,6 +896,111 @@ void QgsOgrProvider::loadFields()
QMetaType::Type varSubType = QMetaType::Type::UnknownType;
QgsOgrUtils::ogrFieldTypeToQVariantType( ogrType, ogrSubType, varType, varSubType );
// Handle special case for OGRFieldType::OFSTJSON which is not necessarily a map.
// If subtype is JSON try to load a feature and check if it's
// really an object (rather than something else like an array)
// fallback to string.
if ( ( ogrType == OFTString || ogrType == OFTWideString ) && ogrSubType == OFSTJSON )
{
QRecursiveMutex *layerMutex = nullptr;
OGRLayerH ogrLayer = mOgrLayer->getHandleAndMutex( layerMutex );
QMutexLocker layerLocker( layerMutex );
gdal::ogr_feature_unique_ptr f( OGR_L_GetNextFeature( ogrLayer ) );
if ( f )
{
const char *json = OGR_F_GetFieldAsString( f.get(), fieldIndex );
if ( json && json[0] != '\0' )
{
try
{
const nlohmann::json json_element = json::parse( json );
// Check if it's an homogeneous array of numbers or strings
if ( json_element.is_array() )
{
// Check whether the values are all of the same type
bool allNumbers = true;
bool allIntegers = true;
bool allStrings = true;
for ( auto &value : json_element )
{
if ( allStrings && !value.is_string() )
{
allStrings = false;
}
if ( allNumbers && !value.is_number() )
{
allNumbers = false;
}
if ( allIntegers && !value.is_number_integer() )
{
allIntegers = false;
}
}
if ( allNumbers )
{
if ( allIntegers )
{
varType = QMetaType::Type::QVariantList;
varSubType = QMetaType::Type::LongLong;
}
else
{
varType = QMetaType::Type::QVariantList;
varSubType = QMetaType::Type::Double;
}
}
else if ( allStrings )
{
varType = QMetaType::Type::QStringList;
varSubType = QMetaType::Type::UnknownType;
}
else
{
QgsDebugMsgLevel( QStringLiteral( "JSON array contains mixed types, falling back to string" ), 2 );
varType = QMetaType::Type::QString;
varSubType = QMetaType::Type::UnknownType;
}
}
else if ( ! json_element.is_object() )
{
QgsDebugMsgLevel( QStringLiteral( "JSON is neither an array nor an object, falling back to string" ), 2 );
varType = QMetaType::Type::QString;
varSubType = QMetaType::Type::UnknownType;
}
else if ( json_element.is_number() )
{
if ( json_element.is_number_float() )
{
varType = QMetaType::Type::Double;
varSubType = QMetaType::Type::UnknownType;
}
else
{
varType = QMetaType::Type::LongLong;
varSubType = QMetaType::Type::UnknownType;
}
}
else if ( json_element.is_string() )
{
varType = QMetaType::Type::QString;
varSubType = QMetaType::Type::UnknownType;
}
else
{
QgsDebugMsgLevel( QStringLiteral( "JSON is not valid, falling back to string" ), 2 );
}
}
catch ( const json::parse_error & )
{
QgsDebugMsgLevel( QStringLiteral( "JSON is not valid, falling back to string" ), 2 );
varType = QMetaType::Type::QString;
varSubType = QMetaType::Type::UnknownType;
}
}
OGR_L_ResetReading( ogrLayer );
}
}
//TODO: fix this hack
#ifdef ANDROID
QString name = OGR_Fld_GetNameRef( fldDef );

View File

@ -635,7 +635,8 @@ QgsAuxiliaryStorage::QgsAuxiliaryStorage( const QString &filename, bool copy )
QgsAuxiliaryStorage::~QgsAuxiliaryStorage()
{
QFile::remove( mTmpFileName );
if ( QFile::exists( mTmpFileName ) )
QFile::remove( mTmpFileName );
}
bool QgsAuxiliaryStorage::isValid() const

View File

@ -38,6 +38,7 @@
#include "qgsfontmanager.h"
#include "qgsvariantutils.h"
#include "qgsogrproviderutils.h"
#include "qgsjsonutils.h"
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
#include "qgsweakrelation.h"
@ -59,6 +60,7 @@
#include "ogr_srs_api.h"
#include "nlohmann/json.hpp"
void gdal::OGRDataSourceDeleter::operator()( OGRDataSourceH source ) const
{
@ -541,24 +543,49 @@ QVariant QgsOgrUtils::getOgrFeatureAttribute( OGRFeatureH ogrFet, const QgsField
if ( ok )
*ok = true;
auto getJsonValue = [&]() -> bool
{
const char *json = OGR_F_GetFieldAsString( ogrFet, attIndex );
QString jsonContent;
if ( encoding )
jsonContent = encoding->toUnicode( json ).toUtf8();
else
jsonContent = QString::fromUtf8( json ).toUtf8();
try
{
const nlohmann::json json_element = json::parse( jsonContent.toStdString() );
value = QgsJsonUtils::jsonToVariant( json_element );
return true;
}
catch ( const json::parse_error &e )
{
QgsDebugMsgLevel( QStringLiteral( "Error parsing JSON: %1" ).arg( e.what() ), 2 );
return false;
}
};
if ( OGR_F_IsFieldSetAndNotNull( ogrFet, attIndex ) )
{
switch ( field.type() )
{
case QMetaType::Type::QString:
{
if ( encoding )
value = QVariant( encoding->toUnicode( OGR_F_GetFieldAsString( ogrFet, attIndex ) ) );
else
value = QVariant( QString::fromUtf8( OGR_F_GetFieldAsString( ogrFet, attIndex ) ) );
if ( field.typeName() != QStringLiteral( "JSON" ) || ! getJsonValue() )
{
if ( encoding )
value = QVariant( encoding->toUnicode( OGR_F_GetFieldAsString( ogrFet, attIndex ) ) );
else
value = QVariant( QString::fromUtf8( OGR_F_GetFieldAsString( ogrFet, attIndex ) ) );
#ifdef Q_OS_WIN
// Fixes GH #41076 (empty strings shown as NULL), because we have checked before that it was NOT NULL
// Note: QVariant( QString( ) ).isNull( ) is still true on windows so we really need string literal :(
if ( value.isNull() )
value = QVariant( QStringLiteral( "" ) ); // skip-keyword-check
// Fixes GH #41076 (empty strings shown as NULL), because we have checked before that it was NOT NULL
// Note: QVariant( QString( ) ).isNull( ) is still true on windows so we really need string literal :(
if ( value.isNull() )
value = QVariant( QStringLiteral( "" ) ); // skip-keyword-check
#endif
}
break;
}
case QMetaType::Type::Int:
@ -614,21 +641,24 @@ QVariant QgsOgrUtils::getOgrFeatureAttribute( OGRFeatureH ogrFet, const QgsField
case QMetaType::Type::QStringList:
{
QStringList list;
char **lst = OGR_F_GetFieldAsStringList( ogrFet, attIndex );
const int count = CSLCount( lst );
if ( count > 0 )
if ( field.typeName() != QStringLiteral( "JSON" ) || ! getJsonValue() )
{
list.reserve( count );
for ( int i = 0; i < count; i++ )
QStringList list;
char **lst = OGR_F_GetFieldAsStringList( ogrFet, attIndex );
const int count = CSLCount( lst );
if ( count > 0 )
{
if ( encoding )
list << encoding->toUnicode( lst[i] );
else
list << QString::fromUtf8( lst[i] );
list.reserve( count );
for ( int i = 0; i < count; i++ )
{
if ( encoding )
list << encoding->toUnicode( lst[i] );
else
list << QString::fromUtf8( lst[i] );
}
}
value = list;
}
value = list;
break;
}
@ -638,72 +668,84 @@ QVariant QgsOgrUtils::getOgrFeatureAttribute( OGRFeatureH ogrFet, const QgsField
{
case QMetaType::Type::QString:
{
QStringList list;
char **lst = OGR_F_GetFieldAsStringList( ogrFet, attIndex );
const int count = CSLCount( lst );
if ( count > 0 )
if ( field.typeName() != QStringLiteral( "JSON" ) || ! getJsonValue() )
{
list.reserve( count );
for ( int i = 0; i < count; i++ )
QStringList list;
char **lst = OGR_F_GetFieldAsStringList( ogrFet, attIndex );
const int count = CSLCount( lst );
if ( count > 0 )
{
if ( encoding )
list << encoding->toUnicode( lst[i] );
else
list << QString::fromUtf8( lst[i] );
list.reserve( count );
for ( int i = 0; i < count; i++ )
{
if ( encoding )
list << encoding->toUnicode( lst[i] );
else
list << QString::fromUtf8( lst[i] );
}
}
value = list;
}
value = list;
break;
}
case QMetaType::Type::Int:
{
QVariantList list;
int count = 0;
const int *lst = OGR_F_GetFieldAsIntegerList( ogrFet, attIndex, &count );
if ( count > 0 )
if ( field.typeName() != QStringLiteral( "JSON" ) || ! getJsonValue() )
{
list.reserve( count );
for ( int i = 0; i < count; i++ )
QVariantList list;
int count = 0;
const int *lst = OGR_F_GetFieldAsIntegerList( ogrFet, attIndex, &count );
if ( count > 0 )
{
list << lst[i];
list.reserve( count );
for ( int i = 0; i < count; i++ )
{
list << lst[i];
}
}
value = list;
}
value = list;
break;
}
case QMetaType::Type::Double:
{
QVariantList list;
int count = 0;
const double *lst = OGR_F_GetFieldAsDoubleList( ogrFet, attIndex, &count );
if ( count > 0 )
if ( field.typeName() != QStringLiteral( "JSON" ) || ! getJsonValue() )
{
list.reserve( count );
for ( int i = 0; i < count; i++ )
QVariantList list;
int count = 0;
const double *lst = OGR_F_GetFieldAsDoubleList( ogrFet, attIndex, &count );
if ( count > 0 )
{
list << lst[i];
list.reserve( count );
for ( int i = 0; i < count; i++ )
{
list << lst[i];
}
}
value = list;
}
value = list;
break;
}
case QMetaType::Type::LongLong:
{
QVariantList list;
int count = 0;
const long long *lst = OGR_F_GetFieldAsInteger64List( ogrFet, attIndex, &count );
if ( count > 0 )
if ( field.typeName() != QStringLiteral( "JSON" ) || ! getJsonValue() )
{
list.reserve( count );
for ( int i = 0; i < count; i++ )
QVariantList list;
int count = 0;
const long long *lst = OGR_F_GetFieldAsInteger64List( ogrFet, attIndex, &count );
if ( count > 0 )
{
list << lst[i];
list.reserve( count );
for ( int i = 0; i < count; i++ )
{
list << lst[i];
}
}
value = list;
}
value = list;
break;
}
@ -1833,7 +1875,6 @@ void QgsOgrUtils::ogrFieldTypeToQVariantType( OGRFieldType ogrType, OGRFieldSubT
if ( ogrSubType == OFSTBoolean )
{
variantType = QMetaType::Type::Bool;
ogrSubType = OFSTBoolean;
}
else
variantType = QMetaType::Type::Int;
@ -1862,7 +1903,6 @@ void QgsOgrUtils::ogrFieldTypeToQVariantType( OGRFieldType ogrType, OGRFieldSubT
case OFTWideString:
if ( ogrSubType == OFSTJSON )
{
ogrSubType = OFSTJSON;
variantType = QMetaType::Type::QVariantMap;
variantSubType = QMetaType::Type::QString;
}

View File

@ -498,7 +498,7 @@ QgsGraduatedSymbolRendererWidget::QgsGraduatedSymbolRendererWidget( QgsVectorLay
mModel = new QgsGraduatedSymbolRendererModel( this, screen() );
mExpressionWidget->setFilters( QgsFieldProxyModel::Numeric | QgsFieldProxyModel::Date );
mExpressionWidget->setFilters( QgsFieldProxyModel::Numeric );
mExpressionWidget->setLayer( mLayer );
btnChangeGraduatedSymbol->setLayer( mLayer );

View File

@ -166,7 +166,7 @@ QgsGrassProvider::QgsGrassProvider( const QString &uri )
mLayerField = grassLayer( mLayerName );
if ( mLayerField == -1 )
{
QgsDebugError( QString( "Invalid layer name, no underscore found: %1" ).arg( mLayerName ) );
QgsDebugError( QString( "Invalid layer name, no underscore found: \"%1\"" ).arg( mLayerName ) );
return;
}

View File

@ -777,13 +777,16 @@ void QgsGrassVectorMapLayer::deleteColumn( const QgsField &field, QString &error
if ( error.isEmpty() )
{
QgsDebugError( "error = " + error );
int index = mTableFields.indexFromName( field.name() );
if ( index != -1 )
{
mTableFields.remove( index );
}
}
else
{
QgsDebugError( "error = " + error );
}
}
void QgsGrassVectorMapLayer::insertCats( QString &error )

View File

@ -1793,7 +1793,7 @@ void QgsWmsCapabilities::parseWMTSContents( const QDomElement &element )
tileMatrix.scaleDenom = secondChildElement.firstChildElement( QStringLiteral( "ScaleDenominator" ) ).text().toDouble();
QStringList topLeft = secondChildElement.firstChildElement( QStringLiteral( "TopLeftCorner" ) ).text().split( ' ' );
QStringList topLeft = secondChildElement.firstChildElement( QStringLiteral( "TopLeftCorner" ) ).text().split( ' ', Qt::SkipEmptyParts );
if ( topLeft.size() == 2 )
{
if ( invert )
@ -1856,8 +1856,8 @@ void QgsWmsCapabilities::parseWMTSContents( const QDomElement &element )
QDomElement bbox = childElement.firstChildElement( QStringLiteral( "ows:WGS84BoundingBox" ) );
if ( !bbox.isNull() )
{
QStringList ll = bbox.firstChildElement( QStringLiteral( "ows:LowerCorner" ) ).text().split( ' ' );
QStringList ur = bbox.firstChildElement( QStringLiteral( "ows:UpperCorner" ) ).text().split( ' ' );
QStringList ll = bbox.firstChildElement( QStringLiteral( "ows:LowerCorner" ) ).text().split( ' ', Qt::SkipEmptyParts );
QStringList ur = bbox.firstChildElement( QStringLiteral( "ows:UpperCorner" ) ).text().split( ' ', Qt::SkipEmptyParts );
if ( ll.size() == 2 && ur.size() == 2 )
{
@ -1872,8 +1872,8 @@ void QgsWmsCapabilities::parseWMTSContents( const QDomElement &element )
!bbox.isNull();
bbox = bbox.nextSiblingElement( QStringLiteral( "ows:BoundingBox" ) ) )
{
QStringList ll = bbox.firstChildElement( QStringLiteral( "ows:LowerCorner" ) ).text().split( ' ' );
QStringList ur = bbox.firstChildElement( QStringLiteral( "ows:UpperCorner" ) ).text().split( ' ' );
QStringList ll = bbox.firstChildElement( QStringLiteral( "ows:LowerCorner" ) ).text().split( ' ', Qt::SkipEmptyParts );
QStringList ur = bbox.firstChildElement( QStringLiteral( "ows:UpperCorner" ) ).text().split( ' ', Qt::SkipEmptyParts );
if ( ll.size() == 2 && ur.size() == 2 )
{

View File

@ -58,6 +58,8 @@ class TestQgsOgrProvider : public QgsTest
void testExtent();
void testVsiCredentialOptions();
void testVsiCredentialOptionsQuerySublayers();
void testJSONFields_data();
void testJSONFields();
private:
QString mTestDataDir;
@ -579,5 +581,229 @@ void TestQgsOgrProvider::testVsiCredentialOptionsQuerySublayers()
}
void TestQgsOgrProvider::testJSONFields_data()
{
QTest::addColumn<QString>( "jsonData" );
QTest::addColumn<int>( "expectedType" );
QTest::addColumn<int>( "expectedSubType" );
QTest::newRow( "array of map string fallback" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"array_of_map": [
{
"a": 1,
"b": 2.0
}
]
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QString )
<< static_cast<int>( QMetaType::Type::UnknownType );
QTest::newRow( "simple map" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"map": {
"a": 1,
"b": 2.0
}
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QVariantMap )
<< static_cast<int>( QMetaType::Type::QString );
QTest::newRow( "complex map" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"map": {
"a": 1,
"b": [2.0, "c"]
}
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QVariantMap )
<< static_cast<int>( QMetaType::Type::QString );
QTest::newRow( "int" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"int": 1
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::Int )
<< static_cast<int>( QMetaType::Type::UnknownType );
QTest::newRow( "stringlist" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"string_list": [ "a", "b", "c" ]
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QStringList )
<< static_cast<int>( QMetaType::Type::QString );
QTest::newRow( "string" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"string": "a"
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QString )
<< static_cast<int>( QMetaType::Type::UnknownType );
QTest::newRow( "double" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"double": 1.0
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::Double )
<< static_cast<int>( QMetaType::Type::UnknownType );
QTest::newRow( "bool" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"bool": true
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::Bool )
<< static_cast<int>( QMetaType::Type::UnknownType );
QTest::newRow( "int list" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"int_list": [1, 2, 3]
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QVariantList )
<< static_cast<int>( QMetaType::Type::Int );
QTest::newRow( "real list" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"real_list": [1.0, 2.1, 3]
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QVariantList )
<< static_cast<int>( QMetaType::Type::Double );
QTest::newRow( "array mixed types string fallback" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"mixed_list": [1, 2.0, "a", true]
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QString )
<< static_cast<int>( QMetaType::Type::UnknownType );
QTest::newRow( "array mixed numeric types" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"mixed_numeric_list": [1, 2.3]
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QVariantList )
<< static_cast<int>( QMetaType::Type::Double );
}
void TestQgsOgrProvider::testJSONFields()
{
QFETCH( QString, jsonData );
QFETCH( int, expectedType );
QFETCH( int, expectedSubType );
QTemporaryDir dir;
QString filePath = dir.path() + "/test.json";
QFile file( filePath );
if ( file.open( QIODevice::WriteOnly ) )
{
QTextStream textStream( &file );
textStream << jsonData;
file.close();
}
QgsVectorLayer layer { filePath, QStringLiteral( "json" ), QLatin1String( "ogr" ) };
QVERIFY( layer.isValid() );
QgsFields fields = layer.fields();
QCOMPARE( fields.count(), 1 );
QgsField field = fields.at( 0 );
QCOMPARE( static_cast<int>( field.type() ), expectedType );
QCOMPARE( static_cast<int>( field.subType() ), expectedSubType );
}
QGSTEST_MAIN( TestQgsOgrProvider )
#include "testqgsogrprovider.moc"

View File

@ -568,11 +568,11 @@ void TestQgsVectorFileWriter::testExportArrayToGpkg()
const QgsVectorLayer vl2( QStringLiteral( "%1|layername=test" ).arg( fileName ), "src_test", "ogr" );
QVERIFY( vl2.isValid() );
QCOMPARE( vl2.featureCount(), 1L );
QCOMPARE( vl2.fields().at( 1 ).type(), QMetaType::Type::QVariantMap );
QCOMPARE( vl2.fields().at( 1 ).subType(), QMetaType::Type::QString );
QCOMPARE( vl2.fields().at( 1 ).type(), QMetaType::Type::QVariantList );
QCOMPARE( vl2.fields().at( 1 ).subType(), QMetaType::Type::LongLong );
QCOMPARE( vl2.fields().at( 1 ).typeName(), QStringLiteral( "JSON" ) );
QCOMPARE( vl2.fields().at( 2 ).type(), QMetaType::Type::QVariantMap );
QCOMPARE( vl2.fields().at( 2 ).subType(), QMetaType::Type::QString );
QCOMPARE( vl2.fields().at( 2 ).type(), QMetaType::Type::QStringList );
QCOMPARE( vl2.fields().at( 2 ).subType(), QMetaType::Type::UnknownType );
QCOMPARE( vl2.fields().at( 2 ).typeName(), QStringLiteral( "JSON" ) );
QCOMPARE( vl2.getFeature( 1 ).attribute( 1 ).toList(), QVariantList() << 1 << 2 << 3 );
QCOMPARE( vl2.getFeature( 1 ).attribute( 2 ).toStringList(), QStringList() << "a" << "b" << "c" );

View File

@ -127,13 +127,13 @@ QString TestQgsGrassCommand::toString() const
{
if ( grassFeature.hasGeometry() )
{
string += "<br>grass: " + grassFeature.geometry().asWkt( 1 );
string += "grass: " + grassFeature.geometry().asWkt( 1 );
}
}
if ( expectedFeature.hasGeometry() )
{
string += "<br>expected: " + expectedFeature.geometry().asWkt( 1 );
string += "expected: " + expectedFeature.geometry().asWkt( 1 );
}
}
else if ( command == DeleteFeature )
@ -243,11 +243,16 @@ class TestQgsGrassProvider : public QgsTest
void TestQgsGrassProvider::reportRow( const QString &message )
{
mReport += message + "<br>\n";
for ( const QString &line : message.split( '\n' ) )
{
qDebug() << line;
}
}
void TestQgsGrassProvider::reportHeader( const QString &message )
{
mReport += "<h2>" + message + "</h2>\n";
qDebug() << "------";
qDebug() << message;
qDebug() << "------";
}
//runs before all tests
@ -264,8 +269,8 @@ void TestQgsGrassProvider::initTestCase()
QgsApplication::initQgis();
QString mySettings = QgsApplication::showSettings();
mySettings = mySettings.replace( QLatin1String( "\n" ), QLatin1String( "<br />\n" ) );
mReport += QStringLiteral( "<h1>GRASS %1 provider tests</h1>\n" ).arg( GRASS_BUILD_VERSION );
mReport += "<p>" + mySettings + "</p>\n";
reportHeader( QStringLiteral( "<h1>GRASS %1 provider tests</h1>\n" ).arg( GRASS_BUILD_VERSION ) );
reportRow( mySettings );
#ifndef Q_OS_WIN
reportRow( "LD_LIBRARY_PATH: " + QString( getenv( "LD_LIBRARY_PATH" ) ) );
@ -1163,7 +1168,7 @@ void TestQgsGrassProvider::edit()
Q_FOREACH ( const TestQgsGrassCommand &command, commandGroup.commands )
{
reportRow( "<br>command: " + command.toString() );
reportRow( "command: " + command.toString() );
bool commandOk = true;
if ( command.command == TestQgsGrassCommand::StartEditing )
@ -1388,7 +1393,7 @@ void TestQgsGrassProvider::edit()
{
for ( int j = editCommands.size() - 1; j >= 0; j-- )
{
reportRow( "<br>undo command: " + editCommands[j].toString() );
reportRow( "undo command: " + editCommands[j].toString() );
grassLayer->undoStack()->undo();
expectedLayer->undoStack()->undo();
if ( !compare( expectedLayers, ok ) )
@ -1418,7 +1423,7 @@ void TestQgsGrassProvider::edit()
{
for ( int j = 0; j < editCommands.size(); j++ )
{
reportRow( "<br>redo command: " + editCommands[j].toString() );
reportRow( "redo command: " + editCommands[j].toString() );
grassLayer->undoStack()->redo();
expectedLayer->undoStack()->redo();
if ( !compare( expectedLayers, ok ) )

View File

@ -596,6 +596,44 @@ class TestQgsWmsCapabilities : public QObject
QCOMPARE( res.end(), range.end() );
QCOMPARE( static_cast<int>( resFormat ), format );
}
void wmtsWithRedundantSpaces()
{
QgsWmsCapabilities capabilities;
const QgsWmsParserSettings config;
// Note: Redundant spaces in the coordinate tuples
const QByteArray configData( R"""(<Capabilities>
<Contents>
<Layer>
<ows:Identifier>Layer1</ows:Identifier>
<ows:WGS84BoundingBox>
<ows:LowerCorner>109.999000 -45.081000</ows:LowerCorner>
<ows:UpperCorner>155.005000 -9.978000</ows:UpperCorner>
</ows:WGS84BoundingBox>
</Layer>
<TileMatrixSet>
<ows:Identifier>WholeWorld_CRS_84</ows:Identifier>
<ows:SupportedCRS>urn:ogc:def:crs:OGC:1.3:CRS84</ows:SupportedCRS>
<WellKnownScaleSet>urn:ogc:def:wkss:OGC:1.0:GlobalCRS84Pixel</WellKnownScaleSet>
<TileMatrix>
<ows:Identifier>1g</ows:Identifier>
<ScaleDenominator>397569609.975977</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>320</TileWidth>
<TileHeight>200</TileHeight>
<MatrixWidth>2</MatrixWidth>
<MatrixHeight>1</MatrixHeight>
</TileMatrix>
</TileMatrixSet>
</Contents>
</Capabilities>)""" );
QVERIFY( capabilities.parseResponse( configData, config ) );
QCOMPARE( capabilities.supportedTileMatrixSets().size(), 1 );
QgsWmtsTileLayer layer = capabilities.supportedTileLayers().at( 0 );
QCOMPARE( layer.boundingBoxes.first().box, QgsRectangle( 109.999, -45.081, 155.005, -9.978 ) );
}
};
QGSTEST_MAIN( TestQgsWmsCapabilities )

View File

@ -120,13 +120,13 @@ class TestQgsExpressionCustomFunctions(unittest.TestCase):
"""Test help about python function."""
QgsExpression.registerFunction(self.help_with_variable)
html = (
"<h3>help_with_variable function</h3><br>" "The help comes from a variable."
"<h3>function help_with_variable</h3>\n" "The help comes from a variable."
)
self.assertEqual(self.help_with_variable.helpText(), html)
QgsExpression.registerFunction(self.help_with_docstring)
html = (
"<h3>help_with_docstring function</h3><br>"
"<h3>function help_with_docstring</h3>\n"
"The help comes from the python docstring."
)
self.assertEqual(self.help_with_docstring.helpText(), html)