Skip to content
Permalink
Browse files

Merge pull request #7688 from m-kuhn/array_filter

Add array_filter expression function
  • Loading branch information
m-kuhn committed Aug 23, 2018
2 parents 0b96fd9 + d9e59e2 commit 51ec333199f8b423e2f99e9e28910050558f1141
@@ -0,0 +1,12 @@
{
"name": "array_filter",
"type": "function",
"description": "Returns an array with only the items for which the expression evaluates to true.",
"arguments": [
{"arg":"array","description":"an array"},
{"arg":"expression","description":"an expression to evaluate on each item. The variable `@element` will be replaced by the current value."}
],
"examples": [
{ "expression": "array_filter(array(1,2,3),@element < 3)", "returns":"array: 1, 2"}
]
}
@@ -4730,6 +4730,7 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()

// functions for arrays
<< new QgsArrayForeachExpressionFunction()
<< new QgsArrayFilterExpressionFunction()
<< new QgsStaticExpressionFunction( QStringLiteral( "array" ), -1, fcnArray, QStringLiteral( "Arrays" ), QString(), false, QSet<QString>(), false, QStringList(), true )
<< new QgsStaticExpressionFunction( QStringLiteral( "array_length" ), 1, fcnArrayLength, QStringLiteral( "Arrays" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "array_contains" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "array" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnArrayContains, QStringLiteral( "Arrays" ) )
@@ -4869,6 +4870,100 @@ bool QgsArrayForeachExpressionFunction::prepare( const QgsExpressionNodeFunction
return true;
}

QgsArrayFilterExpressionFunction::QgsArrayFilterExpressionFunction()
: QgsExpressionFunction( QStringLiteral( "array_filter" ), QgsExpressionFunction::ParameterList()
<< QgsExpressionFunction::Parameter( QStringLiteral( "array" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "expression" ) ),
QCoreApplication::tr( "Arrays" ) )
{

}

bool QgsArrayFilterExpressionFunction::isStatic( const QgsExpressionNodeFunction *node, QgsExpression *parent, const QgsExpressionContext *context ) const
{
bool isStatic = false;

QgsExpressionNode::NodeList *args = node->args();

if ( args->count() < 2 )
return false;

if ( args->at( 0 )->isStatic( parent, context ) && args->at( 1 )->isStatic( parent, context ) )
{
isStatic = true;
}
return isStatic;
}

QVariant QgsArrayFilterExpressionFunction::run( QgsExpressionNode::NodeList *args, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction *node )
{
Q_UNUSED( node )
QVariantList result;

if ( args->count() < 2 )
// error
return result;

const QVariantList array = args->at( 0 )->eval( parent, context ).toList();

QgsExpressionContext *subContext = const_cast<QgsExpressionContext *>( context );
std::unique_ptr< QgsExpressionContext > tempContext;
if ( !subContext )
{
tempContext = qgis::make_unique< QgsExpressionContext >();
subContext = tempContext.get();
}

QgsExpressionContextScope *subScope = new QgsExpressionContextScope();
subContext->appendScope( subScope );

for ( const QVariant &value : array )
{
subScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "element" ), value, true ) );
if ( args->at( 1 )->eval( parent, subContext ).toBool() )
result << value;
}

if ( context )
delete subContext->popScope();

return result;
}

QVariant QgsArrayFilterExpressionFunction::func( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction *node )
{
// This is a dummy function, all the real handling is in run
Q_UNUSED( values )
Q_UNUSED( context )
Q_UNUSED( parent )
Q_UNUSED( node )

Q_ASSERT( false );
return QVariant();
}

bool QgsArrayFilterExpressionFunction::prepare( const QgsExpressionNodeFunction *node, QgsExpression *parent, const QgsExpressionContext *context ) const
{
QgsExpressionNode::NodeList *args = node->args();

if ( args->count() < 2 )
// error
return false;

args->at( 0 )->prepare( parent, context );

QgsExpressionContext subContext;
if ( context )
subContext = *context;

QgsExpressionContextScope *subScope = new QgsExpressionContextScope();
subScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "element" ), QVariant(), true ) );
subContext.appendScope( subScope );

args->at( 1 )->prepare( parent, &subContext );

return true;
}
QgsWithVariableExpressionFunction::QgsWithVariableExpressionFunction()
: QgsExpressionFunction( QStringLiteral( "with_variable" ), QgsExpressionFunction::ParameterList() <<
QgsExpressionFunction::Parameter( QStringLiteral( "name" ) )
@@ -521,6 +521,29 @@ class QgsArrayForeachExpressionFunction : public QgsExpressionFunction

};

/**
* Handles the ``array_filter(array, expression)`` expression function.
* It temporarily appends a new scope to the expression context.
*
* \ingroup core
* \note Not available in Python bindings
* \since QGIS 3.4
*/
class QgsArrayFilterExpressionFunction : public QgsExpressionFunction
{
public:
QgsArrayFilterExpressionFunction();

bool isStatic( const QgsExpressionNodeFunction *node, QgsExpression *parent, const QgsExpressionContext *context ) const override;

QVariant run( QgsExpressionNode::NodeList *args, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction *node ) override;

QVariant func( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction *node ) override;

bool prepare( const QgsExpressionNodeFunction *node, QgsExpression *parent, const QgsExpressionContext *context ) const override;

};

/**
* Handles the ``with_variable(name, value, node)`` expression function.
* It temporarily appends a new scope to the expression context for all nested
@@ -2691,6 +2691,9 @@ class TestQgsExpression: public QObject
foreachExpected << QStringLiteral( "ABC" ) << QStringLiteral( "HELLO" );
QCOMPARE( QgsExpression( "array_foreach(array:=array('abc', 'hello'), expression:=upper(@element))" ).evaluate( &context ), QVariant( foreachExpected ) );

QVariantList filterExpected = QVariantList() << QStringLiteral( "A: a" ) << QStringLiteral( "A: d" );
QCOMPARE( QgsExpression( "array_filter(array:=array('A: a', 'B: b', 'C: c', 'A: d'), expression:=substr(@element, 1, 2) = 'A:')" ).evaluate( &context ), QVariant( filterExpected ) );

QCOMPARE( QgsExpression( "array_intersect(array('1', '2', '3', '4'), array('4', '0', '2', '5'))" ).evaluate( &context ), QVariant( true ) );
QCOMPARE( QgsExpression( "array_intersect(array('1', '2', '3', '4'), array('0', '5'))" ).evaluate( &context ), QVariant( false ) );

@@ -2774,6 +2777,9 @@ class TestQgsExpression: public QObject
foreachExpected << 10 << 20 << 40;
QCOMPARE( QgsExpression( "array_foreach(array(1, 2, 4), @element * 10)" ).evaluate( &context ), QVariant( foreachExpected ) );

QVariantList filterExpected = QVariantList() << 1 << 2;
QCOMPARE( QgsExpression( "array_filter(array(1, 2, 4), @element < 3)" ).evaluate( &context ), QVariant( filterExpected ) );

QgsExpression badArray( QStringLiteral( "array_get('not an array', 0)" ) );
QCOMPARE( badArray.evaluate( &context ), QVariant() );
QVERIFY( badArray.hasEvalError() );

0 comments on commit 51ec333

Please sign in to comment.
You can’t perform that action at this time.