Skip to content

Commit

Permalink
Merge pull request #46502 from elpaso/formatted_attributes
Browse files Browse the repository at this point in the history
New represent_attributes function
  • Loading branch information
elpaso committed Jan 4, 2022
2 parents e8a9711 + 7c356eb commit 89d54ca
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 0 deletions.
26 changes: 26 additions & 0 deletions resources/function_help/json/represent_attributes
@@ -0,0 +1,26 @@
{
"name": "represent_attributes",
"type": "function",
"groups": ["Record and Attributes"],
"description": "Returns a map with the attribute names as keys and the configured representation values as values. The representation value for the attributes depends on the configured widget type for each attribute. Can be used with zero, one or more arguments, see below for details.",
"variants": [
{
"variant": "No parameters",
"variant_description": "If called with no parameters, the function will return the representation of the attributes of the current feature in the current layer.",
"arguments": [],
"examples": [ { "expression": "represent_attributes()", "returns" : "The representation of the attributes for the current feature." } ]
},
{
"variant": "One 'feature' parameter",
"variant_description": "If called with a 'feature' parameter only, the function will return the representation of the attributes of the specified feature from the current layer.",
"arguments": [ { "arg": "feature", "description": "The feature which should be evaluated." } ],
"examples": [ { "expression": "represent_attributes(@atlas_feature)", "returns" : "The representation of the attributes for the specified feature from the current layer." } ]
},
{
"variant": "Layer and feature parameters",
"variant_description": "If called with a 'layer' and a 'feature' parameter, the function will return the representation of the attributes of the specified feature from the specified layer.",
"arguments": [ { "arg": "layer", "description": "The layer (or its ID or name)." }, { "arg": "feature", "description": "The feature which should be evaluated." } ],
"examples": [ { "expression": "represent_attributes('atlas_layer', @atlas_feature)", "returns" : "The representation of the attributes for the specified feature from the specified layer." } ]
}
]
}
86 changes: 86 additions & 0 deletions src/core/expression/qgsexpressionfunction.cpp
Expand Up @@ -1729,6 +1729,88 @@ static QVariant fcnAttributes( const QVariantList &values, const QgsExpressionCo
return result;
}

static QVariant fcnRepresentAttributes( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QgsVectorLayer *layer = nullptr;
QgsFeature feature;

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
{
parent->setEvalErrorString( QObject::tr( "Function `represent_attributes` requires no more than two parameters. %1 given." ).arg( values.length() ) );
return QVariant();
}

if ( !layer )
{
parent->setEvalErrorString( QObject::tr( "Cannot use represent attributes function: layer could not be resolved." ) );
return QVariant();
}

if ( !feature.isValid() )
{
parent->setEvalErrorString( QObject::tr( "Cannot use represent attributes function: feature could not be resolved." ) );
return QVariant();
}

const QgsFields fields = feature.fields();
QVariantMap result;
for ( int fieldIndex = 0; fieldIndex < fields.count(); ++fieldIndex )
{
const QString fieldName { fields.at( fieldIndex ).name() };
const QVariant attributeVal = feature.attribute( fieldIndex );
const QString cacheValueKey = QStringLiteral( "repvalfcnval:%1:%2:%3" ).arg( layer->id(), fieldName, attributeVal.toString() );
if ( context && context->hasCachedValue( cacheValueKey ) )
{
result.insert( fieldName, context->cachedValue( cacheValueKey ) );
}
else
{
const QgsEditorWidgetSetup setup = layer->editorWidgetSetup( fieldIndex );
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
QVariant cache;
if ( context )
{
const QString cacheKey = QStringLiteral( "repvalfcn:%1:%2" ).arg( layer->id(), fieldName );

if ( !context->hasCachedValue( cacheKey ) )
{
cache = fieldFormatter->createCache( layer, fieldIndex, setup.config() );
context->setCachedValue( cacheKey, cache );
}
else
{
cache = context->cachedValue( cacheKey );
}
}
QString value( fieldFormatter->representValue( layer, fieldIndex, setup.config(), cache, attributeVal ) );

result.insert( fields.at( fieldIndex ).name(), value );

if ( context )
{
context->setCachedValue( cacheValueKey, value );
}

}
}
return result;
}

static QVariant fcnCoreFeatureMaptipDisplay( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const bool isMaptip )
{
QgsVectorLayer *layer = nullptr;
Expand Down Expand Up @@ -7809,6 +7891,10 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
fcnAttributes, QStringLiteral( "Record and Attributes" ), QString(), false, QSet<QString>() << QgsFeatureRequest::ALL_ATTRIBUTES );
attributesFunc->setIsStatic( false );
functions << attributesFunc;
QgsStaticExpressionFunction *representAttributesFunc = new QgsStaticExpressionFunction( QStringLiteral( "represent_attributes" ), -1,
fcnRepresentAttributes, QStringLiteral( "Record and Attributes" ), QString(), false, QSet<QString>() << QgsFeatureRequest::ALL_ATTRIBUTES );
representAttributesFunc->setIsStatic( false );
functions << representAttributesFunc;

QgsStaticExpressionFunction *maptipFunc = new QgsStaticExpressionFunction(
QStringLiteral( "maptip" ),
Expand Down
71 changes: 71 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -520,6 +520,77 @@ class TestQgsExpression: public QObject
QCOMPARE( exp.dump(), dump );
}

void represent_attributes()
{

QgsVectorLayer layer { QStringLiteral( "Point?field=col1:integer&field=col2:string" ), QStringLiteral( "test_represent_attributes" ), QStringLiteral( "memory" ) };
QVERIFY( layer.isValid() );
QgsFeature f1( layer.dataProvider()->fields(), 1 );
f1.setAttribute( QStringLiteral( "col1" ), 1 );
f1.setAttribute( QStringLiteral( "col2" ), "test1" );
QgsFeature f2( layer.dataProvider()->fields(), 2 );
f2.setAttribute( QStringLiteral( "col1" ), 2 );
f2.setAttribute( QStringLiteral( "col2" ), "test2" );
layer.dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 );

QVariantMap config;
QVariantMap map;
map.insert( QStringLiteral( "one" ), QStringLiteral( "1" ) );
map.insert( QStringLiteral( "two" ), QStringLiteral( "2" ) );

config.insert( QStringLiteral( "map" ), map );
layer.setEditorWidgetSetup( 0, QgsEditorWidgetSetup( QStringLiteral( "ValueMap" ), config ) );

QgsExpressionContext context( { QgsExpressionContextUtils::layerScope( &layer ) } );
context.setFeature( f2 );
QgsExpression expression( "represent_attributes()" );

if ( expression.hasParserError() )
qDebug() << expression.parserErrorString();
QVERIFY( !expression.hasParserError() );

expression.prepare( &context );

QVariantMap result { expression.evaluate( &context ).toMap() };

QCOMPARE( result.value( QStringLiteral( "col1" ) ).toString(), QStringLiteral( "two" ) );
QCOMPARE( result.value( QStringLiteral( "col2" ) ).toString(), QStringLiteral( "test2" ) );

QgsExpressionContext context2( { QgsExpressionContextUtils::layerScope( &layer ) } );
context2.setFeature( f2 );
expression = QgsExpression( "represent_attributes($currentfeature)" );

result = expression.evaluate( &context2 ).toMap();

QVERIFY( !expression.hasEvalError() );

QCOMPARE( result.value( QStringLiteral( "col1" ) ).toString(), QStringLiteral( "two" ) );
QCOMPARE( result.value( QStringLiteral( "col2" ) ).toString(), QStringLiteral( "test2" ) );

QgsProject::instance()->addMapLayer( &layer, false, false );
QgsExpressionContext context3;
context3.setFeature( f2 );
expression = QgsExpression( "represent_attributes('test_represent_attributes', $currentfeature)" );

result = expression.evaluate( &context3 ).toMap();

QVERIFY( !expression.hasEvalError() );

QCOMPARE( result.value( QStringLiteral( "col1" ) ).toString(), QStringLiteral( "two" ) );
QCOMPARE( result.value( QStringLiteral( "col2" ) ).toString(), QStringLiteral( "test2" ) );

// Test the cached value
QCOMPARE( context3.cachedValue( QStringLiteral( "repvalfcnval:%1:%2:%3" ).arg( layer.id(), QStringLiteral( "col1" ), QStringLiteral( "2" ) ) ).toString(), QStringLiteral( "two" ) );

// Test errors
QgsProject::instance()->removeMapLayer( layer.id() );
expression = QgsExpression( "represent_attributes('test_represent_attributes', $currentfeature)" );
QgsExpressionContext context4;
result = expression.evaluate( &context4 ).toMap();
QVERIFY( expression.hasEvalError() );

};

void represent_value()
{
QVariantMap config;
Expand Down

0 comments on commit 89d54ca

Please sign in to comment.