Skip to content

Commit

Permalink
Merge pull request #3723 from nirvn/regexp_matches
Browse files Browse the repository at this point in the history
[FEATURE] add regexp_matches() function
  • Loading branch information
nyalldawson authored Nov 7, 2016
2 parents 4e4bb2b + 1d245b2 commit 3c530a6
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 2 deletions.
2 changes: 1 addition & 1 deletion resources/function_help/json/array_to_string
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"arguments": [
{"arg":"array", "description":"the input array"},
{"arg":"delimiter","optional":true,"default":"','","description":"the string delimiter used to separate concatenated array elements"},
{"arg":"emptyvalue","optional":true,"default":"''","description":"the optional string to use as replacement to empty values"}],
{"arg":"empty_value","optional":true,"default":"''","description":"the optional string to use as replacement for empty (zero length) matches"}],
"examples": [ { "expression":"array_to_string(array('1','2','3'),',')", "returns":"'1,2,3'"},
{ "expression":"array_to_string(array('1','','3'),',','0')", "returns":"'1,0,3'"}
]
Expand Down
12 changes: 12 additions & 0 deletions resources/function_help/json/regexp_matches
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "regexp_matches",
"type": "function",
"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.",
"arguments": [
{"arg":"string", "description":"the string to capture groups from against the regular expression"},
{"arg":"regex","description":"the regular expression used to capture groups"},
{"arg":"empty_value","optional":true,"default":"''","description":"the optional string to use as replacement for empty (zero length) matches"}],
"examples": [ { "expression":"regexp_matches('qgis=>rocks','(.*)=>(.*)')", "returns":"array: 'qgis', 'rocks'"},
{ "expression":"regexp_matches('key=>','(.*)=>(.*)','empty value')", "returns":"array: 'key', 'empty value'"}
]
}
2 changes: 1 addition & 1 deletion resources/function_help/json/string_to_array
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"arguments": [
{"arg":"string", "description":"the input string"},
{"arg":"delimiter","optional":true,"default":"','","description":"the string delimiter used to split the input string"},
{"arg":"emptyvalue","optional":true,"default":"''","description":"the optional string to use as replacement to empty values"}],
{"arg":"empty_value","optional":true,"default":"''","description":"the optional string to use as replacement for empty (zero length) matches"}],
"examples": [ { "expression":"string_to_array('1,2,3',',')", "returns":"array: '1', '2', '3'"},
{ "expression":"string_to_array('1,,3',',','0')", "returns":"array: '1', '0', '3'"}
]
Expand Down
35 changes: 35 additions & 0 deletions src/core/qgsexpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,39 @@ static QVariant fcnRegexpMatch( const QVariantList& values, const QgsExpressionC
return QVariant( str.contains( re ) ? 1 : 0 );
}

static QVariant fcnRegexpMatches( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
QString regexp = getStringValue( values.at( 1 ), parent );
QString empty = getStringValue( values.at( 2 ), parent );

QRegularExpression re( regexp );
if ( !re.isValid() )
{
parent->setEvalErrorString( QObject::tr( "Invalid regular expression '%1': %2" ).arg( regexp, re.errorString() ) );
return QVariant();
}

QRegularExpressionMatch matches = re.match( str );
if ( matches.hasMatch() )
{
QVariantList array;
QStringList list = matches.capturedTexts();

// Skip the first string to only return captured groups
for ( QStringList::const_iterator it = ++list.constBegin(); it != list.constEnd(); ++it )
{
array += ( *it ).isEmpty() == false ? *it : empty;
}

return QVariant( array );
}
else
{
return QVariant();
}
}

static QVariant fcnRegexpSubstr( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
Expand Down Expand Up @@ -3745,6 +3778,8 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
<< new StaticFunction( QStringLiteral( "concatenate" ), aggParams << Parameter( QStringLiteral( "concatenator" ), true ), fcnAggregateStringConcat, QStringLiteral( "Aggregates" ), QString(), False, QSet<QString>(), true )

<< new StaticFunction( QStringLiteral( "regexp_match" ), ParameterList() << Parameter( QStringLiteral( "string" ) ) << Parameter( QStringLiteral( "regex" ) ), fcnRegexpMatch, QStringList() << QStringLiteral( "Conditionals" ) << QStringLiteral( "String" ) )
<< new StaticFunction( QStringLiteral( "regexp_matches" ), ParameterList() << Parameter( QStringLiteral( "string" ) ) << Parameter( QStringLiteral( "regex" ) ) << Parameter( QStringLiteral( "emptyvalue" ), true, "" ), fcnRegexpMatches, QStringLiteral( "Arrays" ) )

<< new StaticFunction( QStringLiteral( "now" ), 0, fcnNow, QStringLiteral( "Date and Time" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "$now" ) )
<< new StaticFunction( QStringLiteral( "age" ), 2, fcnAge, QStringLiteral( "Date and Time" ) )
<< new StaticFunction( QStringLiteral( "year" ), 1, fcnYear, QStringLiteral( "Date and Time" ) )
Expand Down
6 changes: 6 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,11 @@ class TestQgsExpression: public QObject
QTest::newRow( "regexp_substr non-greedy" ) << "regexp_substr('abc123','(\\\\d+?)')" << false << QVariant( "1" );
QTest::newRow( "regexp_substr no hit" ) << "regexp_substr('abcdef','(\\\\d+)')" << false << QVariant( "" );
QTest::newRow( "regexp_substr invalid" ) << "regexp_substr('abc123','([[[')" << true << QVariant();
QTest::newRow( "regexp_matches" ) << "array_get(regexp_matches('qgis=>rOcks;hello=>world','qgis=>(.*)[;$]'),0)" << false << QVariant( "rOcks" );
QTest::newRow( "regexp_matches empty custom value" ) << "array_get(regexp_matches('qgis=>;hello=>world','qgis=>(.*)[;$]','empty'),0)" << false << QVariant( "empty" );
QTest::newRow( "regexp_matches no match" ) << "regexp_matches('123','no()match')" << false << QVariant();
QTest::newRow( "regexp_matches no capturing group" ) << "regexp_matches('some string','.*')" << false << QVariant( QVariantList() );
QTest::newRow( "regexp_matches invalid" ) << "regexp_matches('invalid','(')" << true << QVariant();
QTest::newRow( "strpos" ) << "strpos('Hello World','World')" << false << QVariant( 7 );
QTest::newRow( "strpos outside" ) << "strpos('Hello World','blah')" << false << QVariant( 0 );
QTest::newRow( "left" ) << "left('Hello World',5)" << false << QVariant( "Hello" );
Expand Down Expand Up @@ -2266,6 +2271,7 @@ class TestQgsExpression: public QObject
builderExpected << "world";
QCOMPARE( QgsExpression( "array('hello', 'world')" ).evaluate( &context ), QVariant( builderExpected ) );
QCOMPARE( QgsExpression( "string_to_array('hello,world',',')" ).evaluate( &context ), QVariant( builderExpected ) );
QCOMPARE( QgsExpression( "regexp_matches('hello=>world','([A-Za-z]*)=>([A-Za-z]*)')" ).evaluate( &context ), QVariant( builderExpected ) );

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

Expand Down

0 comments on commit 3c530a6

Please sign in to comment.