add maptip, expression display and eval_template expressions

This commit is contained in:
Etienne Trimaille 2019-11-04 07:48:04 +01:00 committed by Nyall Dawson
parent 6a0ae060bc
commit e7556c4ea6
5 changed files with 278 additions and 0 deletions

View File

@ -0,0 +1,39 @@
{
"name": "display_expression",
"type": "function",
"description": "Returns the display expression for a given feature in a layer. If called with no parameters, it evaluates the current feature. The expression is evaluated by default.",
"arguments": [
{
"arg": "feature",
"optional": true,
"default": "current feature",
"description": "The feature which should be evaluated."
},
{
"arg": "layer",
"optional": true,
"default": "current layer",
"description": "The layer (or its id or name)."
},
{
"arg": "evaluate",
"description": "If the expression must be evaluated. If false, the expression will be returned as a string literal only (which could potentially be later evaluated using the 'eval' function).",
"optional": true,
"default": "true"
}
],
"examples": [
{
"expression": "display_expression()",
"returns": "The display expression of the current feature."
},
{
"expression": "display_expression($currentfeature)",
"returns": "The display expression for a given feature."
},
{
"expression": "display_expression('a_layer_id', $currentfeature, 'False')",
"returns": "The display expression of the current feature not evaluated."
}
]
}

View File

@ -0,0 +1,13 @@
{
"name": "eval_template",
"type": "function",
"description": "Evaluates a template which is passed in a string. Useful to expand dynamic parameters passed as context variables or fields.",
"arguments": [{
"arg": "template",
"description": "a template string"
}],
"examples": [{
"expression": "eval_template('QGIS [% upper(\\\\'rocks\\\\') %]')",
"returns": "QGIS ROCKS"
}]
}

View File

@ -0,0 +1,39 @@
{
"name": "maptip",
"type": "function",
"description": "Returns the maptip for a given feature in a layer. If called with no parameters, it evaluates the current feature. The maptip is evaluated by default.",
"arguments": [
{
"arg": "feature",
"optional": true,
"default": "current feature",
"description": "The feature which should be evaluated."
},
{
"arg": "layer",
"optional": true,
"default": "current layer",
"description": "The layer (or its id or name)."
},
{
"arg": "evaluate",
"description": "If the expression must be evaluated. If false, the expression will be returned as a string literal only (which could potentially be later evaluated using the 'eval_template' function).",
"optional": true,
"default": "true"
}
],
"examples": [
{
"expression": "maptip()",
"returns": "The maptip of the current feature."
},
{
"expression": "maptip($currentFeature)",
"returns": "The maptip of the current feature."
},
{
"expression": "maptip('a_layer_id', $currentFeature, 'False')",
"returns": "The maptip of the current feature not evaluated."
}
]
}

View File

@ -272,6 +272,12 @@ static QVariant fcnGetVariable( const QVariantList &values, const QgsExpressionC
return context->variable( name );
}
static QVariant fcnEvalTemplate( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QString templateString = QgsExpressionUtils::getStringValue( values.at( 0 ), parent );
return QgsExpression::replaceExpressionText( templateString, context );
}
static QVariant fcnEval( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
if ( !context )
@ -1513,6 +1519,96 @@ static QVariant fcnAttributes( const QVariantList &values, const QgsExpressionCo
return result;
}
static QVariant fcnCoreFeatureMaptipDisplay( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const bool isMaptip )
{
QgsVectorLayer *layer = nullptr;
QgsFeature feature;
bool evaluate = true;
if ( values.isEmpty() )
{
feature = context->feature();
layer = QgsExpressionUtils::getVectorLayer( context->variable( QStringLiteral( "layer" ) ), parent );
}
else if ( values.size() == 1 )
{
layer = QgsExpressionUtils::getVectorLayer( context->variable( QStringLiteral( "layer" ) ), parent );
feature = QgsExpressionUtils::getFeature( values.at( 0 ), parent );
}
else if ( values.size() == 2 )
{
layer = QgsExpressionUtils::getVectorLayer( values.at( 0 ), parent );
feature = QgsExpressionUtils::getFeature( values.at( 1 ), parent );
}
else if ( values.size() == 3 )
{
layer = QgsExpressionUtils::getVectorLayer( values.at( 0 ), parent );
feature = QgsExpressionUtils::getFeature( values.at( 1 ), parent );
evaluate = values.value( 2 ).toBool();
}
else
{
if ( isMaptip )
{
parent->setEvalErrorString( QObject::tr( "Function `maptip` requires no more than three parameters. %1 given." ).arg( values.length() ) );
}
else
{
parent->setEvalErrorString( QObject::tr( "Function `display` requires no more than three parameters. %1 given." ).arg( values.length() ) );
}
return QVariant();
}
if ( !layer )
{
parent->setEvalErrorString( QObject::tr( "The layer is not valid." ) );
return QVariant( );
}
if ( !feature.isValid() )
{
parent->setEvalErrorString( QObject::tr( "The feature is not valid." ) );
return QVariant( );
}
if ( ! evaluate )
{
if ( isMaptip )
{
return layer->mapTipTemplate();
}
else
{
return layer->displayExpression();
}
}
QgsExpressionContext subContext( *context );
subContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
subContext.setFeature( feature );
if ( isMaptip )
{
return QgsExpression::replaceExpressionText( layer->mapTipTemplate(), &subContext );
}
else
{
QgsExpression exp( layer->displayExpression() );
exp.prepare( &subContext );
return exp.evaluate( &subContext ).toString();
}
}
static QVariant fcnFeatureDisplayExpression( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
return fcnCoreFeatureMaptipDisplay( values, context, parent, false );
}
static QVariant fcnFeatureMaptip( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
return fcnCoreFeatureMaptipDisplay( values, context, parent, true );
}
static QVariant fcnIsSelected( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QgsVectorLayer *layer = nullptr;
@ -5903,6 +5999,30 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< new QgsStaticExpressionFunction( QStringLiteral( "attributes" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "feature" ), true ),
fcnAttributes, QStringLiteral( "Record and Attributes" ), QString(), false, QSet<QString>() << QgsFeatureRequest::ALL_ATTRIBUTES );
QgsStaticExpressionFunction *maptipFunc = new QgsStaticExpressionFunction(
QStringLiteral( "maptip" ),
-1,
fcnFeatureMaptip,
QStringLiteral( "Record and Attributes" ),
QString(),
false,
QSet<QString>()
);
maptipFunc->setIsStatic( false );
functions << maptipFunc;
QgsStaticExpressionFunction *displayFunc = new QgsStaticExpressionFunction(
QStringLiteral( "display_expression" ),
-1,
fcnFeatureDisplayExpression,
QStringLiteral( "Record and Attributes" ),
QString(),
false,
QSet<QString>()
);
displayFunc->setIsStatic( false );
functions << displayFunc;
QgsStaticExpressionFunction *isSelectedFunc = new QgsStaticExpressionFunction(
QStringLiteral( "is_selected" ),
-1,
@ -6015,6 +6135,9 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
functions
<< varFunction;
functions << new QgsStaticExpressionFunction( QStringLiteral( "eval_template" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "template" ) ), fcnEvalTemplate, QStringLiteral( "General" ), QString(), true );
QgsStaticExpressionFunction *evalFunc = new QgsStaticExpressionFunction( QStringLiteral( "eval" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "expression" ) ), fcnEval, QStringLiteral( "General" ), QString(), true, QSet<QString>() << QgsFeatureRequest::ALL_ATTRIBUTES );
evalFunc->setIsStaticFunction(
[]( const QgsExpressionNodeFunction * node, QgsExpression * parent, const QgsExpressionContext * context )

View File

@ -94,6 +94,8 @@ class TestQgsExpression: public QObject
mPointsLayer->setAttributionUrl( QStringLiteral( "attribution url" ) );
mPointsLayer->setMinimumScale( 500 );
mPointsLayer->setMaximumScale( 1000 );
mPointsLayer->setMapTipTemplate( QStringLiteral( "Maptip with class = [% \"Class\" %]" ) );
mPointsLayer->setDisplayExpression( QStringLiteral( "'Display expression with class = ' || \"Class\"" ) );
mPointsLayerMetadata = new QgsVectorLayer( pointFileInfo.filePath(),
pointFileInfo.completeBaseName() + "_metadata", QStringLiteral( "ogr" ) );
@ -1411,6 +1413,11 @@ class TestQgsExpression: public QObject
QTest::newRow( "right associativity" ) << "(2^3)^2" << false << QVariant( 64. );
QTest::newRow( "left associativity" ) << "1-(2-1)" << false << QVariant( 0 );
// eval_template tests
QTest::newRow( "eval_template" ) << QStringLiteral( "eval_template(\'this is a [% \\'template\\' || \\'!\\' %]\')" ) << false << QVariant( "this is a template!" );
QTest::newRow( "eval_template string" ) << QStringLiteral( "eval_template('string')" ) << false << QVariant( "string" );
QTest::newRow( "eval_template expression" ) << QStringLiteral( "eval_template('a' || ' string')" ) << false << QVariant( "a string" );
// layer_property tests
QTest::newRow( "layer_property no layer" ) << "layer_property('','title')" << false << QVariant();
QTest::newRow( "layer_property bad layer" ) << "layer_property('bad','title')" << false << QVariant();
@ -2015,6 +2022,63 @@ class TestQgsExpression: public QObject
QTest::newRow( "group by with null value" ) << "sum(\"col1\", \"col4\")" << false << QVariant( 8 );
}
void maptip_display_data()
{
QTest::addColumn<QString>( "string" );
QTest::addColumn<QgsFeature>( "feature" );
QTest::addColumn<QgsVectorLayer *>( "layer" );
QTest::addColumn<bool>( "evalError" );
QTest::addColumn<QVariant>( "result" );
QgsFeature firstFeature = mPointsLayer->getFeature( 1 );
QgsVectorLayer *noLayer = nullptr;
QTest::newRow( "display not evaluated" ) << QStringLiteral( "display_expression(@layer_id, $currentfeature, False)" ) << firstFeature << mPointsLayer << false << QVariant( "'Display expression with class = ' || \"Class\"" );
QTest::newRow( "display wrong layer" ) << QStringLiteral( "display_expression()" ) << firstFeature << noLayer << true << QVariant();
QTest::newRow( "display wrong feature" ) << QStringLiteral( "display_expression()" ) << QgsFeature() << mPointsLayer << true << QVariant();
QTest::newRow( "maptip wrong feature" ) << QStringLiteral( "maptip()" ) << QgsFeature() << mPointsLayer << true << QVariant();
QTest::newRow( "maptip wrong layer" ) << QStringLiteral( "maptip()" ) << firstFeature << noLayer << true << QVariant();
QTest::newRow( "maptip not evaluated" ) << QStringLiteral( "maptip(@layer_id, $currentfeature, False)" ) << firstFeature << mPointsLayer << false << QVariant( "Maptip with class = [% \"Class\" %]" );
QTest::newRow( "maptip with 2 params" ) << QStringLiteral( "maptip(@layer_id, $currentfeature)" ) << firstFeature << mPointsLayer << false << QVariant( "Maptip with class = Biplane" );
QTest::newRow( "maptip with 1 param" ) << QStringLiteral( "maptip($currentfeature)" ) << firstFeature << mPointsLayer << false << QVariant( "Maptip with class = Biplane" );
QTest::newRow( "maptip with 0 param" ) << QStringLiteral( "maptip()" ) << firstFeature << mPointsLayer << false << QVariant( "Maptip with class = Biplane" );
QTest::newRow( "display with 2 params" ) << QStringLiteral( "display_expression(@layer_id, $currentfeature)" ) << firstFeature << mPointsLayer << false << QVariant( "Display expression with class = Biplane" );
QTest::newRow( "display with 1 param" ) << QStringLiteral( "display_expression($currentfeature)" ) << firstFeature << mPointsLayer << false << QVariant( "Display expression with class = Biplane" );
QTest::newRow( "display with 0 param" ) << QStringLiteral( "display_expression()" ) << firstFeature << mPointsLayer << false << QVariant( "Display expression with class = Biplane" );
}
void maptip_display()
{
QFETCH( QString, string );
QFETCH( QgsFeature, feature );
QFETCH( QgsVectorLayer *, layer );
QFETCH( bool, evalError );
QFETCH( QVariant, result );
QgsExpressionContext context;
context.appendScope( QgsExpressionContextUtils::globalScope() );
context.appendScope( QgsExpressionContextUtils::projectScope( QgsProject::instance() ) );
if ( layer )
{
//layer->setDisplayExpression( QStringLiteral( "Display expression with class = ' || Class" ) );
context.appendScope( QgsExpressionContextUtils::layerScope( layer ) );
}
context.setFeature( feature );
QgsExpression exp( string );
exp.prepare( &context );
if ( exp.hasParserError() )
qDebug() << exp.parserErrorString();
QCOMPARE( exp.hasParserError(), false );
QVariant res = exp.evaluate( &context );
QCOMPARE( exp.hasEvalError(), evalError );
QCOMPARE( res.toString(), result.toString() );
}
void selection()
{
QFETCH( QgsFeatureIds, selectedFeatures );