From 109b4d7f21a61dd549050b3692139404ccdb955d Mon Sep 17 00:00:00 2001 From: David Signer Date: Wed, 17 Jul 2019 16:24:42 +0200 Subject: [PATCH] improved parsing and building function to handle multidimensional arrays code mostly taken from this integration in the postgresprovider / postgresconn x --- src/core/qgsarrayutils.cpp | 113 +++++++++++++++++++++++---- src/core/qgsarrayutils.h | 6 +- tests/src/core/testqgsarrayutils.cpp | 49 +++++++++--- 3 files changed, 143 insertions(+), 25 deletions(-) diff --git a/src/core/qgsarrayutils.cpp b/src/core/qgsarrayutils.cpp index 546e538a691..27cfbac2ee9 100644 --- a/src/core/qgsarrayutils.cpp +++ b/src/core/qgsarrayutils.cpp @@ -14,33 +14,109 @@ ***************************************************************************/ #include "qgsarrayutils.h" +#include "qgsmessagelog.h" #include #include using json = nlohmann::json; +static void jumpSpace( const QString &txt, int &i ) +{ + while ( i < txt.length() && txt.at( i ).isSpace() ) + ++i; +} + +QString QgsArrayUtils::getNextString( const QString &txt, int &i, const QString &sep ) +{ + jumpSpace( txt, i ); + QString cur = txt.mid( i ); + if ( cur.startsWith( '"' ) ) + { + QRegExp stringRe( "^\"((?:\\\\.|[^\"\\\\])*)\".*" ); + if ( !stringRe.exactMatch( cur ) ) + { + QgsMessageLog::logMessage( QObject::tr( "Cannot find end of double quoted string: %1" ).arg( txt ), QObject::tr( "PostgresStringUtils" ) ); + return QString(); + } + i += stringRe.cap( 1 ).length() + 2; + jumpSpace( txt, i ); + if ( !txt.midRef( i ).startsWith( sep ) && i < txt.length() ) + { + QgsMessageLog::logMessage( QObject::tr( "Cannot find separator: %1" ).arg( txt.mid( i ) ), QObject::tr( "PostgresStringUtils" ) ); + return QString(); + } + i += sep.length(); + return stringRe.cap( 1 ).replace( QLatin1String( "\\\"" ), QLatin1String( "\"" ) ).replace( QLatin1String( "\\\\" ), QLatin1String( "\\" ) ); + } + else + { + int sepPos = cur.indexOf( sep ); + if ( sepPos < 0 ) + { + i += cur.length(); + return cur.trimmed(); + } + i += sepPos + sep.length(); + return cur.left( sepPos ).trimmed(); + } +} + QVariantList QgsArrayUtils::parse( const QString &string ) { QVariantList variantList; - QString newVal = string; - if ( newVal.trimmed().startsWith( '{' ) ) + if ( string.trimmed().startsWith( '{' ) ) { - newVal = newVal.trimmed().mid( 1 ).mid( 0, newVal.length() - 2 ).prepend( '[' ).append( ']' ); + //it's a postgres array + QString newVal = string.mid( 1, string.length() - 2 ); - if ( !json::accept( newVal.toStdString() ) ) + if ( newVal.trimmed().startsWith( '{' ) ) { - //fallback for wrongly stored string data without quotes - newVal = string; - const QStringList stringList = newVal.remove( QChar( '{' ) ).remove( QChar( '}' ) ).split( ',' ); - for ( const QString &s : qgis::as_const( stringList ) ) + //it's a multidimensional array + QStringList values; + QString subarray = newVal; + while ( !subarray.isEmpty() ) { - variantList.push_back( s ); + bool escaped = false; + int openedBrackets = 1; + int i = 0; + while ( i < subarray.length() && openedBrackets > 0 ) + { + ++i; + + if ( subarray.at( i ) == '}' && !escaped ) openedBrackets--; + else if ( subarray.at( i ) == '{' && !escaped ) openedBrackets++; + + escaped = !escaped ? subarray.at( i ) == '\\' : false; + } + + variantList.append( subarray.left( ++i ) ); + i = subarray.indexOf( ',', i ); + i = i > 0 ? subarray.indexOf( '{', i ) : -1; + if ( i == -1 ) + break; + + subarray = subarray.mid( i ); + } + } + else + { + int i = 0; + while ( i < newVal.length() ) + { + const QString value = getNextString( newVal, i, QStringLiteral( "," ) ); + if ( value.isNull() ) + { + QgsMessageLog::logMessage( QObject::tr( "Error parsing PG like array: %1" ).arg( newVal ), QObject::tr( "PostgresStringUtils" ) ); + break; + } + variantList.append( value ); } } } - - if ( newVal.trimmed().startsWith( '[' ) ) + else if ( string.trimmed().startsWith( '[' ) ) { + //it's a json array + QString newVal = string; try { for ( auto &element : json::parse( newVal.toStdString() ) ) @@ -62,9 +138,11 @@ QVariantList QgsArrayUtils::parse( const QString &string ) catch ( json::parse_error &ex ) { qDebug() << QString::fromStdString( ex.what() ); + QgsMessageLog::logMessage( QObject::tr( "Error parsing JSON like array: %1 %2" ).arg( newVal, ex.what() ), QObject::tr( "PostgresStringUtils" ) ); } } return variantList; + } QString QgsArrayUtils::build( const QVariantList &list ) @@ -81,9 +159,16 @@ QString QgsArrayUtils::build( const QVariantList &list ) break; default: QString newS = v.toString(); - newS.replace( '\\', QStringLiteral( R"(\\)" ) ); - newS.replace( '\"', QStringLiteral( R"(\")" ) ); - sl.push_back( "\"" + newS + "\"" ); + if ( newS.startsWith( '{' ) ) + { + sl.push_back( newS ); + } + else + { + newS.replace( '\\', QStringLiteral( R"(\\)" ) ); + newS.replace( '\"', QStringLiteral( R"(\")" ) ); + sl.push_back( "\"" + newS + "\"" ); + } break; } } diff --git a/src/core/qgsarrayutils.h b/src/core/qgsarrayutils.h index fcf61463d37..98ad9fb3dc0 100644 --- a/src/core/qgsarrayutils.h +++ b/src/core/qgsarrayutils.h @@ -42,12 +42,14 @@ namespace QgsArrayUtils CORE_EXPORT QVariantList parse( const QString &string ); /** - * Build a hstore-formatted string from a QVariantMap. - * \param map The map to format as a string + * Build a postgres array like formatted list in a string from a QVariantList + * \param list The list that needs to be stored to the string * \since QGIS 3.8 */ CORE_EXPORT QString build( const QVariantList &list ); + QString getNextString( const QString &txt, int &i, const QString &sep ); + }; #endif //QGSARRAYUTILS_H diff --git a/tests/src/core/testqgsarrayutils.cpp b/tests/src/core/testqgsarrayutils.cpp index c8072dd2d78..4b468392db0 100644 --- a/tests/src/core/testqgsarrayutils.cpp +++ b/tests/src/core/testqgsarrayutils.cpp @@ -22,12 +22,14 @@ class TestQgsArrayUtils : public QObject Q_OBJECT private slots: - void testListToPgArrayStringAndBack(); - void testFallbackPgArrayStringToList(); + void testPgArrayStringToListAndBack(); + void testUnquotedPgArrayStringToListAndBack(); + void testNumberArrayStringToListAndBack(); + void testMultidimensionalPgArrayStringToListAndBack(); }; -void TestQgsArrayUtils::testListToPgArrayStringAndBack() +void TestQgsArrayUtils::testPgArrayStringToListAndBack() { QVariantList vl; @@ -41,14 +43,13 @@ void TestQgsArrayUtils::testListToPgArrayStringAndBack() vl.push_back( QStringLiteral( "...all the etceteras" ) ); QString string = QStringLiteral( "{\"one\",\"}two{\",\"thr\\\"ee\",\"fo,ur\",\"fiv'e\",6,\"and 7garcìa][\",\"...all the etceteras\"}" ); - - QCOMPARE( QgsArrayUtils::build( vl ), string ); + QCOMPARE( QgsArrayUtils::parse( string ), vl ); // and back - QCOMPARE( QgsArrayUtils::parse( string ), vl ); + QCOMPARE( QgsArrayUtils::build( vl ), string ); } -void TestQgsArrayUtils::testFallbackPgArrayStringToList() +void TestQgsArrayUtils::testUnquotedPgArrayStringToListAndBack() { //there might have been used QVariantList vl; @@ -60,13 +61,43 @@ void TestQgsArrayUtils::testFallbackPgArrayStringToList() vl.push_back( QStringLiteral( "that genius" ) ); QString fallback_string = QStringLiteral( "{one,two,three,four,five,that genius}" ); - QCOMPARE( QgsArrayUtils::parse( fallback_string ), vl ); - // and back with quotes + // and back including quotes QString new_string = QStringLiteral( "{\"one\",\"two\",\"three\",\"four\",\"five\",\"that genius\"}" ); QCOMPARE( QgsArrayUtils::build( vl ), new_string ); } +void TestQgsArrayUtils::testNumberArrayStringToListAndBack() +{ + //there might have been used + QVariantList vl; + vl.push_back( 1 ); + vl.push_back( 2 ); + vl.push_back( 3 ); + vl.push_back( 4 ); + + QString number_string = QStringLiteral( "{1,2,3,4}" ); + QCOMPARE( QgsArrayUtils::parse( number_string ), vl ); + + // and back without quotes + QCOMPARE( QgsArrayUtils::build( vl ), number_string ); +} + +void TestQgsArrayUtils::testMultidimensionalPgArrayStringToListAndBack() +{ + //there might have been used + QVariantList vl; + vl.push_back( QStringLiteral( "{one one third,one two third,one three third}" ) ); + vl.push_back( QStringLiteral( "{\"two one third\",\"two two third\",\"two three third\"}" ) ); + vl.push_back( QStringLiteral( "{three one third,three two third,three three third}" ) ); + + QString string = QStringLiteral( "{{one one third,one two third,one three third},{\"two one third\",\"two two third\",\"two three third\"},{three one third,three two third,three three third}}" ); + QCOMPARE( QgsArrayUtils::parse( string ), vl ); + + // and back without quotes + QCOMPARE( QgsArrayUtils::build( vl ), string ); +} + QGSTEST_MAIN( TestQgsArrayUtils ) #include "testqgsarrayutils.moc"