Skip to content

Commit

Permalink
Merge pull request #5269 from m-kuhn/represent_value
Browse files Browse the repository at this point in the history
[expression] represent_value also determines implicitly provided column name
  • Loading branch information
m-kuhn committed Sep 29, 2017
2 parents f981795 + ca5a0bb commit cb8ae89
Show file tree
Hide file tree
Showing 16 changed files with 364 additions and 292 deletions.
2 changes: 2 additions & 0 deletions doc/api_break.dox
Expand Up @@ -1154,6 +1154,8 @@ QgsExpression::Function {#qgis_api_break_3_0_QgsExpression_Function}
- `QgsExpression::Function::usesgeometry()` has been renamed to `QgsExpression::Function::usesGeometry( const NodeFunction* node )`
- `QStringList QgsExpression::Function::referencedColumns()` has been changed to `QSet<QString> QgsExpression::Function::referencedColumns( const NodeFunction* node )`
- `QgsExpression::Function::helptext()` has been renamed to `helpText()`
- `QgsExpression::Function::func` has an additional parameter `node` that
provides access to the original node.

QgsExpressionItem {#qgis_api_break_3_0_QgsExpressionItem}
-----------------
Expand Down
2 changes: 1 addition & 1 deletion python/core/__init__.py
Expand Up @@ -84,7 +84,7 @@ def __init__(self, func, name, args, group, helptext='', usesGeometry=True,
self.uses_geometry = usesGeometry
self.referenced_columns = referencedColumns

def func(self, values, context, parent):
def func(self, values, context, parent, node):
feature = None
if context:
feature = context.feature()
Expand Down
5 changes: 3 additions & 2 deletions python/core/expression/qgsexpressionfunction.sip
Expand Up @@ -239,17 +239,18 @@ The help text for the function.
:rtype: str
%End

virtual QVariant func( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent ) = 0;
virtual QVariant func( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction *node ) = 0;
%Docstring
Returns result of evaluating the function.
\param values list of values passed to the function
\param context context expression is being evaluated against
\param parent parent expression
\param node expression node
:return: result of function
:rtype: QVariant
%End

virtual QVariant run( QgsExpressionNode::NodeList *args, const QgsExpressionContext *context, QgsExpression *parent );
virtual QVariant run( QgsExpressionNode::NodeList *args, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction *node );
%Docstring
:rtype: QVariant
%End
Expand Down
2 changes: 1 addition & 1 deletion python/core/qgsexpressioncontext.sip
Expand Up @@ -53,7 +53,7 @@ class QgsScopedExpressionFunction : QgsExpressionFunction
.. versionadded:: 3.0
%End

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

virtual QgsScopedExpressionFunction *clone() const = 0 /Factory/;
%Docstring
Expand Down
7 changes: 4 additions & 3 deletions resources/function_help/json/represent_value
Expand Up @@ -3,10 +3,11 @@
"type": "function",
"description": "Returns the configured representation value for a field value. It depends on the configured widget type. Often, this is useful for 'Value Map' widgets.",
"arguments": [
{"arg":"fieldName", "description": "The field name for which the widget configuration should be loaded."},
{"arg":"value", "description": "The value which should be resolved. Most likely a field." }
{"arg":"value", "description": "The value which should be resolved. Most likely a field." },
{"arg":"fieldName", "description": "The field name for which the widget configuration should be loaded. (Optional)"}
],
"examples": [
{ "expression":"represent_value('field_with_value_map', \"field_with_value_map\")", "returns":"Description for value"}
{ "expression":"represent_value(\"field_with_value_map\")", "returns":"Description for value"},
{ "expression":"represent_value('static value', 'field_name')", "returns":"Description for static value"}
]
}
10 changes: 8 additions & 2 deletions src/core/expression/qgsexpression.cpp
Expand Up @@ -240,9 +240,15 @@ bool QgsExpression::isValid() const
return d->mRootNode;
}

bool QgsExpression::hasParserError() const { return !d->mParserErrorString.isNull(); }
bool QgsExpression::hasParserError() const
{
return !d->mParserErrorString.isNull();
}

QString QgsExpression::parserErrorString() const { return d->mParserErrorString; }
QString QgsExpression::parserErrorString() const
{
return d->mParserErrorString;
}

QSet<QString> QgsExpression::referencedColumns() const
{
Expand Down
554 changes: 298 additions & 256 deletions src/core/expression/qgsexpressionfunction.cpp

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions src/core/expression/qgsexpressionfunction.h
Expand Up @@ -40,7 +40,7 @@ class CORE_EXPORT QgsExpressionFunction

/** Function definition for evaluation against an expression context, using a list of values as parameters to the function.
*/
typedef QVariant( *FcnEval )( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent ) SIP_SKIP;
typedef QVariant( *FcnEval )( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction *node ) SIP_SKIP;

/** \ingroup core
* Represents a single parameter passed to a function.
Expand Down Expand Up @@ -271,11 +271,12 @@ class CORE_EXPORT QgsExpressionFunction
* \param values list of values passed to the function
* \param context context expression is being evaluated against
* \param parent parent expression
* \param node expression node
* \returns result of function
*/
virtual QVariant func( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent ) = 0;
virtual QVariant func( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction *node ) = 0;

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

bool operator==( const QgsExpressionFunction &other ) const;

Expand Down Expand Up @@ -402,9 +403,9 @@ class QgsStaticExpressionFunction : public QgsExpressionFunction
* \param parent parent expression
* \returns result of function
*/
virtual QVariant func( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent ) override
virtual QVariant func( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction *node ) override
{
return mFnc ? mFnc( values, context, parent ) : QVariant();
return mFnc ? mFnc( values, context, parent, node ) : QVariant();
}

virtual QStringList aliases() const override;
Expand Down Expand Up @@ -471,9 +472,9 @@ class QgsWithVariableExpressionFunction : public QgsExpressionFunction

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

QVariant run( QgsExpressionNode::NodeList *args, const QgsExpressionContext *context, QgsExpression *parent ) 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 ) 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;

Expand Down
2 changes: 1 addition & 1 deletion src/core/expression/qgsexpressionnodeimpl.cpp
Expand Up @@ -846,7 +846,7 @@ QVariant QgsExpressionNodeFunction::evalNode( QgsExpression *parent, const QgsEx
QString name = QgsExpression::QgsExpression::Functions()[mFnIndex]->name();
QgsExpressionFunction *fd = context && context->hasFunction( name ) ? context->function( name ) : QgsExpression::QgsExpression::Functions()[mFnIndex];

QVariant res = fd->run( mArgs, context, parent );
QVariant res = fd->run( mArgs, context, parent, this );
ENSURE_NO_EVAL_ERROR;

// everything went fine
Expand Down
1 change: 0 additions & 1 deletion src/core/expression/qgsexpressionnodeimpl.h
Expand Up @@ -241,7 +241,6 @@ class CORE_EXPORT QgsExpressionNodeFunction : public QgsExpressionNode
private:
int mFnIndex;
NodeList *mArgs = nullptr;

};

/** \ingroup core
Expand Down
8 changes: 4 additions & 4 deletions src/core/qgsexpressioncontext.cpp
Expand Up @@ -626,7 +626,7 @@ class GetNamedProjectColor : public QgsScopedExpressionFunction
}
}

QVariant func( const QVariantList &values, const QgsExpressionContext *, QgsExpression * ) override
QVariant func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * ) override
{
QString colorName = values.at( 0 ).toString().toLower();
if ( mColors.contains( colorName ) )
Expand Down Expand Up @@ -657,7 +657,7 @@ class GetComposerItemVariables : public QgsScopedExpressionFunction
, mComposition( c )
{}

QVariant func( const QVariantList &values, const QgsExpressionContext *, QgsExpression * ) override
QVariant func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * ) override
{
if ( !mComposition )
return QVariant();
Expand Down Expand Up @@ -692,7 +692,7 @@ class GetLayerVisibility : public QgsScopedExpressionFunction
, mLayers( layers )
{}

QVariant func( const QVariantList &values, const QgsExpressionContext *, QgsExpression * ) override
QVariant func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * ) override
{
if ( mLayers.isEmpty() )
{
Expand Down Expand Up @@ -729,7 +729,7 @@ class GetProcessingParameterValue : public QgsScopedExpressionFunction
, mParams( params )
{}

QVariant func( const QVariantList &values, const QgsExpressionContext *, QgsExpression * ) override
QVariant func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * ) override
{
return mParams.value( values.at( 0 ).toString() );
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/qgsexpressioncontext.h
Expand Up @@ -91,7 +91,7 @@ class CORE_EXPORT QgsScopedExpressionFunction : public QgsExpressionFunction
, mReferencedColumns( referencedColumns )
{}

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

/** Returns a clone of the function.
*/
Expand Down
4 changes: 2 additions & 2 deletions src/providers/virtual/qgsvirtuallayersqlitemodule.cpp
Expand Up @@ -777,8 +777,8 @@ void qgisFunctionWrapper( sqlite3_context *ctxt, int nArgs, sqlite3_value **args
};
}

QgsExpression parentExpr( QLatin1String( "" ) );
QVariant ret = foo->func( variants, &qgisFunctionExpressionContext, &parentExpr );
QgsExpression parentExpr = QgsExpression( QString() );
QVariant ret = foo->func( variants, &qgisFunctionExpressionContext, &parentExpr, nullptr );
if ( parentExpr.hasEvalError() )
{
QByteArray ba = parentExpr.evalErrorString().toUtf8();
Expand Down
24 changes: 22 additions & 2 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -386,7 +386,7 @@ class TestQgsExpression: public QObject

// Usage on a value map
QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mPointsLayer ) );
QgsExpression expression( "represent_value('Pilots', \"Pilots\")" );
QgsExpression expression( "represent_value(\"Pilots\", 'Pilots')" );
if ( expression.hasParserError() )
qDebug() << expression.parserErrorString();
Q_ASSERT( !expression.hasParserError() );
Expand All @@ -401,7 +401,7 @@ class TestQgsExpression: public QObject
QCOMPARE( expression.evaluate( &context ).toString(), QStringLiteral( "one" ) );

// Usage on a simple string
QgsExpression expression2( "represent_value('Class', \"Class\")" );
QgsExpression expression2( "represent_value(\"Class\", 'Class')" );
if ( expression2.hasParserError() )
qDebug() << expression2.parserErrorString();
Q_ASSERT( !expression2.hasParserError() );
Expand All @@ -412,6 +412,26 @@ class TestQgsExpression: public QObject
mPointsLayer->getFeatures( QgsFeatureRequest().setFilterExpression( "Class = 'Jet'" ) ).nextFeature( feature );
context.setFeature( feature );
QCOMPARE( expression2.evaluate( &context ).toString(), QStringLiteral( "Jet" ) );

// Test with implicit field name discovery
QgsExpression expression3( "represent_value(\"Pilots\")" );
if ( expression3.hasParserError() )
qDebug() << expression.parserErrorString();
Q_ASSERT( !expression3.hasParserError() );
if ( expression3.hasEvalError() )
qDebug() << expression3.evalErrorString();
Q_ASSERT( !expression3.hasEvalError() );
expression3.prepare( &context );
mPointsLayer->getFeatures( QgsFeatureRequest().setFilterExpression( "Pilots = 1" ) ).nextFeature( feature );
context.setFeature( feature );
QCOMPARE( expression.evaluate( &context ).toString(), QStringLiteral( "one" ) );


QgsExpression expression4( "represent_value('Class')" );
if ( expression4.hasParserError() )
qDebug() << expression4.parserErrorString();
Q_ASSERT( !expression4.hasParserError() );
Q_ASSERT( expression4.hasEvalError() );
}

void evaluation_data()
Expand Down
16 changes: 8 additions & 8 deletions tests/src/core/testqgsexpressioncontext.cpp
Expand Up @@ -62,7 +62,7 @@ class TestQgsExpressionContext : public QObject
GetTestValueFunction()
: QgsScopedExpressionFunction( QStringLiteral( "get_test_value" ), 1, QStringLiteral( "test" ) ) {}

virtual QVariant func( const QVariantList &, const QgsExpressionContext *, QgsExpression * ) override
virtual QVariant func( const QVariantList &, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * ) override
{
return 42;
}
Expand All @@ -80,7 +80,7 @@ class TestQgsExpressionContext : public QObject
GetTestValueFunction2()
: QgsScopedExpressionFunction( QStringLiteral( "get_test_value" ), 1, QStringLiteral( "test" ) ) {}

virtual QVariant func( const QVariantList &, const QgsExpressionContext *, QgsExpression * ) override
virtual QVariant func( const QVariantList &, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * ) override
{
return 43;
}
Expand All @@ -99,7 +99,7 @@ class TestQgsExpressionContext : public QObject
, mVal( v )
{}

virtual QVariant func( const QVariantList &, const QgsExpressionContext *, QgsExpression * ) override
virtual QVariant func( const QVariantList &, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * ) override
{
if ( !mVal )
return QVariant();
Expand Down Expand Up @@ -228,7 +228,7 @@ void TestQgsExpressionContext::contextScopeFunctions()
QVERIFY( scope.hasFunction( "get_test_value" ) );
QVERIFY( scope.function( "get_test_value" ) );
QgsExpressionContext temp;
QCOMPARE( scope.function( "get_test_value" )->func( QVariantList(), &temp, 0 ).toInt(), 42 );
QCOMPARE( scope.function( "get_test_value" )->func( QVariantList(), &temp, 0, nullptr ).toInt(), 42 );

//test functionNames
scope.addFunction( QStringLiteral( "get_test_value2" ), new GetTestValueFunction() );
Expand Down Expand Up @@ -371,27 +371,27 @@ void TestQgsExpressionContext::contextStackFunctions()
QVERIFY( context.hasFunction( "get_test_value" ) );
QVERIFY( context.function( "get_test_value" ) );
QgsExpressionContext temp;
QCOMPARE( context.function( "get_test_value" )->func( QVariantList(), &temp, 0 ).toInt(), 42 );
QCOMPARE( context.function( "get_test_value" )->func( QVariantList(), &temp, 0, nullptr ).toInt(), 42 );

//add a second scope, should override the first
context << new QgsExpressionContextScope();
//test without setting function first...
QVERIFY( context.hasFunction( "get_test_value" ) );
QVERIFY( context.function( "get_test_value" ) );
QCOMPARE( context.function( "get_test_value" )->func( QVariantList(), &temp, 0 ).toInt(), 42 );
QCOMPARE( context.function( "get_test_value" )->func( QVariantList(), &temp, 0, nullptr ).toInt(), 42 );

//then set the variable so it overrides
QgsExpressionContextScope *scope2 = context.scope( 1 );
scope2->addFunction( QStringLiteral( "get_test_value" ), new GetTestValueFunction2() );
QVERIFY( context.hasFunction( "get_test_value" ) );
QVERIFY( context.function( "get_test_value" ) );
QCOMPARE( context.function( "get_test_value" )->func( QVariantList(), &temp, 0 ).toInt(), 43 );
QCOMPARE( context.function( "get_test_value" )->func( QVariantList(), &temp, 0, nullptr ).toInt(), 43 );

//make sure stack falls back to earlier contexts
scope2->addFunction( QStringLiteral( "get_test_value2" ), new GetTestValueFunction() );
QVERIFY( context.hasFunction( "get_test_value2" ) );
QVERIFY( context.function( "get_test_value2" ) );
QCOMPARE( context.function( "get_test_value2" )->func( QVariantList(), &temp, 0 ).toInt(), 42 );
QCOMPARE( context.function( "get_test_value2" )->func( QVariantList(), &temp, 0, nullptr ).toInt(), 42 );

//test functionNames
QStringList names = context.functionNames();
Expand Down
2 changes: 1 addition & 1 deletion tests/src/python/test_qgsexpression.py
Expand Up @@ -97,7 +97,7 @@ def testAutoArgsAreExpanded(self):
self.assertEqual(args, 3)
values = [1, 2, 3]
exp = QgsExpression("")
result = function.func(values, None, exp)
result = function.func(values, None, exp, None)
# Make sure there is no eval error
self.assertEqual(exp.evalErrorString(), "")
self.assertEqual(result, (1, 2, 3))
Expand Down

0 comments on commit cb8ae89

Please sign in to comment.