Skip to content

Commit 9a671f9

Browse files
author
Hugo Mercier
committed
Add support for user-defined special columns in expressions
1 parent 4c82ade commit 9a671f9

File tree

5 files changed

+155
-16
lines changed

5 files changed

+155
-16
lines changed

python/core/qgsexpression.sip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ public:
119119

120120
static const QList<QgsExpression::FunctionDef> &BuiltinFunctions();
121121

122+
static QList<QgsExpression::FunctionDef> specialColumns();
123+
122124
// tells whether the identifier is a name of existing function
123125
static bool isFunctionName( QString name );
124126

src/core/qgsexpression.cpp

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,19 @@ static QVariant fcnFormatNumber( const QVariantList& values, QgsFeature*, QgsExp
799799
return QString( "%L1" ).arg( value, 0, 'f', places );
800800
}
801801

802+
static QVariant fcnFormatDate( const QVariantList& values, QgsFeature*, QgsExpression* parent )
803+
{
804+
QDateTime dt = getDateTimeValue( values.at( 0 ), parent );
805+
QString format = getStringValue( values.at( 1 ), parent );
806+
return dt.toString( format );
807+
}
808+
809+
static QVariant fcnSpecialColumn( const QVariantList& values, QgsFeature* /*f*/, QgsExpression* parent )
810+
{
811+
QString varName = getStringValue( values.at( 0 ), parent );
812+
return QgsExpression::specialColumn( varName );
813+
}
814+
802815
QList<QgsExpression::FunctionDef> QgsExpression::gmBuiltinFunctions;
803816

804817
const QList<QgsExpression::FunctionDef> &QgsExpression::BuiltinFunctions()
@@ -855,6 +868,7 @@ const QList<QgsExpression::FunctionDef> &QgsExpression::BuiltinFunctions()
855868
<< FunctionDef( "rpad", 3, fcnRPad, QObject::tr( "String" ) )
856869
<< FunctionDef( "lpad", 3, fcnLPad, QObject::tr( "String" ) )
857870
<< FunctionDef( "format_number", 2, fcnFormatNumber, QObject::tr( "String" ) )
871+
<< FunctionDef( "format_date", 2, fcnFormatDate, QObject::tr( "String" ) )
858872

859873
// geometry accessors
860874
<< FunctionDef( "xat", 1, fcnXat, QObject::tr( "Geometry" ), "", true )
@@ -868,12 +882,61 @@ const QList<QgsExpression::FunctionDef> &QgsExpression::BuiltinFunctions()
868882
<< FunctionDef( "$rownum", 0, fcnRowNumber, QObject::tr( "Record" ) )
869883
<< FunctionDef( "$id", 0, fcnFeatureId, QObject::tr( "Record" ) )
870884
<< FunctionDef( "$scale", 0, fcnScale, QObject::tr( "Record" ) )
885+
// private functions
886+
<< FunctionDef( "_specialcol_", 1, fcnSpecialColumn, QObject::tr( "Special" ) )
871887
;
872888
}
873889

874890
return gmBuiltinFunctions;
875891
}
876892

893+
QMap<QString, QVariant> QgsExpression::gmSpecialColumns;
894+
895+
void QgsExpression::setSpecialColumn( const QString& name, QVariant variant )
896+
{
897+
int fnIdx = functionIndex( name );
898+
if ( fnIdx != -1 )
899+
{
900+
// function of the same name already exists
901+
return;
902+
}
903+
gmSpecialColumns[ name ] = variant;
904+
}
905+
906+
void QgsExpression::unsetSpecialColumn( const QString& name )
907+
{
908+
QMap<QString, QVariant>::iterator fit = gmSpecialColumns.find( name );
909+
if ( fit != gmSpecialColumns.end() )
910+
{
911+
gmSpecialColumns.erase( fit );
912+
}
913+
}
914+
915+
QVariant QgsExpression::specialColumn( const QString& name )
916+
{
917+
int fnIdx = functionIndex( name );
918+
if ( fnIdx != -1 )
919+
{
920+
// function of the same name already exists
921+
return QVariant();
922+
}
923+
QMap<QString, QVariant>::iterator it = gmSpecialColumns.find( name );
924+
if ( it == gmSpecialColumns.end() )
925+
{
926+
return QVariant();
927+
}
928+
return it.value();
929+
}
930+
931+
QList<QgsExpression::FunctionDef> QgsExpression::specialColumns()
932+
{
933+
QList<FunctionDef> defs;
934+
for ( QMap<QString, QVariant>::const_iterator it = gmSpecialColumns.begin(); it != gmSpecialColumns.end(); ++it )
935+
{
936+
defs << FunctionDef( it.key(), 0, 0, QObject::tr( "Record" ));
937+
}
938+
return defs;
939+
}
877940

878941
bool QgsExpression::isFunctionName( QString name )
879942
{
@@ -1049,12 +1112,27 @@ void QgsExpression::acceptVisitor( QgsExpression::Visitor& v )
10491112
mRootNode->accept( v );
10501113
}
10511114

1052-
QString QgsExpression::replaceExpressionText( QString action, QgsFeature &feat,
1115+
QString QgsExpression::replaceExpressionText( QString action, QgsFeature* feat,
10531116
QgsVectorLayer* layer,
10541117
const QMap<QString, QVariant> *substitutionMap )
10551118
{
10561119
QString expr_action;
10571120

1121+
QMap<QString, QVariant> savedValues;
1122+
if ( substitutionMap )
1123+
{
1124+
// variables with a local scope (must be restored after evaluation)
1125+
for ( QMap<QString, QVariant>::const_iterator sit = substitutionMap->begin(); sit != substitutionMap->end(); ++sit )
1126+
{
1127+
QVariant oldValue = QgsExpression::specialColumn( sit.key() );
1128+
if ( !oldValue.isNull() )
1129+
savedValues.insert( sit.key(), oldValue );
1130+
1131+
// set the new value
1132+
QgsExpression::setSpecialColumn( sit.key(), sit.value() );
1133+
}
1134+
}
1135+
10581136
int index = 0;
10591137
while ( index < action.size() )
10601138
{
@@ -1070,12 +1148,6 @@ QString QgsExpression::replaceExpressionText( QString action, QgsFeature &feat,
10701148
QString to_replace = rx.cap( 1 ).trimmed();
10711149
QgsDebugMsg( "Found expression: " + to_replace );
10721150

1073-
if ( substitutionMap && substitutionMap->contains( to_replace ) )
1074-
{
1075-
expr_action += action.mid( start, pos - start ) + substitutionMap->value( to_replace ).toString();
1076-
continue;
1077-
}
1078-
10791151
QgsExpression exp( to_replace );
10801152
if ( exp.hasParserError() )
10811153
{
@@ -1084,7 +1156,15 @@ QString QgsExpression::replaceExpressionText( QString action, QgsFeature &feat,
10841156
continue;
10851157
}
10861158

1087-
QVariant result = exp.evaluate( &feat, layer->pendingFields() );
1159+
QVariant result;
1160+
if ( layer )
1161+
{
1162+
result = exp.evaluate( feat, layer->pendingFields() );
1163+
}
1164+
else
1165+
{
1166+
result = exp.evaluate( feat );
1167+
}
10881168
if ( exp.hasEvalError() )
10891169
{
10901170
QgsDebugMsg( "Expression parser eval error: " + exp.evalErrorString() );
@@ -1097,10 +1177,24 @@ QString QgsExpression::replaceExpressionText( QString action, QgsFeature &feat,
10971177
}
10981178

10991179
expr_action += action.mid( index );
1180+
1181+
// restore overwritten local values
1182+
for ( QMap<QString, QVariant>::const_iterator sit = savedValues.begin(); sit != savedValues.end(); ++sit )
1183+
{
1184+
QgsExpression::setSpecialColumn( sit.key(), sit.value() );
1185+
}
1186+
11001187
return expr_action;
11011188
}
11021189

11031190

1191+
QString QgsExpression::replaceExpressionText( QString action, QgsFeature& feat,
1192+
QgsVectorLayer* layer,
1193+
const QMap<QString, QVariant> *substitutionMap )
1194+
{
1195+
return replaceExpressionText( action, &feat, layer, substitutionMap );
1196+
}
1197+
11041198
QgsExpression::Node* QgsExpression::Node::createFromOgcFilter( QDomElement &element, QString &errorMessage )
11051199
{
11061200
if ( element.isNull() )
@@ -2048,7 +2142,11 @@ QgsExpression::Node* QgsExpression::NodeLiteral::createFromOgcFilter( QDomElemen
20482142

20492143
QVariant QgsExpression::NodeColumnRef::eval( QgsExpression* /*parent*/, QgsFeature* f )
20502144
{
2051-
return f->attributeMap()[mIndex];
2145+
if ( f )
2146+
{
2147+
return f->attributeMap()[mIndex];
2148+
}
2149+
return QVariant("[" + mName + "]");
20522150
}
20532151

20542152
bool QgsExpression::NodeColumnRef::prepare( QgsExpression* parent, const QgsFieldMap& fields )

src/core/qgsexpression.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ class CORE_EXPORT QgsExpression
122122
//! Return the number used for $rownum special column
123123
int currentRowNumber() { return mRowNumber; }
124124

125+
//! Assign a special column
126+
static void setSpecialColumn( const QString& name, QVariant value );
127+
//! Unset a special column
128+
static void unsetSpecialColumn( const QString& name );
129+
//! Return the value of the given special column or a null QVariant if undefined
130+
static QVariant specialColumn( const QString& name );
131+
125132
void setScale( double scale ) { mScale = scale; }
126133

127134
int scale() {return mScale; }
@@ -140,10 +147,14 @@ class CORE_EXPORT QgsExpression
140147
Additional substitutions can be passed through the substitutionMap
141148
parameter
142149
*/
143-
static QString replaceExpressionText( QString action, QgsFeature &feat,
150+
static QString replaceExpressionText( QString action, QgsFeature* feat,
144151
QgsVectorLayer* layer,
145152
const QMap<QString, QVariant> *substitutionMap = 0 );
146153

154+
155+
static QString replaceExpressionText( QString action, QgsFeature& feat,
156+
QgsVectorLayer* layer,
157+
const QMap<QString, QVariant> *substitutionMap = 0 );
147158
//
148159

149160
enum UnaryOperator
@@ -224,6 +235,11 @@ class CORE_EXPORT QgsExpression
224235
*/
225236
static int functionCount();
226237

238+
/**
239+
* Returns a list of special Column definitions
240+
*/
241+
static QList<FunctionDef> specialColumns();
242+
227243
//! return quoted column reference (in double quotes)
228244
static QString quotedColumnRef( QString name ) { return QString( "\"%1\"" ).arg( name.replace( "\"", "\"\"" ) ); }
229245
//! return quoted string (in single quotes)
@@ -530,6 +546,8 @@ class CORE_EXPORT QgsExpression
530546
int mRowNumber;
531547
double mScale;
532548

549+
static QMap<QString, QVariant> gmSpecialColumns;
550+
533551
QgsDistanceArea* mCalc;
534552
};
535553

src/core/qgsexpressionparser.yy

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,23 @@ expression:
193193
int fnIndex = QgsExpression::functionIndex(*$1);
194194
if (fnIndex == -1)
195195
{
196-
exp_error("Special column is not known");
197-
YYERROR;
196+
QVariant userVar = QgsExpression::specialColumn( *$1 );
197+
if ( userVar.isNull() )
198+
{
199+
exp_error("Special column is not known");
200+
YYERROR;
201+
}
202+
// $var is equivalent to _specialcol_( "$var" )
203+
QgsExpression::NodeList* args = new QgsExpression::NodeList();
204+
QgsExpression::NodeLiteral* literal = new QgsExpression::NodeLiteral( *$1 );
205+
args->append( literal );
206+
$$ = new QgsExpression::NodeFunction( QgsExpression::functionIndex( "_specialcol_" ), args );
198207
}
199-
$$ = new QgsExpression::NodeFunction( fnIndex, NULL );
200-
delete $1;
208+
else
209+
{
210+
$$ = new QgsExpression::NodeFunction( fnIndex, NULL );
211+
delete $1;
212+
}
201213
}
202214

203215
// literals

src/gui/qgsexpressionbuilderwidget.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
9090
{
9191
QgsExpression::FunctionDef func = QgsExpression::BuiltinFunctions()[i];
9292
QString name = func.mName;
93+
if ( name.startsWith( "_" ) ) // do not display private functions
94+
continue;
9395
if ( func.mParams >= 1 )
9496
name += "(";
9597
registerItem( func.mGroup, func.mName, " " + name + " " );
@@ -259,8 +261,6 @@ void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()
259261

260262
QgsExpression exp( text );
261263

262-
// TODO We could do this without a layer.
263-
// Maybe just calling exp.evaluate()?
264264
if ( mLayer )
265265
{
266266
if ( !mFeature.isValid() )
@@ -282,6 +282,15 @@ void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()
282282
lblPreview->setText( "" );
283283
}
284284
}
285+
else
286+
{
287+
// No layer defined
288+
QVariant value = exp.evaluate();
289+
if ( !exp.hasEvalError() )
290+
{
291+
lblPreview->setText( value.toString() );
292+
}
293+
}
285294

286295
if ( exp.hasParserError() || exp.hasEvalError() )
287296
{

0 commit comments

Comments
 (0)