Skip to content

Commit 06d5f92

Browse files
committedDec 18, 2018
More square brackets
1 parent 3490217 commit 06d5f92

File tree

12 files changed

+299
-13
lines changed

12 files changed

+299
-13
lines changed
 

‎python/core/auto_generated/expression/qgsexpressionnode.sip.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ Abstract base class for all nodes that can appear in an expression.
6060
ntFunction,
6161
ntLiteral,
6262
ntColumnRef,
63-
ntCondition
63+
ntCondition,
64+
ntIndexOperator,
6465
};
6566

6667

‎python/core/auto_generated/expression/qgsexpressionnodeimpl.sip.in

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,63 @@ Returns a the name of this operator without the operands.
179179
I.e. "AND", "OR", ...
180180
%End
181181

182+
};
183+
184+
class QgsExpressionNodeIndexOperator : QgsExpressionNode
185+
{
186+
%Docstring
187+
A indexing expression operator, which allows use of square brackets [] to reference map and array items.
188+
189+
.. versionadded:: 3.6
190+
%End
191+
192+
%TypeHeaderCode
193+
#include "qgsexpressionnodeimpl.h"
194+
%End
195+
public:
196+
197+
QgsExpressionNodeIndexOperator( QgsExpressionNode *container /Transfer/, QgsExpressionNode *index /Transfer/ );
198+
%Docstring
199+
Constructor for QgsExpressionNodeIndexOperator.
200+
%End
201+
~QgsExpressionNodeIndexOperator();
202+
203+
QgsExpressionNode *container() const;
204+
%Docstring
205+
Returns the container node, representing an array or map value.
206+
207+
.. seealso:: :py:func:`index`
208+
%End
209+
210+
QgsExpressionNode *index() const;
211+
%Docstring
212+
Returns the index node, representing an array element index or map key.
213+
214+
.. seealso:: :py:func:`container`
215+
%End
216+
217+
virtual QgsExpressionNode::NodeType nodeType() const;
218+
219+
virtual bool prepareNode( QgsExpression *parent, const QgsExpressionContext *context );
220+
221+
virtual QVariant evalNode( QgsExpression *parent, const QgsExpressionContext *context );
222+
223+
virtual QString dump() const;
224+
225+
226+
virtual QSet<QString> referencedColumns() const;
227+
228+
virtual QSet<QString> referencedVariables() const;
229+
230+
virtual QSet<QString> referencedFunctions() const;
231+
232+
virtual bool needsGeometry() const;
233+
234+
virtual QgsExpressionNode *clone() const /Factory/;
235+
236+
virtual bool isStatic( QgsExpression *parent, const QgsExpressionContext *context ) const;
237+
238+
182239
};
183240

184241
class QgsExpressionNodeInOperator : QgsExpressionNode

‎resources/function_help/json/op_index

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "[]",
3+
"type": "operator",
4+
"description": "Index operator. Returns an element from an array or map value.",
5+
"arguments": [
6+
{ "arg": "index", "description": "array index or map key value" }
7+
],
8+
"examples": [
9+
{ "expression":"array(1,2,3)[0]", "returns":"1"},
10+
{ "expression":"array(1,2,3)[2]", "returns":"3"},
11+
{ "expression":"array(1,2,3)[-1]", "returns":"3"},
12+
{ "expression":"map('a',1,'b',2)['a']", "returns":"1"},
13+
{ "expression":"map('a',1,'b',2)['b']", "returns":"2"}
14+
]
15+
}

‎src/core/expression/qgsexpressionnode.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ class CORE_EXPORT QgsExpressionNode SIP_ABSTRACT
7979
ntFunction, //!< \see QgsExpression::Node::NodeFunction
8080
ntLiteral, //!< \see QgsExpression::Node::NodeLiteral
8181
ntColumnRef, //!< \see QgsExpression::Node::NodeColumnRef
82-
ntCondition //!< \see QgsExpression::Node::NodeCondition
82+
ntCondition, //!< \see QgsExpression::Node::NodeCondition
83+
ntIndexOperator, //!< Index operator
8384
};
8485

8586

‎src/core/expression/qgsexpressionnodeimpl.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,3 +1548,97 @@ QString QgsExpressionNodeBinaryOperator::text() const
15481548
return BINARY_OPERATOR_TEXT[mOp];
15491549
}
15501550

1551+
//
1552+
1553+
QVariant QgsExpressionNodeIndexOperator::evalNode( QgsExpression *parent, const QgsExpressionContext *context )
1554+
{
1555+
const QVariant container = mContainer->eval( parent, context );
1556+
ENSURE_NO_EVAL_ERROR;
1557+
const QVariant index = mIndex->eval( parent, context );
1558+
ENSURE_NO_EVAL_ERROR;
1559+
1560+
switch ( container.type() )
1561+
{
1562+
case QVariant::Map:
1563+
return QgsExpressionUtils::getMapValue( container, parent ).value( index.toString() );
1564+
1565+
case QVariant::List:
1566+
case QVariant::StringList:
1567+
{
1568+
const QVariantList list = QgsExpressionUtils::getListValue( container, parent );
1569+
qlonglong pos = QgsExpressionUtils::getIntValue( index, parent );
1570+
if ( pos >= list.length() || pos < -list.length() )
1571+
{
1572+
return QVariant();
1573+
}
1574+
if ( pos < 0 )
1575+
{
1576+
// negative indices are from back of list
1577+
pos += list.length();
1578+
}
1579+
1580+
return list.at( pos );
1581+
}
1582+
1583+
default:
1584+
parent->setEvalErrorString( tr( "[] can only be used with map or array values, not %1" ).arg( QMetaType::typeName( container.type() ) ) );
1585+
return QVariant();
1586+
}
1587+
}
1588+
1589+
QgsExpressionNode::NodeType QgsExpressionNodeIndexOperator::nodeType() const
1590+
{
1591+
return ntIndexOperator;
1592+
}
1593+
1594+
bool QgsExpressionNodeIndexOperator::prepareNode( QgsExpression *parent, const QgsExpressionContext *context )
1595+
{
1596+
bool resC = mContainer->prepare( parent, context );
1597+
bool resV = mIndex->prepare( parent, context );
1598+
return resC && resV;
1599+
}
1600+
1601+
QString QgsExpressionNodeIndexOperator::dump() const
1602+
{
1603+
return QStringLiteral( "%1[%2]" ).arg( mContainer->dump(), mIndex->dump() );
1604+
}
1605+
1606+
QSet<QString> QgsExpressionNodeIndexOperator::referencedColumns() const
1607+
{
1608+
return mContainer->referencedColumns() + mIndex->referencedColumns();
1609+
}
1610+
1611+
QSet<QString> QgsExpressionNodeIndexOperator::referencedVariables() const
1612+
{
1613+
return mContainer->referencedVariables() + mIndex->referencedVariables();
1614+
}
1615+
1616+
QSet<QString> QgsExpressionNodeIndexOperator::referencedFunctions() const
1617+
{
1618+
return mContainer->referencedFunctions() + mIndex->referencedFunctions();
1619+
}
1620+
1621+
QList<const QgsExpressionNode *> QgsExpressionNodeIndexOperator::nodes() const
1622+
{
1623+
QList<const QgsExpressionNode *> lst;
1624+
lst << this;
1625+
lst += mContainer->nodes() + mIndex->nodes();
1626+
return lst;
1627+
}
1628+
1629+
bool QgsExpressionNodeIndexOperator::needsGeometry() const
1630+
{
1631+
return mContainer->needsGeometry() || mIndex->needsGeometry();
1632+
}
1633+
1634+
QgsExpressionNode *QgsExpressionNodeIndexOperator::clone() const
1635+
{
1636+
QgsExpressionNodeIndexOperator *copy = new QgsExpressionNodeIndexOperator( mContainer->clone(), mIndex->clone() );
1637+
cloneTo( copy );
1638+
return copy;
1639+
}
1640+
1641+
bool QgsExpressionNodeIndexOperator::isStatic( QgsExpression *parent, const QgsExpressionContext *context ) const
1642+
{
1643+
return mContainer->isStatic( parent, context ) && mIndex->isStatic( parent, context );
1644+
}

‎src/core/expression/qgsexpressionnodeimpl.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,57 @@ class CORE_EXPORT QgsExpressionNodeBinaryOperator : public QgsExpressionNode
206206
static const char *BINARY_OPERATOR_TEXT[];
207207
};
208208

209+
/**
210+
* A indexing expression operator, which allows use of square brackets [] to reference map and array items.
211+
* \ingroup core
212+
* \since QGIS 3.6
213+
*/
214+
class CORE_EXPORT QgsExpressionNodeIndexOperator : public QgsExpressionNode
215+
{
216+
public:
217+
218+
/**
219+
* Constructor for QgsExpressionNodeIndexOperator.
220+
*/
221+
QgsExpressionNodeIndexOperator( QgsExpressionNode *container SIP_TRANSFER, QgsExpressionNode *index SIP_TRANSFER )
222+
: mContainer( container )
223+
, mIndex( index )
224+
{}
225+
~QgsExpressionNodeIndexOperator() override { delete mContainer; delete mIndex; }
226+
227+
/**
228+
* Returns the container node, representing an array or map value.
229+
* \see index()
230+
*/
231+
QgsExpressionNode *container() const { return mContainer; }
232+
233+
/**
234+
* Returns the index node, representing an array element index or map key.
235+
* \see container()
236+
*/
237+
QgsExpressionNode *index() const { return mIndex; }
238+
239+
QgsExpressionNode::NodeType nodeType() const override;
240+
bool prepareNode( QgsExpression *parent, const QgsExpressionContext *context ) override;
241+
QVariant evalNode( QgsExpression *parent, const QgsExpressionContext *context ) override;
242+
QString dump() const override;
243+
244+
QSet<QString> referencedColumns() const override;
245+
QSet<QString> referencedVariables() const override;
246+
QSet<QString> referencedFunctions() const override;
247+
QList<const QgsExpressionNode *> nodes( ) const override; SIP_SKIP
248+
249+
bool needsGeometry() const override;
250+
QgsExpressionNode *clone() const override SIP_FACTORY;
251+
bool isStatic( QgsExpression *parent, const QgsExpressionContext *context ) const override;
252+
253+
private:
254+
255+
QgsExpressionNode *mContainer = nullptr;
256+
QgsExpressionNode *mIndex = nullptr;
257+
258+
};
259+
209260
/**
210261
* An expression node for value IN or NOT IN clauses.
211262
* \ingroup core

‎src/core/qgsexpressionparser.yy

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ void addParserLocation(YYLTYPE* yyloc, QgsExpressionNode *node)
115115
//
116116

117117
// operator tokens
118-
%token <b_op> OR AND EQ NE LE GE LT GT REGEXP LIKE IS PLUS MINUS MUL DIV INTDIV MOD CONCAT POW SQUARE_BRAKET_OPENING SQUARE_BRAKET_CLOSING
118+
%token <b_op> OR AND EQ NE LE GE LT GT REGEXP LIKE IS PLUS MINUS MUL DIV INTDIV MOD CONCAT POW
119119
%token <u_op> NOT
120120
%token IN
121121

@@ -166,6 +166,7 @@ void addParserLocation(YYLTYPE* yyloc, QgsExpressionNode *node)
166166
%right UMINUS // fictitious symbol (for unary minus)
167167

168168
%left COMMA
169+
%left '['
169170

170171
%destructor { delete $$; } <node>
171172
%destructor { delete $$; } <nodelist>
@@ -284,15 +285,8 @@ expression:
284285
| expression IN '(' exp_list ')' { $$ = new QgsExpressionNodeInOperator($1, $4, false); }
285286
| expression NOT IN '(' exp_list ')' { $$ = new QgsExpressionNodeInOperator($1, $5, true); }
286287

287-
| expression '[' expression ']'
288-
{
289-
QgsExpressionNode::NodeList* args = new QgsExpressionNode::NodeList();
290-
args->append( $1 );
291-
args->append( $3 );
292-
QgsExpressionNodeFunction *f = new QgsExpressionNodeFunction( QgsExpression::functionIndex( "map_get" ), args );
293-
$$ = f;
294-
}
295-
288+
| expression '[' expression ']' { $$ = new QgsExpressionNodeIndexOperator( $1, $3 ); }
289+
296290
| PLUS expression %prec UMINUS { $$ = $2; }
297291
| MINUS expression %prec UMINUS { $$ = new QgsExpressionNodeUnaryOperator( QgsExpressionNodeUnaryOperator::uoMinus, $2); }
298292

‎src/core/qgssqlexpressioncompiler.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,9 @@ QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compileNode( const Qg
401401

402402
case QgsExpressionNode::ntCondition:
403403
break;
404+
405+
case QgsExpressionNode::ntIndexOperator:
406+
break;
404407
}
405408

406409
return Fail;

‎src/gui/qgsexpressionbuilderwidget.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
9797
connect( mShowHelpButton, &QPushButton::clicked, this, [ = ]()
9898
{
9999
functionsplit->setSizes( QList<int>( {mOperationListGroup->width() - mHelpAndValuesWidget->minimumWidth(),
100-
mHelpAndValuesWidget->minimumWidth()} ) );
100+
mHelpAndValuesWidget->minimumWidth()
101+
} ) );
101102
mShowHelpButton->setEnabled( false );
102103
} );
103104
connect( functionsplit, &QSplitter::splitterMoved, this, [ = ]( int, int )
@@ -608,6 +609,7 @@ void QgsExpressionBuilderWidget::updateFunctionTree()
608609
registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<>" ), QStringLiteral( " <> " ) );
609610
registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<=" ), QStringLiteral( " <= " ) );
610611
registerItem( QStringLiteral( "Operators" ), QStringLiteral( ">=" ), QStringLiteral( " >= " ) );
612+
registerItem( QStringLiteral( "Operators" ), QStringLiteral( "[]" ), QStringLiteral( "[ ]" ) );
611613
registerItem( QStringLiteral( "Operators" ), QStringLiteral( "||" ), QStringLiteral( " || " ) );
612614
registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IN" ), QStringLiteral( " IN " ) );
613615
registerItem( QStringLiteral( "Operators" ), QStringLiteral( "LIKE" ), QStringLiteral( " LIKE " ) );
@@ -957,6 +959,10 @@ void QgsExpressionBuilderWidget::createMarkers( const QgsExpressionNode *inNode
957959
}
958960
break;
959961
}
962+
case QgsExpressionNode::NodeType::ntIndexOperator:
963+
{
964+
break;
965+
}
960966
}
961967
}
962968

‎src/providers/ogr/qgsogrexpressioncompiler.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ QgsSqlExpressionCompiler::Result QgsOgrExpressionCompiler::compileNode( const Qg
7979
case QgsExpressionNode::ntColumnRef:
8080
case QgsExpressionNode::ntInOperator:
8181
case QgsExpressionNode::ntLiteral:
82+
case QgsExpressionNode::ntIndexOperator:
8283
break;
8384
}
8485

‎tests/src/core/testqgsexpression.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3181,6 +3181,69 @@ class TestQgsExpression: public QObject
31813181

31823182
QCOMPARE( v.toDateTime().toMSecsSinceEpoch(), v2.toDateTime().toMSecsSinceEpoch() );
31833183
}
3184+
3185+
void test_IndexOperator()
3186+
{
3187+
QgsExpressionContext context;
3188+
QgsExpression e( QStringLiteral( "'['" ) );
3189+
QVariant result = e.evaluate( &context );
3190+
QCOMPARE( result.toString(), QStringLiteral( "[" ) );
3191+
e = QgsExpression( QStringLiteral( "']'" ) );
3192+
QCOMPARE( e.evaluate( &context ).toString(), QStringLiteral( "]" ) );
3193+
e = QgsExpression( QStringLiteral( "'[3]'" ) );
3194+
QCOMPARE( e.evaluate( &context ).toString(), QStringLiteral( "[3]" ) );
3195+
e = QgsExpression( QStringLiteral( "'a[3]'" ) );
3196+
QCOMPARE( e.evaluate( &context ).toString(), QStringLiteral( "a[3]" ) );
3197+
e = QgsExpression( QStringLiteral( "\"a[3]\"" ) );
3198+
QCOMPARE( e.evaluate( &context ).toString(), QStringLiteral( "[a[3]]" ) );
3199+
e = QgsExpression( QStringLiteral( "(1+2)[0]" ) );
3200+
QVERIFY( !e.evaluate( &context ).isValid() );
3201+
QVERIFY( e.hasEvalError() );
3202+
e = QgsExpression( QStringLiteral( "(1+2)['a']" ) );
3203+
QVERIFY( !e.evaluate( &context ).isValid() );
3204+
QVERIFY( e.hasEvalError() );
3205+
// arrays
3206+
e = QgsExpression( QStringLiteral( "array(1,2,3)[0]" ) );
3207+
QCOMPARE( e.evaluate( &context ).toInt(), 1 );
3208+
e = QgsExpression( QStringLiteral( "((array(1,2,3)))[0]" ) );
3209+
QCOMPARE( e.evaluate( &context ).toInt(), 1 );
3210+
e = QgsExpression( QStringLiteral( "array(1,2,3)[1]" ) );
3211+
QCOMPARE( e.evaluate( &context ).toInt(), 2 );
3212+
e = QgsExpression( QStringLiteral( "array(1,2,3)[2]" ) );
3213+
QCOMPARE( e.evaluate( &context ).toInt(), 3 );
3214+
e = QgsExpression( QStringLiteral( "array(1,2,3)[-1]" ) );
3215+
QCOMPARE( e.evaluate( &context ).toInt(), 3 );
3216+
e = QgsExpression( QStringLiteral( "array(1,2,3)[-2]" ) );
3217+
QCOMPARE( e.evaluate( &context ).toInt(), 2 );
3218+
e = QgsExpression( QStringLiteral( "array(1,2,3)[-3]" ) );
3219+
QCOMPARE( e.evaluate( &context ).toInt(), 1 );
3220+
e = QgsExpression( QStringLiteral( "array(1,2,3)[1+1]" ) );
3221+
QCOMPARE( e.evaluate( &context ).toInt(), 3 );
3222+
e = QgsExpression( QStringLiteral( "array(1,2,3)[(3-2)]" ) );
3223+
QCOMPARE( e.evaluate( &context ).toInt(), 2 );
3224+
e = QgsExpression( QStringLiteral( "array(1,2,3)[3]" ) );
3225+
QVERIFY( !e.evaluate( &context ).isValid() );
3226+
QVERIFY( !e.hasEvalError() ); // no eval error - we are tolerant to this
3227+
e = QgsExpression( QStringLiteral( "array(1,2,3)[-4]" ) );
3228+
QVERIFY( !e.evaluate( &context ).isValid() );
3229+
QVERIFY( !e.hasEvalError() ); // no eval error - we are tolerant to this
3230+
3231+
// maps
3232+
e = QgsExpression( QStringLiteral( "map('a',1,'b',2,'c',3)[0]" ) );
3233+
QVERIFY( !e.evaluate( &context ).isValid() );
3234+
QVERIFY( !e.hasEvalError() ); // no eval error - we are tolerant to this
3235+
e = QgsExpression( QStringLiteral( "map('a',1,'b',2,'c',3)['d']" ) );
3236+
QVERIFY( !e.evaluate( &context ).isValid() );
3237+
QVERIFY( !e.hasEvalError() ); // no eval error - we are tolerant to this
3238+
e = QgsExpression( QStringLiteral( "map('a',1,'b',2,'c',3)['a']" ) );
3239+
QCOMPARE( e.evaluate( &context ).toInt(), 1 );
3240+
e = QgsExpression( QStringLiteral( "map('a',1,'b',2,'c',3)['b']" ) );
3241+
QCOMPARE( e.evaluate( &context ).toInt(), 2 );
3242+
e = QgsExpression( QStringLiteral( "map('a',1,'b',2,'c',3)['c']" ) );
3243+
QCOMPARE( e.evaluate( &context ).toInt(), 3 );
3244+
e = QgsExpression( QStringLiteral( "map('a',1,'bbb',2,'c',3)['b'||'b'||'b']" ) );
3245+
QCOMPARE( e.evaluate( &context ).toInt(), 2 );
3246+
}
31843247
};
31853248

31863249
QGSTEST_MAIN( TestQgsExpression )
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.