Skip to content

Commit

Permalink
Merge pull request #31189 from m-kuhn/backport-30610-to-release-3_8_a
Browse files Browse the repository at this point in the history
Backport 3.8:  Fix multi-selection on value relation widget using string fields
  • Loading branch information
m-kuhn committed Aug 19, 2019
2 parents 4470baa + 8b3461a commit 27b466f
Show file tree
Hide file tree
Showing 10 changed files with 1,045 additions and 41 deletions.
54 changes: 54 additions & 0 deletions python/core/auto_generated/qgspostgresstringutils.sip.in
@@ -0,0 +1,54 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgspostgresstringutils.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/



%ModuleHeaderCode
#include "qgspostgresstringutils.h"
%End

class QgsPostgresStringUtils
{
%Docstring
The QgsPostgresStringUtils provides functions to handle postgres array like formatted lists in strings.

.. versionadded:: 3.8
%End

%TypeHeaderCode
#include "qgspostgresstringutils.h"
%End
public:

static QVariantList parseArray( const QString &string );
%Docstring
Returns a QVariantList created out of a string containing an array in postgres array format {1,2,3} or {"a","b","c"}

:param string: The formatted list in a string

.. versionadded:: 3.8
%End

static QString buildArray( const QVariantList &list );
%Docstring
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

.. versionadded:: 3.8
%End

};

/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgspostgresstringutils.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
1 change: 1 addition & 0 deletions python/core/core_auto.sip
Expand Up @@ -10,6 +10,7 @@
%Include auto_generated/qgsactionscope.sip
%Include auto_generated/qgsactionmanager.sip
%Include auto_generated/qgsaggregatecalculator.sip
%Include auto_generated/qgspostgresstringutils.sip
%Include auto_generated/qgsattributes.sip
%Include auto_generated/qgsattributetableconfig.sip
%Include auto_generated/qgsattributeeditorelement.sip
Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -148,6 +148,7 @@ SET(QGIS_CORE_SRCS
qgsactionmanager.cpp
qgsaggregatecalculator.cpp
qgsanimatedicon.cpp
qgspostgresstringutils.cpp
qgsattributes.cpp
qgsattributetableconfig.cpp
qgsattributeeditorelement.cpp
Expand Down Expand Up @@ -845,6 +846,7 @@ SET(QGIS_CORE_HDRS
qgsactionscope.h
qgsactionmanager.h
qgsaggregatecalculator.h
qgspostgresstringutils.h
qgsattributes.h
qgsattributetableconfig.h
qgsattributeeditorelement.h
Expand Down
64 changes: 36 additions & 28 deletions src/core/fieldformatter/qgsvaluerelationfieldformatter.cpp
Expand Up @@ -22,6 +22,7 @@
#include "qgsapplication.h"
#include "qgsexpressioncontextutils.h"
#include "qgsvectorlayerref.h"
#include "qgspostgresstringutils.h"

#include <nlohmann/json.hpp>
using json = nlohmann::json;
Expand Down Expand Up @@ -173,43 +174,50 @@ QStringList QgsValueRelationFieldFormatter::valueToStringList( const QVariant &v
{
checkList = value.toStringList();
}
else if ( value.type() == QVariant::String )
else
{
// This must be an array representation
auto newVal { value };
if ( newVal.toString().trimmed().startsWith( '{' ) )
{
newVal = QVariant( newVal.toString().trimmed().mid( 1 ).mid( 0, newVal.toString().length() - 2 ).prepend( '[' ).append( ']' ) );
}
if ( newVal.toString().trimmed().startsWith( '[' ) )
QVariantList valuesList;
if ( value.type() == QVariant::String )
{
try
// This must be an array representation
QVariant newVal { value };
if ( newVal.toString().trimmed().startsWith( '{' ) )
{
//normal case
valuesList = QgsPostgresStringUtils::parseArray( newVal.toString() );
}
else if ( newVal.toString().trimmed().startsWith( '[' ) )
{
for ( auto &element : json::parse( newVal.toString().toStdString() ) )
//fallback, in case it's a json array
try
{
if ( element.is_number_integer() )
{
checkList << QString::number( element.get<int>() );
}
else if ( element.is_number_unsigned() )
{
checkList << QString::number( element.get<unsigned>() );
}
else if ( element.is_string() )
for ( auto &element : json::parse( newVal.toString().toStdString() ) )
{
checkList << QString::fromStdString( element.get<std::string>() );
if ( element.is_number_integer() )
{
valuesList.push_back( element.get<int>() );
}
else if ( element.is_number_unsigned() )
{
valuesList.push_back( element.get<unsigned>() );
}
else if ( element.is_string() )
{
valuesList.push_back( QString::fromStdString( element.get<std::string>() ) );
}
}
}
}
catch ( json::parse_error &ex )
{
qDebug() << QString::fromStdString( ex.what() );
catch ( json::parse_error &ex )
{
qDebug() << QString::fromStdString( ex.what() );
}
}
}
}
else if ( value.type() == QVariant::List )
{
QVariantList valuesList( value.toList( ) );
else if ( value.type() == QVariant::List )
{
valuesList = value.toList( );
}

checkList.reserve( valuesList.size() );
for ( const QVariant &listItem : qgis::as_const( valuesList ) )
{
Expand Down
149 changes: 149 additions & 0 deletions src/core/qgspostgresstringutils.cpp
@@ -0,0 +1,149 @@
/***************************************************************************
qgspostgresstringutils.cpp
---------------------
begin : July 2019
copyright : (C) 2019 by David Signer
email : david at opengis dot ch
***************************************************************************
* *
* 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 "qgspostgresstringutils.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 QgsPostgresStringUtils::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 QgsPostgresStringUtils::parseArray( const QString &string )
{
QVariantList variantList;

//it's a postgres array
QString newVal = string.mid( 1, string.length() - 2 );

if ( newVal.trimmed().startsWith( '{' ) )
{
//it's a multidimensional array
QStringList values;
QString subarray = newVal;
while ( !subarray.isEmpty() )
{
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 );
}
}

return variantList;

}

QString QgsPostgresStringUtils::buildArray( const QVariantList &list )
{
QStringList sl;
for ( const QVariant &v : qgis::as_const( list ) )
{
// Convert to proper type
switch ( v.type() )
{
case QVariant::Type::Int:
case QVariant::Type::LongLong:
sl.push_back( v.toString() );
break;
default:
QString newS = v.toString();
if ( newS.startsWith( '{' ) )
{
sl.push_back( newS );
}
else
{
newS.replace( '\\', QStringLiteral( R"(\\)" ) );
newS.replace( '\"', QStringLiteral( R"(\")" ) );
sl.push_back( "\"" + newS + "\"" );
}
break;
}
}
//store as a formatted string because the fields supports only string
QString s = sl.join( ',' ).prepend( '{' ).append( '}' );

return s;
}
65 changes: 65 additions & 0 deletions src/core/qgspostgresstringutils.h
@@ -0,0 +1,65 @@
/***************************************************************************
qgspostgresstringutils.h
---------------------
begin : July 2019
copyright : (C) 2019 by David Signer
email : david at opengis dot ch
***************************************************************************
* *
* 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. *
* *
***************************************************************************/

#ifndef QGSPOSTGRESSTRINGUTILS_H
#define QGSPOSTGRESSTRINGUTILS_H

#include "qgis_core.h"
#include "qgis.h"
#include <QList>

#ifdef SIP_RUN
% ModuleHeaderCode
#include "qgspostgresstringutils.h"
% End
#endif

/**
* \ingroup core
* The QgsPostgresStringUtils provides functions to handle postgres array like formatted lists in strings.
* \since QGIS 3.8
*/
class CORE_EXPORT QgsPostgresStringUtils
{

public:

/**
* Returns a QVariantList created out of a string containing an array in postgres array format {1,2,3} or {"a","b","c"}
* \param string The formatted list in a string
* \since QGIS 3.8
*/
static QVariantList parseArray( const QString &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
*/
static QString buildArray( const QVariantList &list );

private:

/**
* get the string until the separator
* \param txt the input text
* \param i the current position
* \param sep the separator
* \since QGIS 3.8
*/
static QString getNextString( const QString &txt, int &i, const QString &sep );
};

#endif //QGSPOSTGRESSTRINGUTILS_H

0 comments on commit 27b466f

Please sign in to comment.