Skip to content

Commit b7a0c8c

Browse files
committed
[FEATURE] hstore_to_map() / map_to_hstore() expression functions
1 parent 68162a5 commit b7a0c8c

File tree

4 files changed

+118
-0
lines changed

4 files changed

+118
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "hstore_to_map",
3+
"type": "function",
4+
"description": "Creates a map from a hstore-formatted string.",
5+
"arguments": [
6+
{"arg":"string", "description":"the input string"}],
7+
"examples": [ { "expression":"hstore_to_map('qgis=>rocks')", "returns":"{ \"qgis\" : \"rocks\" }"}
8+
]
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "map_to_hstore",
3+
"type": "function",
4+
"description": "Merge map elements into a hstore-formatted string.",
5+
"arguments": [
6+
{"arg":"map", "description":"the input map"}],
7+
"examples": [ { "expression":"map_to_hstore(map('qgis','rocks'))", "returns":"\"qgis\"=>\"rocks\"}"}
8+
]
9+
}

src/core/expression/qgsexpressionfunction.cpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4367,6 +4367,95 @@ static QVariant fcnMapToJson( const QVariantList &values, const QgsExpressionCon
43674367
return document.toJson( QJsonDocument::Compact );
43684368
}
43694369

4370+
static QVariant fcnHstoreToMap( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
4371+
{
4372+
QString str = QgsExpressionUtils::getStringValue( values.at( 0 ), parent );
4373+
if ( str.isEmpty() )
4374+
return QVariantMap();
4375+
str = str.trimmed();
4376+
4377+
QVariantMap map;
4378+
QList<QString> bits;
4379+
QList<QString> seps;
4380+
seps << "=>" << ",";
4381+
int i = 0;
4382+
while ( i < str.length() )
4383+
{
4384+
while ( i < str.length() && str.at( i ).isSpace() )
4385+
++i;
4386+
QString current = str.mid( i );
4387+
QString sep = seps.at( bits.length() );
4388+
if ( current.startsWith( '"' ) )
4389+
{
4390+
QRegularExpression re( "^\"((?:\\\\.|[^\"\\\\])*)\".*" );
4391+
QRegularExpressionMatch match = re.match( current );
4392+
bits << QString();
4393+
if ( match.hasMatch() )
4394+
{
4395+
bits[bits.length() - 1] = match.captured( 1 ).replace( QLatin1String( "\\\"" ), QLatin1String( "\"" ) ).replace( QLatin1String( "\\\\" ), QLatin1String( "\\" ) );
4396+
i += match.captured( 1 ).length() + 2;
4397+
while ( i < str.length() && str.at( i ).isSpace() )
4398+
++i;
4399+
4400+
if ( str.midRef( i ).startsWith( sep ) )
4401+
{
4402+
i += sep.length();
4403+
}
4404+
else if ( i < str.length() )
4405+
{
4406+
// hstore string format broken, end construction
4407+
i += current.length();
4408+
}
4409+
}
4410+
else
4411+
{
4412+
// hstore string format broken, end construction
4413+
i += current.length();
4414+
bits[bits.length() - 1] = current.trimmed();
4415+
}
4416+
}
4417+
else
4418+
{
4419+
int sepPos = current.indexOf( sep );
4420+
if ( sepPos < 0 )
4421+
{
4422+
i += current.length();
4423+
bits << current.trimmed();
4424+
}
4425+
else
4426+
{
4427+
i += sepPos + sep.length();
4428+
bits << current.left( sepPos ).trimmed();
4429+
}
4430+
}
4431+
4432+
if ( bits.length() == 2 )
4433+
{
4434+
if ( !bits.at( 0 ).isEmpty() && !bits.at( 1 ).isEmpty() )
4435+
map[ bits.at( 0 ) ] = bits.at( 1 );
4436+
bits.clear();
4437+
}
4438+
}
4439+
4440+
return map;
4441+
}
4442+
4443+
static QVariant fcnMapToHstore( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
4444+
{
4445+
QVariantMap map = QgsExpressionUtils::getMapValue( values.at( 0 ), parent );
4446+
QStringList list;
4447+
4448+
for ( QVariantMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it )
4449+
{
4450+
QString key = it.key();
4451+
QString value = it.value().toString();
4452+
list << QString( "\"%1\"=>\"%2\"" ).arg( key.replace( "\\", "\\\\" ).replace( "\"", "\\\"" ),
4453+
value.replace( "\\", "\\\\" ).replace( "\"", "\\\"" ) );
4454+
}
4455+
4456+
return list.join( ',' );
4457+
}
4458+
43704459
static QVariant fcnMap( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
43714460
{
43724461
QVariantMap result;
@@ -5034,6 +5123,8 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
50345123
//functions for maps
50355124
<< new QgsStaticExpressionFunction( QStringLiteral( "json_to_map" ), 1, fcnJsonToMap, QStringLiteral( "Maps" ) )
50365125
<< new QgsStaticExpressionFunction( QStringLiteral( "map_to_json" ), 1, fcnMapToJson, QStringLiteral( "Maps" ) )
5126+
<< new QgsStaticExpressionFunction( QStringLiteral( "hstore_to_map" ), 1, fcnHstoreToMap, QStringLiteral( "Maps" ) )
5127+
<< new QgsStaticExpressionFunction( QStringLiteral( "map_to_hstore" ), 1, fcnMapToHstore, QStringLiteral( "Maps" ) )
50375128
<< new QgsStaticExpressionFunction( QStringLiteral( "map" ), -1, fcnMap, QStringLiteral( "Maps" ) )
50385129
<< new QgsStaticExpressionFunction( QStringLiteral( "map_get" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "map" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "key" ) ), fcnMapGet, QStringLiteral( "Maps" ) )
50395130
<< new QgsStaticExpressionFunction( QStringLiteral( "map_exist" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "map" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "key" ) ), fcnMapExist, QStringLiteral( "Maps" ) )

tests/src/core/testqgsexpression.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2888,6 +2888,15 @@ class TestQgsExpression: public QObject
28882888
QCOMPARE( QgsExpression( "json_to_map('{\"1\":\"one\",\"2\":\"two\",\"3\":\"three\"}')" ).evaluate( &context ), QVariant( concatExpected ) );
28892889
QCOMPARE( QgsExpression( "map_to_json(map('1','one','2','two','3','three'))" ).evaluate( &context ), QVariant( "{\"1\":\"one\",\"2\":\"two\",\"3\":\"three\"}" ) );
28902890

2891+
QCOMPARE( QgsExpression( "hstore_to_map('1=>one,2=>two,3=>three')" ).evaluate( &context ), QVariant( concatExpected ) );
2892+
QCOMPARE( QgsExpression( "map_to_hstore(map('1','one','2','two','3','three'))" ).evaluate( &context ), QVariant( "\"1\"=>\"one\",\"2\"=>\"two\",\"3\"=>\"three\"" ) );
2893+
2894+
QVariantMap hstoreExpected;
2895+
hstoreExpected[QStringLiteral( "test_quotes" )] = "test \"quote\" symbol";
2896+
hstoreExpected[QStringLiteral( "test_slashes" )] = "test \\slash symbol";
2897+
hstoreExpected[QStringLiteral( "test_mix" )] = "key with value in quotation marks";
2898+
QCOMPARE( QgsExpression( "hstore_to_map('\"test_quotes\"=>\"test \\\\\"quote\\\\\" symbol\",\"test_slashes\"=>\"test \\\\slash symbol\",test_mix=>\"key with value in quotation marks\"')" ).evaluate( &context ), QVariant( hstoreExpected ) );
2899+
28912900
QStringList keysExpected;
28922901
keysExpected << QStringLiteral( "1" ) << QStringLiteral( "2" );
28932902
QCOMPARE( QgsExpression( "map_akeys(\"map\")" ).evaluate( &context ), QVariant( keysExpected ) );

0 commit comments

Comments
 (0)