Skip to content
Permalink
Browse files
Merge pull request #46502 from elpaso/formatted_attributes
New represent_attributes function
  • Loading branch information
elpaso committed Jan 4, 2022
2 parents e8a9711 + 7c356eb commit 89d54caaa4e4bc75f929ef4b59af93d064d6e3fa
Showing with 183 additions and 0 deletions.
  1. +26 −0 resources/function_help/json/represent_attributes
  2. +86 −0 src/core/expression/qgsexpressionfunction.cpp
  3. +71 −0 tests/src/core/testqgsexpression.cpp
@@ -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." } ]
}
]
}
@@ -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;
@@ -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" ),
@@ -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;

0 comments on commit 89d54ca

Please sign in to comment.