From baec93e78ffecdc2c0aa16dc2eda9caeb864f02a Mon Sep 17 00:00:00 2001 From: Jacky Volpes Date: Fri, 21 Jun 2024 17:49:22 +0200 Subject: [PATCH] fix(clipboard): format null attribute values in system clipboard Numeric attributes with a NULL value were represented with a 0 value in the system text and html clipboard. Related to #57710. --- src/app/qgsclipboard.cpp | 14 +++++-- tests/src/app/testqgisappclipboard.cpp | 58 ++++++++++++++++---------- 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/src/app/qgsclipboard.cpp b/src/app/qgsclipboard.cpp index 6ff4ffb352e..484129ebfbc 100644 --- a/src/app/qgsclipboard.cpp +++ b/src/app/qgsclipboard.cpp @@ -200,11 +200,14 @@ void QgsClipboard::generateClipboardText( QString &textContent, QString &htmlCon if ( useJSONFromVariant ) { - value = QString::fromStdString( QgsJsonUtils::jsonFromVariant( attributes.at( idx ) ).dump() ); + value = QString::fromStdString( QgsJsonUtils::jsonFromVariant( variant ).dump() ); } else { - value = attributes.at( idx ).toString(); + if ( QgsVariantUtils::isNull( variant ) && variant.isValid() ) + value = ""; + else + value = variant.toString(); } if ( value.contains( '\n' ) || value.contains( '\t' ) ) @@ -215,11 +218,14 @@ void QgsClipboard::generateClipboardText( QString &textContent, QString &htmlCon } if ( useJSONFromVariant ) { - value = QString::fromStdString( QgsJsonUtils::jsonFromVariant( attributes.at( idx ) ).dump() ); + value = QString::fromStdString( QgsJsonUtils::jsonFromVariant( variant ).dump() ); } else { - value = attributes.at( idx ).toString(); + if ( QgsVariantUtils::isNull( variant ) && variant.isValid() ) + value = ""; + else + value = variant.toString(); } value.replace( '\n', QLatin1String( "
" ) ).replace( '\t', QLatin1String( " " ) ); htmlFields += QStringLiteral( "%1" ).arg( value ); diff --git a/tests/src/app/testqgisappclipboard.cpp b/tests/src/app/testqgisappclipboard.cpp index 464bca5cfe5..645fbf7ad59 100644 --- a/tests/src/app/testqgisappclipboard.cpp +++ b/tests/src/app/testqgisappclipboard.cpp @@ -131,18 +131,27 @@ void TestQgisAppClipboard::copyToText() //set clipboard to some QgsFeatures QgsFields fields; fields.append( QgsField( QStringLiteral( "int_field" ), QMetaType::Type::Int ) ); + fields.append( QgsField( QStringLiteral( "double_field" ), QMetaType::Type::Double ) ); fields.append( QgsField( QStringLiteral( "string_field" ), QMetaType::Type::QString ) ); QgsFeature feat( fields, 5 ); feat.setAttribute( QStringLiteral( "int_field" ), 9 ); + feat.setAttribute( QStringLiteral( "double_field" ), 9.9 ); feat.setAttribute( QStringLiteral( "string_field" ), "val" ); feat.setGeometry( QgsGeometry( new QgsPoint( 5, 6 ) ) ); QgsFeature feat2( fields, 6 ); feat2.setAttribute( QStringLiteral( "int_field" ), 19 ); + feat2.setAttribute( QStringLiteral( "double_field" ), 19.19 ); feat2.setAttribute( QStringLiteral( "string_field" ), "val2" ); feat2.setGeometry( QgsGeometry( new QgsPoint( 7, 8 ) ) ); + QgsFeature feat3( fields, 7 ); // NULL field values + feat3.setAttribute( QStringLiteral( "int_field" ), QVariant( QVariant::Int ) ); + feat3.setAttribute( QStringLiteral( "double_field" ), QVariant( QVariant::Double ) ); + feat3.setAttribute( QStringLiteral( "string_field" ), QVariant( QVariant::String ) ); + feat3.setGeometry( QgsGeometry( new QgsPoint( 9, 10 ) ) ); QgsFeatureStore feats; feats.addFeature( feat ); feats.addFeature( feat2 ); + feats.addFeature( feat3 ); feats.setFields( fields ); mQgisApp->clipboard()->replaceWithCopyOf( feats ); @@ -151,30 +160,32 @@ void TestQgisAppClipboard::copyToText() settings.setEnumValue( QStringLiteral( "/qgis/copyFeatureFormat" ), QgsClipboard::AttributesOnly ); QString result, resultHtml; mQgisApp->clipboard()->generateClipboardText( result, resultHtml ); - QCOMPARE( result, QString( "int_field\tstring_field\n9\tval\n19\tval2" ) ); + QCOMPARE( result, QString( "int_field\tdouble_field\tstring_field\n9\t9.9\tval\n19\t19.19\tval2\n\t\t" ) ); // attributes with WKT settings.setEnumValue( QStringLiteral( "/qgis/copyFeatureFormat" ), QgsClipboard::AttributesWithWKT ); mQgisApp->clipboard()->generateClipboardText( result, resultHtml ); - QCOMPARE( result, QString( "wkt_geom\tint_field\tstring_field\nPoint (5 6)\t9\tval\nPoint (7 8)\t19\tval2" ) ); + QCOMPARE( result, QString( "wkt_geom\tint_field\tdouble_field\tstring_field\nPoint (5 6)\t9\t9.9\tval\nPoint (7 8)\t19\t19.19\tval2\nPoint (9 10)\t\t\t" ) ); // attributes with WKB settings.setEnumValue( QStringLiteral( "/qgis/copyFeatureFormat" ), QgsClipboard::AttributesWithWKB ); mQgisApp->clipboard()->generateClipboardText( result, resultHtml ); - QCOMPARE( result, QString( "wkb_geom\tint_field\tstring_field\n010100000000000000000014400000000000001840\t9\tval\n01010000000000000000001c400000000000002040\t19\tval2" ) ); + QCOMPARE( result, QString( "wkb_geom\tint_field\tdouble_field\tstring_field\n010100000000000000000014400000000000001840\t9\t9.9\tval\n01010000000000000000001c400000000000002040\t19\t19.19\tval2\n010100000000000000000022400000000000002440\t\t\t" ) ); // HTML test mQgisApp->clipboard()->replaceWithCopyOf( feats ); result = mQgisApp->clipboard()->data( "text/html" ); - QCOMPARE( result, QString( "
wkb_geomint_fieldstring_field
0101000000000000000000144000000000000018409val
01010000000000000000001c40000000000000204019val2
" ) ); + QCOMPARE( result, QString( "
wkb_geomint_fielddouble_fieldstring_field
01010000000000000000001440000000000000184099.9val
01010000000000000000001c4000000000000020401919.19val2
010100000000000000000022400000000000002440
" ) ); // GeoJSON settings.setEnumValue( QStringLiteral( "/qgis/copyFeatureFormat" ), QgsClipboard::GeoJSON ); mQgisApp->clipboard()->generateClipboardText( result, resultHtml ); const QString expected = "{\"features\":[{\"geometry\":{\"coordinates\":[5.0,6.0],\"type\":\"Point\"},\"id\":5," - "\"properties\":{\"int_field\":9,\"string_field\":\"val\"},\"type\":\"Feature\"}," + "\"properties\":{\"double_field\":9.9,\"int_field\":9,\"string_field\":\"val\"},\"type\":\"Feature\"}," "{\"geometry\":{\"coordinates\":[7.0,8.0],\"type\":\"Point\"},\"id\":6," - "\"properties\":{\"int_field\":19,\"string_field\":\"val2\"},\"type\":\"Feature\"}]," + "\"properties\":{\"double_field\":19.19,\"int_field\":19,\"string_field\":\"val2\"},\"type\":\"Feature\"}," + "{\"geometry\":{\"coordinates\":[9.0,10.0],\"type\":\"Point\"},\"id\":7," + "\"properties\":{\"double_field\":null,\"int_field\":null,\"string_field\":null},\"type\":\"Feature\"}]," "\"type\":\"FeatureCollection\"}"; QCOMPARE( result, expected ); @@ -204,22 +215,25 @@ void TestQgisAppClipboard::copyToText() QCOMPARE( y, -38 ); // test that multiline text fields are quoted to render correctly as csv files in WKT mode - QgsFeature feat3( fields, 7 ); - feat3.setAttribute( QStringLiteral( "string_field" ), "Single line text" ); - feat3.setAttribute( QStringLiteral( "int_field" ), 1 ); - feat3.setGeometry( QgsGeometry( new QgsPoint( 5, 6 ) ) ); - QgsFeature feat4( fields, 8 ); - feat4.setAttribute( QStringLiteral( "string_field" ), "Unix Multiline \nText" ); - feat4.setAttribute( QStringLiteral( "int_field" ), 2 ); - feat4.setGeometry( QgsGeometry( new QgsPoint( 7, 8 ) ) ); - QgsFeature feat5( fields, 9 ); - feat5.setAttribute( QStringLiteral( "string_field" ), "Windows Multiline \r\nText" ); - feat5.setAttribute( QStringLiteral( "int_field" ), 3 ); - feat5.setGeometry( QgsGeometry( new QgsPoint( 9, 10 ) ) ); + QgsFeature feat4( fields, 7 ); + feat4.setAttribute( QStringLiteral( "string_field" ), "Single line text" ); + feat4.setAttribute( QStringLiteral( "int_field" ), 1 ); + feat4.setAttribute( QStringLiteral( "double_field" ), 1.1 ); + feat4.setGeometry( QgsGeometry( new QgsPoint( 5, 6 ) ) ); + QgsFeature feat5( fields, 8 ); + feat5.setAttribute( QStringLiteral( "string_field" ), "Unix Multiline \nText" ); + feat5.setAttribute( QStringLiteral( "int_field" ), 2 ); + feat5.setAttribute( QStringLiteral( "double_field" ), 2.2 ); + feat5.setGeometry( QgsGeometry( new QgsPoint( 7, 8 ) ) ); + QgsFeature feat6( fields, 9 ); + feat6.setAttribute( QStringLiteral( "string_field" ), "Windows Multiline \r\nText" ); + feat6.setAttribute( QStringLiteral( "int_field" ), 3 ); + feat6.setAttribute( QStringLiteral( "double_field" ), 3.3 ); + feat6.setGeometry( QgsGeometry( new QgsPoint( 9, 10 ) ) ); QgsFeatureStore featsML; - featsML.addFeature( feat3 ); featsML.addFeature( feat4 ); featsML.addFeature( feat5 ); + featsML.addFeature( feat6 ); featsML.setFields( fields ); mQgisApp->clipboard()->replaceWithCopyOf( featsML ); @@ -227,17 +241,17 @@ void TestQgisAppClipboard::copyToText() settings.setEnumValue( QStringLiteral( "/qgis/copyFeatureFormat" ), QgsClipboard::AttributesOnly ); mQgisApp->clipboard()->generateClipboardText( result, resultHtml ); qDebug() << result; - QCOMPARE( result, QString( "int_field\tstring_field\n1\tSingle line text\n2\t\"Unix Multiline \nText\"\n3\t\"Windows Multiline \r\nText\"" ) ); + QCOMPARE( result, QString( "int_field\tdouble_field\tstring_field\n1\t1.1\tSingle line text\n2\t2.2\t\"Unix Multiline \nText\"\n3\t3.3\t\"Windows Multiline \r\nText\"" ) ); // attributes with WKT settings.setEnumValue( QStringLiteral( "/qgis/copyFeatureFormat" ), QgsClipboard::AttributesWithWKT ); mQgisApp->clipboard()->generateClipboardText( result, resultHtml ); - QCOMPARE( result, QString( "wkt_geom\tint_field\tstring_field\nPoint (5 6)\t1\tSingle line text\nPoint (7 8)\t2\t\"Unix Multiline \nText\"\nPoint (9 10)\t3\t\"Windows Multiline \r\nText\"" ) ); + QCOMPARE( result, QString( "wkt_geom\tint_field\tdouble_field\tstring_field\nPoint (5 6)\t1\t1.1\tSingle line text\nPoint (7 8)\t2\t2.2\t\"Unix Multiline \nText\"\nPoint (9 10)\t3\t3.3\t\"Windows Multiline \r\nText\"" ) ); // attributes with WKB settings.setEnumValue( QStringLiteral( "/qgis/copyFeatureFormat" ), QgsClipboard::AttributesWithWKB ); mQgisApp->clipboard()->generateClipboardText( result, resultHtml ); - QCOMPARE( result, QString( "wkb_geom\tint_field\tstring_field\n010100000000000000000014400000000000001840\t1\tSingle line text\n01010000000000000000001c400000000000002040\t2\t\"Unix Multiline \nText\"\n010100000000000000000022400000000000002440\t3\t\"Windows Multiline \r\nText\"" ) ); + QCOMPARE( result, QString( "wkb_geom\tint_field\tdouble_field\tstring_field\n010100000000000000000014400000000000001840\t1\t1.1\tSingle line text\n01010000000000000000001c400000000000002040\t2\t2.2\t\"Unix Multiline \nText\"\n010100000000000000000022400000000000002440\t3\t3.3\t\"Windows Multiline \r\nText\"" ) ); }