diff --git a/src/gui/editorwidgets/qgsvaluemapconfigdlg.cpp b/src/gui/editorwidgets/qgsvaluemapconfigdlg.cpp index 49eab40e730..5b6ea017579 100644 --- a/src/gui/editorwidgets/qgsvaluemapconfigdlg.cpp +++ b/src/gui/editorwidgets/qgsvaluemapconfigdlg.cpp @@ -301,14 +301,18 @@ void QgsValueMapConfigDlg::loadFromCSVButtonPushed() const QString fileName = QFileDialog::getOpenFileName( nullptr, tr( "Load Value Map from File" ), QDir::homePath() ); if ( fileName.isNull() ) return; + loadMapFromCSV( fileName ); +} - QFile f( fileName ); +void QgsValueMapConfigDlg::loadMapFromCSV( const QString &filePath ) +{ + QFile f( filePath ); if ( !f.open( QIODevice::ReadOnly ) ) { QMessageBox::information( nullptr, tr( "Load Value Map from File" ), - tr( "Could not open file %1\nError was: %2" ).arg( fileName, f.errorString() ), + tr( "Could not open file %1\nError was: %2" ).arg( filePath, f.errorString() ), QMessageBox::Cancel ); return; } @@ -316,53 +320,26 @@ void QgsValueMapConfigDlg::loadFromCSVButtonPushed() QTextStream s( &f ); s.setAutoDetectUnicode( true ); - const thread_local QRegularExpression re0( "^([^;]*?);(.*?)$" ); - const thread_local QRegularExpression re1( "^([^,]*?),(.*?)$" ); - + const thread_local QRegularExpression re( "(?:^\"|[;,]\")(\"\"|[\\w\\W]*?)(?=\"[;,]|\"$)|(?:^(?!\")|[;,](?!\"))([^;,]*?)(?=$|[;,])|(\\r\\n|\\n)" ); QList> map; - while ( !s.atEnd() ) { const QString l = s.readLine().trimmed(); - - QString key; - QString val; - - const QRegularExpressionMatch re0match = re0.match( l ); - if ( re0match.hasMatch() ) + QRegularExpressionMatchIterator matches = re.globalMatch( l ); + QStringList ceils; + while ( matches.hasNext() && ceils.size() < 2 ) { - key = re0match.captured( 1 ).trimmed(); - val = re0match.captured( 2 ).trimmed(); - } - else - { - const QRegularExpressionMatch re1match = re1.match( l ); - if ( re1match.hasMatch() ) - { - key = re1match.captured( 1 ).trimmed(); - val = re1match.captured( 2 ).trimmed(); - } - else - { - continue; - } + const QRegularExpressionMatch match = matches.next(); + ceils << match.capturedTexts().last().trimmed().replace( QLatin1String( "\"\"" ), QLatin1String( "\"" ) ); } - if ( ( key.startsWith( '\"' ) && key.endsWith( '\"' ) ) || - ( key.startsWith( '\'' ) && key.endsWith( '\'' ) ) ) - { - key = key.mid( 1, key.length() - 2 ); - } - - if ( ( val.startsWith( '\"' ) && val.endsWith( '\"' ) ) || - ( val.startsWith( '\'' ) && val.endsWith( '\'' ) ) ) - { - val = val.mid( 1, val.length() - 2 ); - } + if ( ceils.size() != 2 ) + continue; + QString key = ceils[0]; + QString val = ceils[1]; if ( key == QgsApplication::nullRepresentation() ) key = QgsValueMapFieldFormatter::NULL_VALUE; - map.append( qMakePair( key, val ) ); } diff --git a/src/gui/editorwidgets/qgsvaluemapconfigdlg.h b/src/gui/editorwidgets/qgsvaluemapconfigdlg.h index 22a395d9afd..0c52e34ee05 100644 --- a/src/gui/editorwidgets/qgsvaluemapconfigdlg.h +++ b/src/gui/editorwidgets/qgsvaluemapconfigdlg.h @@ -58,6 +58,13 @@ class GUI_EXPORT QgsValueMapConfigDlg : public QgsEditorConfigWidget, private Ui */ void updateMap( const QList> &list, bool insertNull ); + /** + * Updates the displayed table with the values from a CSV file. + * \param filePath the absolute file path of the CSV file. + * \since QGIS 3.24 + */ + void loadMapFromCSV( const QString &filePath ); + /** * Populates a \a comboBox with the appropriate entries based on a value map \a configuration. * diff --git a/tests/src/gui/CMakeLists.txt b/tests/src/gui/CMakeLists.txt index 8dae315ea5e..0a0a64b11a4 100644 --- a/tests/src/gui/CMakeLists.txt +++ b/tests/src/gui/CMakeLists.txt @@ -58,6 +58,7 @@ set(TESTS testqgslayoutgui.cpp testqgslayoutview.cpp testqgsvaluemapwidgetwrapper.cpp + testqgsvaluemapconfigdlg.cpp testqgsvaluerelationwidgetwrapper.cpp testqgsrelationeditorwidget.cpp testqgsrelationreferencewidget.cpp diff --git a/tests/src/gui/testqgsvaluemapconfigdlg.cpp b/tests/src/gui/testqgsvaluemapconfigdlg.cpp new file mode 100644 index 00000000000..727ff6dda6e --- /dev/null +++ b/tests/src/gui/testqgsvaluemapconfigdlg.cpp @@ -0,0 +1,90 @@ +/*************************************************************************** + testqgsvaluemapconfigdlg.cpp + -------------------------------------- + Date : 14 02 2021 + Copyright : (C) 2019 Stephen Knox + Email : stephenknox73 at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#include "qgstest.h" + +#include "editorwidgets/qgsvaluemapconfigdlg.h" +#include "qgsgui.h" +#include "qgseditorwidgetregistry.h" +#include "qgsapplication.h" +#include "qgsproject.h" +#include "qgsvectorlayer.h" + +class TestQgsValueMapConfigDlg : public QObject +{ + Q_OBJECT + public: + TestQgsValueMapConfigDlg() = default; + + private slots: + void initTestCase(); // will be called before the first testfunction is executed. + void cleanupTestCase(); // will be called after the last testfunction was executed. + void init(); // will be called before each testfunction is executed. + void cleanup(); // will be called after every testfunction. + + void testLoadFromCSV(); +}; + +void TestQgsValueMapConfigDlg::initTestCase() +{ + QgsApplication::init(); + QgsApplication::initQgis(); + QgsGui::editorWidgetRegistry()->initEditors(); +} + +void TestQgsValueMapConfigDlg::cleanupTestCase() +{ + QgsApplication::exitQgis(); +} + +void TestQgsValueMapConfigDlg::init() +{ +} + +void TestQgsValueMapConfigDlg::cleanup() +{ +} + +void TestQgsValueMapConfigDlg::testLoadFromCSV() +{ + const QString dataDir( TEST_DATA_DIR ); + QgsVectorLayer vl( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=name:string" ), QStringLiteral( "vl1" ), QStringLiteral( "memory" ) ); + + QList valueList; + QVariantMap value; + value.insert( QStringLiteral( "Basic unquoted record" ), QString( "1" ) ); + valueList << value; + value.clear(); + value.insert( QStringLiteral( "Forest type" ), QString( "2" ) ); + valueList << value; + value.clear(); + value.insert( QStringLiteral( "So-called \"data\"" ), QString( "three" ) ); + valueList << value; + value.clear(); + value.insert( QStringLiteral( "444" ), QString( "4" ) ); + valueList << value; + value.clear(); + value.insert( QStringLiteral( "five" ), QString( "5" ) ); + valueList << value; + + QgsValueMapConfigDlg *valueMapConfig = static_cast( QgsGui::editorWidgetRegistry()->createConfigWidget( QStringLiteral( "ValueMap" ), &vl, 1, nullptr ) ); + valueMapConfig->loadMapFromCSV( dataDir + QStringLiteral( "/valuemapsample.csv" ) ); + QCOMPARE( valueMapConfig->config().value( QStringLiteral( "map" ) ).toList(), valueList ); + delete valueMapConfig; +} + +QGSTEST_MAIN( TestQgsValueMapConfigDlg ) +#include "testqgsvaluemapconfigdlg.moc" diff --git a/tests/testdata/valuemapsample.csv b/tests/testdata/valuemapsample.csv new file mode 100644 index 00000000000..dabc617fd1e --- /dev/null +++ b/tests/testdata/valuemapsample.csv @@ -0,0 +1,5 @@ +1,Basic unquoted record,Some description +2,Forest type,"Quoted data" +three,"So-called ""data""",Unquoted remark +4,"444",No comment +5,five