Navigation Menu

Skip to content

Commit

Permalink
Add support for user-defined special columns in expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugo Mercier committed Sep 26, 2012
1 parent 4c82ade commit 9a671f9
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 16 deletions.
2 changes: 2 additions & 0 deletions python/core/qgsexpression.sip
Expand Up @@ -119,6 +119,8 @@ public:

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

static QList<QgsExpression::FunctionDef> specialColumns();

// tells whether the identifier is a name of existing function
static bool isFunctionName( QString name );

Expand Down
116 changes: 107 additions & 9 deletions src/core/qgsexpression.cpp
Expand Up @@ -799,6 +799,19 @@ static QVariant fcnFormatNumber( const QVariantList& values, QgsFeature*, QgsExp
return QString( "%L1" ).arg( value, 0, 'f', places );
}

static QVariant fcnFormatDate( const QVariantList& values, QgsFeature*, QgsExpression* parent )
{
QDateTime dt = getDateTimeValue( values.at( 0 ), parent );
QString format = getStringValue( values.at( 1 ), parent );
return dt.toString( format );
}

static QVariant fcnSpecialColumn( const QVariantList& values, QgsFeature* /*f*/, QgsExpression* parent )
{
QString varName = getStringValue( values.at( 0 ), parent );
return QgsExpression::specialColumn( varName );
}

QList<QgsExpression::FunctionDef> QgsExpression::gmBuiltinFunctions;

const QList<QgsExpression::FunctionDef> &QgsExpression::BuiltinFunctions()
Expand Down Expand Up @@ -855,6 +868,7 @@ const QList<QgsExpression::FunctionDef> &QgsExpression::BuiltinFunctions()
<< FunctionDef( "rpad", 3, fcnRPad, QObject::tr( "String" ) )
<< FunctionDef( "lpad", 3, fcnLPad, QObject::tr( "String" ) )
<< FunctionDef( "format_number", 2, fcnFormatNumber, QObject::tr( "String" ) )
<< FunctionDef( "format_date", 2, fcnFormatDate, QObject::tr( "String" ) )

// geometry accessors
<< FunctionDef( "xat", 1, fcnXat, QObject::tr( "Geometry" ), "", true )
Expand All @@ -868,12 +882,61 @@ const QList<QgsExpression::FunctionDef> &QgsExpression::BuiltinFunctions()
<< FunctionDef( "$rownum", 0, fcnRowNumber, QObject::tr( "Record" ) )
<< FunctionDef( "$id", 0, fcnFeatureId, QObject::tr( "Record" ) )
<< FunctionDef( "$scale", 0, fcnScale, QObject::tr( "Record" ) )
// private functions
<< FunctionDef( "_specialcol_", 1, fcnSpecialColumn, QObject::tr( "Special" ) )
;
}

return gmBuiltinFunctions;
}

QMap<QString, QVariant> QgsExpression::gmSpecialColumns;

void QgsExpression::setSpecialColumn( const QString& name, QVariant variant )
{
int fnIdx = functionIndex( name );
if ( fnIdx != -1 )
{
// function of the same name already exists
return;
}
gmSpecialColumns[ name ] = variant;
}

void QgsExpression::unsetSpecialColumn( const QString& name )
{
QMap<QString, QVariant>::iterator fit = gmSpecialColumns.find( name );
if ( fit != gmSpecialColumns.end() )
{
gmSpecialColumns.erase( fit );
}
}

QVariant QgsExpression::specialColumn( const QString& name )
{
int fnIdx = functionIndex( name );
if ( fnIdx != -1 )
{
// function of the same name already exists
return QVariant();
}
QMap<QString, QVariant>::iterator it = gmSpecialColumns.find( name );
if ( it == gmSpecialColumns.end() )
{
return QVariant();
}
return it.value();
}

QList<QgsExpression::FunctionDef> QgsExpression::specialColumns()
{
QList<FunctionDef> defs;
for ( QMap<QString, QVariant>::const_iterator it = gmSpecialColumns.begin(); it != gmSpecialColumns.end(); ++it )
{
defs << FunctionDef( it.key(), 0, 0, QObject::tr( "Record" ));
}
return defs;
}

bool QgsExpression::isFunctionName( QString name )
{
Expand Down Expand Up @@ -1049,12 +1112,27 @@ void QgsExpression::acceptVisitor( QgsExpression::Visitor& v )
mRootNode->accept( v );
}

QString QgsExpression::replaceExpressionText( QString action, QgsFeature &feat,
QString QgsExpression::replaceExpressionText( QString action, QgsFeature* feat,
QgsVectorLayer* layer,
const QMap<QString, QVariant> *substitutionMap )
{
QString expr_action;

QMap<QString, QVariant> savedValues;
if ( substitutionMap )
{
// variables with a local scope (must be restored after evaluation)
for ( QMap<QString, QVariant>::const_iterator sit = substitutionMap->begin(); sit != substitutionMap->end(); ++sit )
{
QVariant oldValue = QgsExpression::specialColumn( sit.key() );
if ( !oldValue.isNull() )
savedValues.insert( sit.key(), oldValue );

// set the new value
QgsExpression::setSpecialColumn( sit.key(), sit.value() );
}
}

int index = 0;
while ( index < action.size() )
{
Expand All @@ -1070,12 +1148,6 @@ QString QgsExpression::replaceExpressionText( QString action, QgsFeature &feat,
QString to_replace = rx.cap( 1 ).trimmed();
QgsDebugMsg( "Found expression: " + to_replace );

if ( substitutionMap && substitutionMap->contains( to_replace ) )
{
expr_action += action.mid( start, pos - start ) + substitutionMap->value( to_replace ).toString();
continue;
}

QgsExpression exp( to_replace );
if ( exp.hasParserError() )
{
Expand All @@ -1084,7 +1156,15 @@ QString QgsExpression::replaceExpressionText( QString action, QgsFeature &feat,
continue;
}

QVariant result = exp.evaluate( &feat, layer->pendingFields() );
QVariant result;
if ( layer )
{
result = exp.evaluate( feat, layer->pendingFields() );
}
else
{
result = exp.evaluate( feat );
}
if ( exp.hasEvalError() )
{
QgsDebugMsg( "Expression parser eval error: " + exp.evalErrorString() );
Expand All @@ -1097,10 +1177,24 @@ QString QgsExpression::replaceExpressionText( QString action, QgsFeature &feat,
}

expr_action += action.mid( index );

// restore overwritten local values
for ( QMap<QString, QVariant>::const_iterator sit = savedValues.begin(); sit != savedValues.end(); ++sit )
{
QgsExpression::setSpecialColumn( sit.key(), sit.value() );
}

return expr_action;
}


QString QgsExpression::replaceExpressionText( QString action, QgsFeature& feat,
QgsVectorLayer* layer,
const QMap<QString, QVariant> *substitutionMap )
{
return replaceExpressionText( action, &feat, layer, substitutionMap );
}

QgsExpression::Node* QgsExpression::Node::createFromOgcFilter( QDomElement &element, QString &errorMessage )
{
if ( element.isNull() )
Expand Down Expand Up @@ -2048,7 +2142,11 @@ QgsExpression::Node* QgsExpression::NodeLiteral::createFromOgcFilter( QDomElemen

QVariant QgsExpression::NodeColumnRef::eval( QgsExpression* /*parent*/, QgsFeature* f )
{
return f->attributeMap()[mIndex];
if ( f )
{
return f->attributeMap()[mIndex];
}
return QVariant("[" + mName + "]");
}

bool QgsExpression::NodeColumnRef::prepare( QgsExpression* parent, const QgsFieldMap& fields )
Expand Down
20 changes: 19 additions & 1 deletion src/core/qgsexpression.h
Expand Up @@ -122,6 +122,13 @@ class CORE_EXPORT QgsExpression
//! Return the number used for $rownum special column
int currentRowNumber() { return mRowNumber; }

//! Assign a special column
static void setSpecialColumn( const QString& name, QVariant value );
//! Unset a special column
static void unsetSpecialColumn( const QString& name );
//! Return the value of the given special column or a null QVariant if undefined
static QVariant specialColumn( const QString& name );

void setScale( double scale ) { mScale = scale; }

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


static QString replaceExpressionText( QString action, QgsFeature& feat,
QgsVectorLayer* layer,
const QMap<QString, QVariant> *substitutionMap = 0 );
//

enum UnaryOperator
Expand Down Expand Up @@ -224,6 +235,11 @@ class CORE_EXPORT QgsExpression
*/
static int functionCount();

/**
* Returns a list of special Column definitions
*/
static QList<FunctionDef> specialColumns();

//! return quoted column reference (in double quotes)
static QString quotedColumnRef( QString name ) { return QString( "\"%1\"" ).arg( name.replace( "\"", "\"\"" ) ); }
//! return quoted string (in single quotes)
Expand Down Expand Up @@ -530,6 +546,8 @@ class CORE_EXPORT QgsExpression
int mRowNumber;
double mScale;

static QMap<QString, QVariant> gmSpecialColumns;

QgsDistanceArea* mCalc;
};

Expand Down
20 changes: 16 additions & 4 deletions src/core/qgsexpressionparser.yy
Expand Up @@ -193,11 +193,23 @@ expression:
int fnIndex = QgsExpression::functionIndex(*$1);
if (fnIndex == -1)
{
exp_error("Special column is not known");
YYERROR;
QVariant userVar = QgsExpression::specialColumn( *$1 );
if ( userVar.isNull() )
{
exp_error("Special column is not known");
YYERROR;
}
// $var is equivalent to _specialcol_( "$var" )
QgsExpression::NodeList* args = new QgsExpression::NodeList();
QgsExpression::NodeLiteral* literal = new QgsExpression::NodeLiteral( *$1 );
args->append( literal );
$$ = new QgsExpression::NodeFunction( QgsExpression::functionIndex( "_specialcol_" ), args );
}
$$ = new QgsExpression::NodeFunction( fnIndex, NULL );
delete $1;
else
{
$$ = new QgsExpression::NodeFunction( fnIndex, NULL );
delete $1;
}
}

// literals
Expand Down
13 changes: 11 additions & 2 deletions src/gui/qgsexpressionbuilderwidget.cpp
Expand Up @@ -90,6 +90,8 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
{
QgsExpression::FunctionDef func = QgsExpression::BuiltinFunctions()[i];
QString name = func.mName;
if ( name.startsWith( "_" ) ) // do not display private functions
continue;
if ( func.mParams >= 1 )
name += "(";
registerItem( func.mGroup, func.mName, " " + name + " " );
Expand Down Expand Up @@ -259,8 +261,6 @@ void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()

QgsExpression exp( text );

// TODO We could do this without a layer.
// Maybe just calling exp.evaluate()?
if ( mLayer )
{
if ( !mFeature.isValid() )
Expand All @@ -282,6 +282,15 @@ void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()
lblPreview->setText( "" );
}
}
else
{
// No layer defined
QVariant value = exp.evaluate();
if ( !exp.hasEvalError() )
{
lblPreview->setText( value.toString() );
}
}

if ( exp.hasParserError() || exp.hasEvalError() )
{
Expand Down

0 comments on commit 9a671f9

Please sign in to comment.