improved parsing and building function to handle multidimensional arrays

code mostly taken from this integration in the postgresprovider / postgresconn
x
This commit is contained in:
David Signer 2019-07-17 16:24:42 +02:00
parent 98a271b365
commit 109b4d7f21
3 changed files with 143 additions and 25 deletions

View File

@ -14,33 +14,109 @@
***************************************************************************/
#include "qgsarrayutils.h"
#include "qgsmessagelog.h"
#include <QDebug>
#include <nlohmann/json.hpp>
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;
}
}

View File

@ -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

View File

@ -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"