From 8b74201470d98eb02871211f510a1edcaf6980fc Mon Sep 17 00:00:00 2001 From: nirvn Date: Tue, 25 Oct 2016 14:36:04 +0700 Subject: [PATCH 1/2] [FEATURE] array support for the replace() expression function --- resources/function_help/json/replace | 10 +++++--- src/core/qgsexpression.cpp | 38 +++++++++++++++++++++++++--- tests/src/core/testqgsexpression.cpp | 4 +++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/resources/function_help/json/replace b/resources/function_help/json/replace index 509b2e46261..a5d92b77fee 100644 --- a/resources/function_help/json/replace +++ b/resources/function_help/json/replace @@ -1,10 +1,12 @@ { "name": "replace", "type": "function", - "description": "Returns a string with the the supplied string replaced.", + "description": "Returns a string with the supplied string or array of strings replaced by a string or an array of strings.", "arguments": [ {"arg":"string","description":"the input string"}, - {"arg":"before","description":"the string to replace"}, - {"arg":"after","description":"the string to use as a replacement"}], - "examples": [ { "expression":"replace('QGIS SHOULD ROCK','SHOULD','DOES')", "returns":"'QGIS DOES ROCK'"} + {"arg":"before","description":"the string or array of strings to replace"}, + {"arg":"after","description":"the string or array of strings to use as a replacement"}], + "examples": [ { "expression":"replace('QGIS SHOULD ROCK','SHOULD','DOES')", "returns":"'QGIS DOES ROCK'"}, + { "expression":"replace('QGIS ABC',array('A','B','C'),array('X','Y','Z'))", "returns":"'QGIS XYZ'"}, + { "expression":"replace('QGIS',array('Q','S'),'')", "returns":"'GI'"} ] } diff --git a/src/core/qgsexpression.cpp b/src/core/qgsexpression.cpp index e6a6f6a9ed4..e4307700e36 100644 --- a/src/core/qgsexpression.cpp +++ b/src/core/qgsexpression.cpp @@ -1215,9 +1215,41 @@ static QVariant fcnLength( const QVariantList& values, const QgsExpressionContex static QVariant fcnReplace( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) { QString str = getStringValue( values.at( 0 ), parent ); - QString before = getStringValue( values.at( 1 ), parent ); - QString after = getStringValue( values.at( 2 ), parent ); - return QVariant( str.replace( before, after ) ); + QVariantList before; + QVariantList after; + bool isSingleReplacement = false; + + if ( values.at( 1 ).type() != QVariant::List && values.at( 2 ).type() != QVariant::StringList ) + { + before = QVariantList() << getStringValue( values.at( 1 ), parent ); + } + else + { + before = getListValue( values.at( 1 ), parent ); + } + + if ( values.at( 2 ).type() != QVariant::List && values.at( 2 ).type() != QVariant::StringList ) + { + after = QVariantList() << getStringValue( values.at( 2 ), parent ); + isSingleReplacement = true; + } + else + { + after = getListValue( values.at( 2 ), parent ); + } + + if ( !isSingleReplacement && before.length() != after.length() ) + { + parent->setEvalErrorString( QObject::tr( "Invalid pair of array, length not identical" ) ); + return QVariant(); + } + + for ( int i = 0; i < before.length(); i++ ) + { + str = str.replace( before.at( i ).toString(), after.at( isSingleReplacement ? 0 : i ).toString() ); + } + + return QVariant( str ); } static QVariant fcnRegexpReplace( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) { diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 0d8d068c2c8..01eb19d3ee1 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -821,6 +821,10 @@ class TestQgsExpression: public QObject QTest::newRow( "upper" ) << "upper('HeLLo')" << false << QVariant( "HELLO" ); QTest::newRow( "length" ) << "length('HeLLo')" << false << QVariant( 5 ); QTest::newRow( "replace" ) << "replace('HeLLo', 'LL', 'xx')" << false << QVariant( "Hexxo" ); + QTest::newRow( "replace (array replaced by array)" ) << "replace('321', array('1','2','3'), array('7','8','9'))" << false << QVariant( "987" ); + QTest::newRow( "replace (array replaced by string)" ) << "replace('12345', array('2','4'), '')" << false << QVariant( "135" ); + QTest::newRow( "replace (unbalanced array, before > after)" ) << "replace('12345', array('1','2','3'), array('6','7'))" << true << QVariant(); + QTest::newRow( "replace (unbalanced array, before < after)" ) << "replace('12345', array('1','2'), array('6','7','8'))" << true << QVariant(); QTest::newRow( "regexp_replace" ) << "regexp_replace('HeLLo','[eL]+', '-')" << false << QVariant( "H-o" ); QTest::newRow( "regexp_replace greedy" ) << "regexp_replace('HeLLo','(?<=H).*L', '-')" << false << QVariant( "H-o" ); QTest::newRow( "regexp_replace non greedy" ) << "regexp_replace('HeLLo','(?<=H).*?L', '-')" << false << QVariant( "H-Lo" ); From 3fb2d9e4e3ee75632e367644fd229e24ce9a73fb Mon Sep 17 00:00:00 2001 From: nirvn Date: Wed, 26 Oct 2016 17:41:55 +0700 Subject: [PATCH 2/2] [expression] further improve replace() to support a map argument --- resources/function_help/json/replace | 24 +++++--- src/core/qgsexpression.cpp | 82 +++++++++++++++++----------- tests/src/core/testqgsexpression.cpp | 1 + 3 files changed, 68 insertions(+), 39 deletions(-) diff --git a/resources/function_help/json/replace b/resources/function_help/json/replace index a5d92b77fee..0f0ff23dab2 100644 --- a/resources/function_help/json/replace +++ b/resources/function_help/json/replace @@ -1,12 +1,20 @@ { "name": "replace", "type": "function", - "description": "Returns a string with the supplied string or array of strings replaced by a string or an array of strings.", - "arguments": [ {"arg":"string","description":"the input string"}, - {"arg":"before","description":"the string or array of strings to replace"}, - {"arg":"after","description":"the string or array of strings to use as a replacement"}], - "examples": [ { "expression":"replace('QGIS SHOULD ROCK','SHOULD','DOES')", "returns":"'QGIS DOES ROCK'"}, - { "expression":"replace('QGIS ABC',array('A','B','C'),array('X','Y','Z'))", "returns":"'QGIS XYZ'"}, - { "expression":"replace('QGIS',array('Q','S'),'')", "returns":"'GI'"} - ] + "description": "Returns a string with the supplied string, array, or map of strings replaced.", + "variants": [ + { "variant": "String & array variant", + "variant_description": "Returns a string with the supplied string or array of strings replaced by a string or an array of strings.", + "arguments": [ {"arg":"string","description":"the input string"}, + {"arg":"before","description":"the string or array of strings to replace"}, + {"arg":"after","description":"the string or array of strings to use as a replacement"}], + "examples": [ { "expression":"replace('QGIS SHOULD ROCK','SHOULD','DOES')", "returns":"'QGIS DOES ROCK'"}, + { "expression":"replace('QGIS ABC',array('A','B','C'),array('X','Y','Z'))", "returns":"'QGIS XYZ'"}, + { "expression":"replace('QGIS',array('Q','S'),'')", "returns":"'GI'"} ] }, + { "variant": "Map variant", + "variant_description": "Returns a string with the supplied map keys replaced by paired values.", + "arguments": [ {"arg":"string","description":"the input string"}, + {"arg":"map","description":"the map containing keys and values"} ], + "examples": [ { "expression":"replace('APP SHOULD ROCK',map('APP','QGIS','SHOULD','DOES'))", "returns":"'QGIS DOES ROCK'"} ] + }] } diff --git a/src/core/qgsexpression.cpp b/src/core/qgsexpression.cpp index e4307700e36..cd17b777772 100644 --- a/src/core/qgsexpression.cpp +++ b/src/core/qgsexpression.cpp @@ -1214,42 +1214,62 @@ static QVariant fcnLength( const QVariantList& values, const QgsExpressionContex static QVariant fcnReplace( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) { - QString str = getStringValue( values.at( 0 ), parent ); - QVariantList before; - QVariantList after; - bool isSingleReplacement = false; - - if ( values.at( 1 ).type() != QVariant::List && values.at( 2 ).type() != QVariant::StringList ) + if ( values.count() == 2 && values.at( 1 ).type() == QVariant::Map ) { - before = QVariantList() << getStringValue( values.at( 1 ), parent ); + QString str = getStringValue( values.at( 0 ), parent ); + QVariantMap map = getMapValue( values.at( 1 ), parent ); + + for ( QVariantMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it ) + { + str = str.replace( it.key(), it.value().toString() ); + } + + return QVariant( str ); + } + else if ( values.count() == 3 ) + { + QString str = getStringValue( values.at( 0 ), parent ); + QVariantList before; + QVariantList after; + bool isSingleReplacement = false; + + if ( values.at( 1 ).type() != QVariant::List && values.at( 2 ).type() != QVariant::StringList ) + { + before = QVariantList() << getStringValue( values.at( 1 ), parent ); + } + else + { + before = getListValue( values.at( 1 ), parent ); + } + + if ( values.at( 2 ).type() != QVariant::List && values.at( 2 ).type() != QVariant::StringList ) + { + after = QVariantList() << getStringValue( values.at( 2 ), parent ); + isSingleReplacement = true; + } + else + { + after = getListValue( values.at( 2 ), parent ); + } + + if ( !isSingleReplacement && before.length() != after.length() ) + { + parent->setEvalErrorString( QObject::tr( "Invalid pair of array, length not identical" ) ); + return QVariant(); + } + + for ( int i = 0; i < before.length(); i++ ) + { + str = str.replace( before.at( i ).toString(), after.at( isSingleReplacement ? 0 : i ).toString() ); + } + + return QVariant( str ); } else { - before = getListValue( values.at( 1 ), parent ); - } - - if ( values.at( 2 ).type() != QVariant::List && values.at( 2 ).type() != QVariant::StringList ) - { - after = QVariantList() << getStringValue( values.at( 2 ), parent ); - isSingleReplacement = true; - } - else - { - after = getListValue( values.at( 2 ), parent ); - } - - if ( !isSingleReplacement && before.length() != after.length() ) - { - parent->setEvalErrorString( QObject::tr( "Invalid pair of array, length not identical" ) ); + parent->setEvalErrorString( QObject::tr( "Function replace requires 2 or 3 arguments" ) ); return QVariant(); } - - for ( int i = 0; i < before.length(); i++ ) - { - str = str.replace( before.at( i ).toString(), after.at( isSingleReplacement ? 0 : i ).toString() ); - } - - return QVariant( str ); } static QVariant fcnRegexpReplace( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) { @@ -3521,7 +3541,7 @@ const QList& QgsExpression::Functions() << new StaticFunction( QStringLiteral( "char" ), 1, fcnChar, QStringLiteral( "String" ) ) << new StaticFunction( QStringLiteral( "wordwrap" ), ParameterList() << Parameter( QStringLiteral( "text" ) ) << Parameter( QStringLiteral( "length" ) ) << Parameter( QStringLiteral( "delimiter" ), true, "" ), fcnWordwrap, QStringLiteral( "String" ) ) << new StaticFunction( QStringLiteral( "length" ), ParameterList() << Parameter( QStringLiteral( "text" ), true, "" ), fcnLength, QStringList() << QStringLiteral( "String" ) << QStringLiteral( "GeometryGroup" ) ) - << new StaticFunction( QStringLiteral( "replace" ), 3, fcnReplace, QStringLiteral( "String" ) ) + << new StaticFunction( QStringLiteral( "replace" ), -1, fcnReplace, QStringLiteral( "String" ) ) << new StaticFunction( QStringLiteral( "regexp_replace" ), 3, fcnRegexpReplace, QStringLiteral( "String" ) ) << new StaticFunction( QStringLiteral( "regexp_substr" ), 2, fcnRegexpSubstr, QStringLiteral( "String" ) ) << new StaticFunction( QStringLiteral( "substr" ), 3, fcnSubstr, QStringLiteral( "String" ) ) diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 01eb19d3ee1..a35ae35a9e1 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -825,6 +825,7 @@ class TestQgsExpression: public QObject QTest::newRow( "replace (array replaced by string)" ) << "replace('12345', array('2','4'), '')" << false << QVariant( "135" ); QTest::newRow( "replace (unbalanced array, before > after)" ) << "replace('12345', array('1','2','3'), array('6','7'))" << true << QVariant(); QTest::newRow( "replace (unbalanced array, before < after)" ) << "replace('12345', array('1','2'), array('6','7','8'))" << true << QVariant(); + QTest::newRow( "replace (map)" ) << "replace('APP SHOULD ROCK',map('APP','QGIS','SHOULD','DOES'))" << false << QVariant( "QGIS DOES ROCK" ); QTest::newRow( "regexp_replace" ) << "regexp_replace('HeLLo','[eL]+', '-')" << false << QVariant( "H-o" ); QTest::newRow( "regexp_replace greedy" ) << "regexp_replace('HeLLo','(?<=H).*L', '-')" << false << QVariant( "H-o" ); QTest::newRow( "regexp_replace non greedy" ) << "regexp_replace('HeLLo','(?<=H).*?L', '-')" << false << QVariant( "H-Lo" );