Skip to content

Commit

Permalink
Add QgsExpression utility function to match an expression or field na…
Browse files Browse the repository at this point in the history
…me string to a layer's field index
  • Loading branch information
nyalldawson committed Aug 11, 2021
1 parent 5a5b637 commit 4d5014d
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 0 deletions.
17 changes: 17 additions & 0 deletions python/core/auto_generated/expression/qgsexpression.sip.in
Expand Up @@ -286,7 +286,24 @@ Sets evaluation error (used internally by evaluation functions)
%Docstring
Checks whether an expression consists only of a single field reference

.. seealso:: :py:func:`expressionToLayerFieldIndex`

.. versionadded:: 2.9
%End

static int expressionToLayerFieldIndex( const QString &expression, const QgsVectorLayer *layer );
%Docstring
Attempts to resolve an expression to a field index from the given ``layer``.

Given a string which may either directly match a field name from a layer, OR may
be an expression which consists only of a single field reference for that layer, this
method will return the corresponding field index.

:return: field index if found, or -1 if ``expression`` does not represent a field from the layer.

.. seealso:: :py:func:`isField`

.. versionadded:: 3.22
%End

static bool checkExpression( const QString &text, const QgsExpressionContext *context, QString &errorMessage /Out/ );
Expand Down
20 changes: 20 additions & 0 deletions src/core/expression/qgsexpression.cpp
Expand Up @@ -1338,6 +1338,26 @@ bool QgsExpression::isField() const
return d->mRootNode && d->mRootNode->nodeType() == QgsExpressionNode::ntColumnRef;
}

int QgsExpression::expressionToLayerFieldIndex( const QString &expression, const QgsVectorLayer *layer )
{
if ( !layer )
return -1;

// easy check first -- lookup field directly.
int attrIndex = layer->fields().lookupField( expression.trimmed() );
if ( attrIndex >= 0 )
return attrIndex;

// may still be a simple field expression, just one which is enclosed in "" or similar
QgsExpression candidate( expression );
if ( candidate.isField() )
{
const QString fieldName = qgis::down_cast<const QgsExpressionNodeColumnRef *>( candidate.rootNode() )->name();
return layer->fields().lookupField( fieldName );
}
return -1;
}

QList<const QgsExpressionNode *> QgsExpression::nodes() const
{
if ( !d->mRootNode )
Expand Down
16 changes: 16 additions & 0 deletions src/core/expression/qgsexpression.h
Expand Up @@ -349,10 +349,26 @@ class CORE_EXPORT QgsExpression

/**
* Checks whether an expression consists only of a single field reference
*
* \see expressionToLayerFieldIndex()
* \since QGIS 2.9
*/
bool isField() const;

/**
* Attempts to resolve an expression to a field index from the given \a layer.
*
* Given a string which may either directly match a field name from a layer, OR may
* be an expression which consists only of a single field reference for that layer, this
* method will return the corresponding field index.
*
* \returns field index if found, or -1 if \a expression does not represent a field from the layer.
*
* \see isField()
* \since QGIS 3.22
*/
static int expressionToLayerFieldIndex( const QString &expression, const QgsVectorLayer *layer );

/**
* Tests whether a string is a valid expression.
* \param text string to test
Expand Down
26 changes: 26 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -3806,6 +3806,32 @@ class TestQgsExpression: public QObject
QCOMPARE( QgsExpression( "foo + bar" ).isField(), false );
}

void test_expressionToLayerFieldIndex()
{
std::unique_ptr layer = std::make_unique< QgsVectorLayer >( QStringLiteral( "Point" ), QStringLiteral( "test" ), QStringLiteral( "memory" ) );
layer->dataProvider()->addAttributes( { QgsField( QStringLiteral( "field1" ), QVariant::String ),
QgsField( QStringLiteral( "another FIELD" ), QVariant::String ) } );
layer->updateFields();

QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "", layer.get() ), -1 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "42", layer.get() ), -1 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "foo", layer.get() ), -1 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "\"foo bar\"", layer.get() ), -1 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "sqrt(foo)", layer.get() ), -1 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "foo + bar", layer.get() ), -1 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "field1", layer.get() ), 0 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "FIELD1", layer.get() ), 0 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "\"field1\"", layer.get() ), 0 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "\"FIELD1\"", layer.get() ), 0 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( " ( \"field1\" ) ", layer.get() ), 0 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "another FIELD", layer.get() ), 1 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "ANOTHER field", layer.get() ), 1 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( " ANOTHER field ", layer.get() ), 1 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "\"another field\"", layer.get() ), 1 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( "\"ANOTHER FIELD\"", layer.get() ), 1 );
QCOMPARE( QgsExpression::expressionToLayerFieldIndex( " ( \"ANOTHER FIELD\" ) ", layer.get() ), 1 );
}

void test_implicitSharing()
{
QgsExpression *exp = new QgsExpression( QStringLiteral( "Pilots > 2" ) );
Expand Down

0 comments on commit 4d5014d

Please sign in to comment.