Skip to content

Commit 1d245b2

Browse files
committed
[FEATURE] add regexp_matches() function
The new function returns an array of strings captured by capturing groups in a supplied regular expression. For e.g., the following expression: regexp_matches('qgis=>rocks','(.*)=>(.*)') will return the following array: 'qgis', 'rocks'.
1 parent 9a2ca1c commit 1d245b2

File tree

5 files changed

+55
-2
lines changed

5 files changed

+55
-2
lines changed

resources/function_help/json/array_to_string

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"arguments": [
66
{"arg":"array", "description":"the input array"},
77
{"arg":"delimiter","optional":true,"default":"','","description":"the string delimiter used to separate concatenated array elements"},
8-
{"arg":"emptyvalue","optional":true,"default":"''","description":"the optional string to use as replacement to empty values"}],
8+
{"arg":"empty_value","optional":true,"default":"''","description":"the optional string to use as replacement for empty (zero length) matches"}],
99
"examples": [ { "expression":"array_to_string(array('1','2','3'),',')", "returns":"'1,2,3'"},
1010
{ "expression":"array_to_string(array('1','','3'),',','0')", "returns":"'1,0,3'"}
1111
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "regexp_matches",
3+
"type": "function",
4+
"description": "Returns an array of all strings captured by capturing groups, in the order the groups themselves appear in the supplied regular expression against a string.",
5+
"arguments": [
6+
{"arg":"string", "description":"the string to capture groups from against the regular expression"},
7+
{"arg":"regex","description":"the regular expression used to capture groups"},
8+
{"arg":"empty_value","optional":true,"default":"''","description":"the optional string to use as replacement for empty (zero length) matches"}],
9+
"examples": [ { "expression":"regexp_matches('qgis=>rocks','(.*)=>(.*)')", "returns":"array: 'qgis', 'rocks'"},
10+
{ "expression":"regexp_matches('key=>','(.*)=>(.*)','empty value')", "returns":"array: 'key', 'empty value'"}
11+
]
12+
}

resources/function_help/json/string_to_array

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"arguments": [
66
{"arg":"string", "description":"the input string"},
77
{"arg":"delimiter","optional":true,"default":"','","description":"the string delimiter used to split the input string"},
8-
{"arg":"emptyvalue","optional":true,"default":"''","description":"the optional string to use as replacement to empty values"}],
8+
{"arg":"empty_value","optional":true,"default":"''","description":"the optional string to use as replacement for empty (zero length) matches"}],
99
"examples": [ { "expression":"string_to_array('1,2,3',',')", "returns":"array: '1', '2', '3'"},
1010
{ "expression":"string_to_array('1,,3',',','0')", "returns":"array: '1', '0', '3'"}
1111
]

src/core/qgsexpression.cpp

+35
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,39 @@ static QVariant fcnRegexpMatch( const QVariantList& values, const QgsExpressionC
13351335
return QVariant( str.contains( re ) ? 1 : 0 );
13361336
}
13371337

1338+
static QVariant fcnRegexpMatches( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
1339+
{
1340+
QString str = getStringValue( values.at( 0 ), parent );
1341+
QString regexp = getStringValue( values.at( 1 ), parent );
1342+
QString empty = getStringValue( values.at( 2 ), parent );
1343+
1344+
QRegularExpression re( regexp );
1345+
if ( !re.isValid() )
1346+
{
1347+
parent->setEvalErrorString( QObject::tr( "Invalid regular expression '%1': %2" ).arg( regexp, re.errorString() ) );
1348+
return QVariant();
1349+
}
1350+
1351+
QRegularExpressionMatch matches = re.match( str );
1352+
if ( matches.hasMatch() )
1353+
{
1354+
QVariantList array;
1355+
QStringList list = matches.capturedTexts();
1356+
1357+
// Skip the first string to only return captured groups
1358+
for ( QStringList::const_iterator it = ++list.constBegin(); it != list.constEnd(); ++it )
1359+
{
1360+
array += ( *it ).isEmpty() == false ? *it : empty;
1361+
}
1362+
1363+
return QVariant( array );
1364+
}
1365+
else
1366+
{
1367+
return QVariant();
1368+
}
1369+
}
1370+
13381371
static QVariant fcnRegexpSubstr( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
13391372
{
13401373
QString str = getStringValue( values.at( 0 ), parent );
@@ -3745,6 +3778,8 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
37453778
<< new StaticFunction( QStringLiteral( "concatenate" ), aggParams << Parameter( QStringLiteral( "concatenator" ), true ), fcnAggregateStringConcat, QStringLiteral( "Aggregates" ), QString(), False, QSet<QString>(), true )
37463779

37473780
<< new StaticFunction( QStringLiteral( "regexp_match" ), ParameterList() << Parameter( QStringLiteral( "string" ) ) << Parameter( QStringLiteral( "regex" ) ), fcnRegexpMatch, QStringList() << QStringLiteral( "Conditionals" ) << QStringLiteral( "String" ) )
3781+
<< new StaticFunction( QStringLiteral( "regexp_matches" ), ParameterList() << Parameter( QStringLiteral( "string" ) ) << Parameter( QStringLiteral( "regex" ) ) << Parameter( QStringLiteral( "emptyvalue" ), true, "" ), fcnRegexpMatches, QStringLiteral( "Arrays" ) )
3782+
37483783
<< new StaticFunction( QStringLiteral( "now" ), 0, fcnNow, QStringLiteral( "Date and Time" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "$now" ) )
37493784
<< new StaticFunction( QStringLiteral( "age" ), 2, fcnAge, QStringLiteral( "Date and Time" ) )
37503785
<< new StaticFunction( QStringLiteral( "year" ), 1, fcnYear, QStringLiteral( "Date and Time" ) )

tests/src/core/testqgsexpression.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,11 @@ class TestQgsExpression: public QObject
841841
QTest::newRow( "regexp_substr non-greedy" ) << "regexp_substr('abc123','(\\\\d+?)')" << false << QVariant( "1" );
842842
QTest::newRow( "regexp_substr no hit" ) << "regexp_substr('abcdef','(\\\\d+)')" << false << QVariant( "" );
843843
QTest::newRow( "regexp_substr invalid" ) << "regexp_substr('abc123','([[[')" << true << QVariant();
844+
QTest::newRow( "regexp_matches" ) << "array_get(regexp_matches('qgis=>rOcks;hello=>world','qgis=>(.*)[;$]'),0)" << false << QVariant( "rOcks" );
845+
QTest::newRow( "regexp_matches empty custom value" ) << "array_get(regexp_matches('qgis=>;hello=>world','qgis=>(.*)[;$]','empty'),0)" << false << QVariant( "empty" );
846+
QTest::newRow( "regexp_matches no match" ) << "regexp_matches('123','no()match')" << false << QVariant();
847+
QTest::newRow( "regexp_matches no capturing group" ) << "regexp_matches('some string','.*')" << false << QVariant( QVariantList() );
848+
QTest::newRow( "regexp_matches invalid" ) << "regexp_matches('invalid','(')" << true << QVariant();
844849
QTest::newRow( "strpos" ) << "strpos('Hello World','World')" << false << QVariant( 7 );
845850
QTest::newRow( "strpos outside" ) << "strpos('Hello World','blah')" << false << QVariant( 0 );
846851
QTest::newRow( "left" ) << "left('Hello World',5)" << false << QVariant( "Hello" );
@@ -2266,6 +2271,7 @@ class TestQgsExpression: public QObject
22662271
builderExpected << "world";
22672272
QCOMPARE( QgsExpression( "array('hello', 'world')" ).evaluate( &context ), QVariant( builderExpected ) );
22682273
QCOMPARE( QgsExpression( "string_to_array('hello,world',',')" ).evaluate( &context ), QVariant( builderExpected ) );
2274+
QCOMPARE( QgsExpression( "regexp_matches('hello=>world','([A-Za-z]*)=>([A-Za-z]*)')" ).evaluate( &context ), QVariant( builderExpected ) );
22692275

22702276
QCOMPARE( QgsExpression( "array_length(\"strings\")" ).evaluate( &context ), QVariant( 2 ) );
22712277

0 commit comments

Comments
 (0)