New framework for context based expressions

This commit adds the ability for expressions to be evaluated against
specific contexts. It replaces the previous behaviour where
expressions were evaluated against a specific feature and could
utilise fragile global "special columns".

Now, expressions are instead evaluated using a context designed for
each individual expression. This is done via QgsExpressionContext
and QgsExpressionContextScope objects.

A QgsExpressionContextScope encapsulates the variables and functions
relating to a specific context. For instance, scopes can be created
for "global" variables (such as QGIS version, platform, and user-set
variables specified within the QGIS options dialog. Think things
like user name, work department, etc), or for "project" variables
(eg project path, title, filename, and user-set variables set
through the project properties dialog. Project version, reference
number, that kind of thing). Many more scopes are planned, including
map layer scopes (variables for layer name, id, user-set variables
through the layer properties dialog), composer scopes, etc...

QgsExpressionContextScopes are 'stacked' into a QgsExpressionContext
object. Scopes added later to a QgsExpressionContext will override
any variables or functions provided by earlier scopes, so for
instance a user could override their global 'author' variable set
within QGIS options with a different 'author' set via the project
properties dialog.

The intended use is that a QgsExpressionContext is created before
a batch set of QgsExpression evaluations. Scopes are then added to
the context based on what makes sense for that particular
expression. Eg, almost all contexts will consist of the global
scope and project scope, and then additional scopes as required.
So a composer label would be evaluated against a context
consisting of the global scope, project scope, composition scope
and finally composer item scope. The batch set of expression
evaluations would then be performed using this context, after which
the context is discarded. In other words, a context is designed
for use for one specific set of expression evaluations only.
This commit is contained in:
Nyall Dawson 2015-08-07 15:29:51 +10:00
parent f5de32779f
commit f6e0ce788e
15 changed files with 2526 additions and 253 deletions

View File

@ -37,6 +37,7 @@
%Include qgseditorwidgetconfig.sip
%Include qgserror.sip
%Include qgsexpression.sip
%Include qgsexpressioncontext.sip
%Include qgsfeature.sip
%Include qgsfeatureiterator.sip
%Include qgsfeaturerequest.sip

View File

@ -17,7 +17,13 @@ class QgsExpression
const QgsExpression::Node* rootNode() const;
//! Get the expression ready for evaluation - find out column indexes.
bool prepare( const QgsFields &fields );
bool prepare( const QgsFields &fields ) /Deprecated/;
/** Get the expression ready for evaluation - find out column indexes.
* @param context context for preparing expression
* @note added in QGIS 2.12
*/
bool prepare( const QgsExpressionContext *context );
/**
* Get list of columns referenced by the expression.
@ -34,28 +40,41 @@ class QgsExpression
//! Evaluate the feature and return the result
//! @note prepare() should be called before calling this method
QVariant evaluate( const QgsFeature* f = NULL );
QVariant evaluate( const QgsFeature* f ) /Deprecated/;
//! Evaluate the feature and return the result
//! @note prepare() should be called before calling this method
//! @note available in python bindings as evaluatePrepared
QVariant evaluate( const QgsFeature& f ) /PyName=evaluatePrepared/;
QVariant evaluate( const QgsFeature& f ) /PyName=evaluatePrepared,Deprecated/;
//! Evaluate the feature and return the result
//! @note this method does not expect that prepare() has been called on this instance
QVariant evaluate( const QgsFeature* f, const QgsFields& fields );
QVariant evaluate( const QgsFeature* f, const QgsFields& fields ) /Deprecated/;
//! Evaluate the feature and return the result
//! @note this method does not expect that prepare() has been called on this instance
//! @note not available in python bindings
// inline QVariant evaluate( const QgsFeature& f, const QgsFields& fields ) { return evaluate( &f, fields ); }
/** Evaluate the feature and return the result.
* @note this method does not expect that prepare() has been called on this instance
* @note added in QGIS 2.12
*/
QVariant evaluate();
/** Evaluate the expression against the specified context and return the result.
* @param context context for evaluating expression
* @note prepare() should be called before calling this method.
* @note added in QGIS 2.12
*/
QVariant evaluate( const QgsExpressionContext* context );
//! Returns true if an error occurred when evaluating last input
bool hasEvalError() const;
//! Returns evaluation error
QString evalErrorString() const;
//! Set evaluation error (used internally by evaluation functions)
void setEvalErrorString( QString str );
void setEvalErrorString( const QString& str );
//! Set the number for $rownum special column
void setCurrentRowNumber( int rowNumber );
@ -77,7 +96,16 @@ class QgsExpression
*/
bool isField() const;
static bool isValid( const QString& text, const QgsFields& fields, QString &errorMessage );
static bool isValid( const QString& text, const QgsFields& fields, QString &errorMessage ) /Deprecated/;
/** Tests whether a string is a valid expression.
* @param text string to test
* @param context optional expression context
* @param errorMessage will be filled with any error message from the validation
* @returns true if string is a valid expression
* @note added in QGIS 2.12
*/
static bool isValid( const QString& text, const QgsExpressionContext* context, QString &errorMessage );
void setScale( double scale );
@ -114,7 +142,23 @@ class QgsExpression
QgsVectorLayer *layer,
const QMap<QString, QVariant> *substitutionMap = 0,
const QgsDistanceArea* distanceArea = 0
);
) /Deprecated/;
/** This function replaces each expression between [% and %]
in the string with the result of its evaluation with the specified context
Additional substitutions can be passed through the substitutionMap parameter
@param action
@param context expression context
@param substitutionMap
@param distanceArea optional QgsDistanceArea. If specified, the QgsDistanceArea is used for distance
and area conversion
@note added in QGIS 2.12
*/
static QString replaceExpressionText( const QString &action, const QgsExpressionContext* context,
const QMap<QString, QVariant> *substitutionMap = 0,
const QgsDistanceArea* distanceArea = 0
);
/** Attempts to evaluate a text string as an expression to a resultant double
* value.
@ -122,6 +166,8 @@ class QgsExpression
* @param fallbackValue value to return if text can not be evaluated as a double
* @returns evaluated double value, or fallback value
* @note added in QGIS 2.7
* @note this method is inefficient for bulk evaluation of expressions, it is intended
* for one-off evaluations only.
*/
static double evaluateToDouble( const QString& text, const double fallbackValue );
@ -231,7 +277,15 @@ class QgsExpression
/** The help text for the function. */
const QString helptext();
virtual QVariant func( const QVariantList& values, const QgsFeature* f, QgsExpression* parent ) = 0;
virtual QVariant func( const QVariantList& values, const QgsFeature* f, QgsExpression* parent ) /Deprecated/;
/** Returns result of evaluating the function.
* @param values list of values passed to the function
* @param context context expression is being evaluated against
* @param parent parent expression
* @returns result of function
*/
virtual QVariant func( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent );
virtual bool handlesNull() const;
};
@ -243,7 +297,7 @@ class QgsExpression
static bool unregisterFunction( QString name );
// tells whether the identifier is a name of existing function
static bool isFunctionName( QString name );
static bool isFunctionName( const QString& name );
// return index of the function in Functions array
static int functionIndex( const QString& name );
@ -297,11 +351,21 @@ class QgsExpression
virtual QgsExpression::NodeType nodeType() const = 0;
// abstract virtual eval function
// errors are reported to the parent
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f ) = 0;
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f ) /Deprecated/;
/** Evaluation function for nodes. Errors are reported to the parent.
* @note added in QGIS 2.12
*/
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context );
// abstract virtual preparation function
// errors are reported to the parent
virtual bool prepare( QgsExpression* parent, const QgsFields &fields ) = 0;
virtual bool prepare( QgsExpression* parent, const QgsFields &fields ) /Deprecated/;
/** Preparation function for nodes. Errors are reported to the parent.
* @note added in QGIS 2.12
*/
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context );
virtual QString dump() const = 0;
@ -357,8 +421,8 @@ class QgsExpression
QgsExpression::Node* operand() const;
virtual QgsExpression::NodeType nodeType() const;
virtual bool prepare( QgsExpression* parent, const QgsFields &fields );
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f );
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context );
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context );
virtual QString dump() const;
virtual QStringList referencedColumns() const;
@ -377,8 +441,8 @@ class QgsExpression
QgsExpression::Node* opRight() const;
virtual QgsExpression::NodeType nodeType() const;
virtual bool prepare( QgsExpression* parent, const QgsFields &fields );
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f );
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context );
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context );
virtual QString dump() const;
virtual QStringList referencedColumns() const;
@ -400,8 +464,8 @@ class QgsExpression
QgsExpression::NodeList* list() const;
virtual QgsExpression::NodeType nodeType() const;
virtual bool prepare( QgsExpression* parent, const QgsFields &fields );
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f );
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context );
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context );
virtual QString dump() const;
virtual QStringList referencedColumns() const;
@ -420,8 +484,8 @@ class QgsExpression
QgsExpression::NodeList* args() const;
virtual QgsExpression::NodeType nodeType() const;
virtual bool prepare( QgsExpression* parent, const QgsFields &fields );
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f );
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context );
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context );
virtual QString dump() const;
virtual QStringList referencedColumns() const;
@ -437,8 +501,8 @@ class QgsExpression
const QVariant& value() const;
virtual QgsExpression::NodeType nodeType() const;
virtual bool prepare( QgsExpression* parent, const QgsFields &fields );
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f );
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context );
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context );
virtual QString dump() const;
virtual QStringList referencedColumns() const;
@ -454,8 +518,8 @@ class QgsExpression
QString name() const;
virtual QgsExpression::NodeType nodeType() const;
virtual bool prepare( QgsExpression* parent, const QgsFields &fields );
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f );
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context );
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context );
virtual QString dump() const;
virtual QStringList referencedColumns() const;
@ -482,8 +546,8 @@ class QgsExpression
~NodeCondition();
virtual QgsExpression::NodeType nodeType() const;
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f );
virtual bool prepare( QgsExpression* parent, const QgsFields &fields );
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context );
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context );
virtual QString dump() const;
virtual QStringList referencedColumns() const;

View File

@ -0,0 +1,392 @@
/** \ingroup core
* \class QgsScopedExpressionFunction
* \brief Expression function for use within a QgsExpressionContextScope. This differs from a
* standard QgsExpression::Function in that it requires an implemented
* clone() method.
* \note added in QGIS 2.12
*/
class QgsScopedExpressionFunction : QgsExpression::Function
{
%TypeHeaderCode
#include <qgsexpressioncontext.h>
%End
public:
QgsScopedExpressionFunction( QString fnname,
int params,
QString group,
QString helpText = QString(),
bool usesGeometry = false,
QStringList referencedColumns = QStringList(),
bool lazyEval = false,
bool handlesNull = false );
virtual ~QgsScopedExpressionFunction();
virtual QVariant func( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent ) = 0;
/** Returns a clone of the function.
*/
virtual QgsScopedExpressionFunction* clone() const = 0 /Factory/;
};
/** \ingroup core
* \class QgsExpressionContextScope
* \brief Single scope for storing variables and functions for use within a QgsExpressionContext.
* Examples include a project's scope, which could contain information about the current project such as
* the project file's location. QgsExpressionContextScope can encapsulate both variables (static values)
* and functions(which are calculated only when an expression is evaluated).
* \note added in QGIS 2.12
*/
class QgsExpressionContextScope
{
%TypeHeaderCode
#include <qgsexpressioncontext.h>
%End
public:
/** Single variable definition for use within a QgsExpressionContextScope.
*/
struct StaticVariable
{
/** Constructor for StaticVariable.
* @param name variable name (should be unique within the QgsExpressionContextScope)
* @param value intial variable value
* @param readOnly true if variable should not be editable by users
*/
StaticVariable( const QString& name = QString(), const QVariant& value = QVariant(), bool readOnly = false );
/** Variable name */
QString name;
/** Variable value */
QVariant value;
/** True if variable should not be editable by users */
bool readOnly;
};
/** Constructor for QgsExpressionContextScope
* @param name friendly display name for the context scope
*/
QgsExpressionContextScope( const QString& name = QString() );
QgsExpressionContextScope( const QgsExpressionContextScope& other );
~QgsExpressionContextScope();
/** Returns the friendly display name of the context scope.
*/
QString name() const;
/** Convenience method for setting a variable in the context scope by name and value. If a variable
* with the same name is already set then its value is overwritten, otherwise a new variable is added to the scope.
* @param name variable name
* @param value variable value
* @see addVariable()
*/
void setVariable( const QString& name, const QVariant& value );
/** Adds a variable into the context scope. If a variable with the same name is already set then its
* value is overwritten, otherwise a new variable is added to the scope.
* @param variable definition of variable to insert
* @see setVariable()
* @see addFunction()
*/
void addVariable( const QgsExpressionContextScope::StaticVariable& variable );
/** Removes a variable from the context scope, if found.
* @param name name of variable to remove
* @returns true if variable was removed from the scope, false if matching variable was not
* found within the scope
*/
bool removeVariable( const QString& name );
/** Tests whether a variable with the specified name exists in the scope.
* @param name variable name
* @returns true if matching variable was found in the scope
* @see variable()
* @see hasFunction()
*/
bool hasVariable( const QString& name ) const;
/** Retrieves a variable's value from the scope.
* @param name variable name
* @returns variable value, or invalid QVariant if matching variable could not be found
* @see hasVariable()
* @see function()
*/
QVariant variable( const QString& name ) const;
/** Returns a list of variable names contained within the scope.
*/
QStringList variableNames() const;
/** Tests whether the specified variable is read only and should not be editable
* by users.
* @param name variable name
* @returns true if variable is read only
*/
bool isReadOnly( const QString& name ) const;
/** Returns the count of variables contained within the scope.
*/
int variableCount() const;
/** Tests whether a function with the specified name exists in the scope.
* @param name function name
* @returns true if matching function was found in the scope
* @see function()
* @see hasFunction()
*/
bool hasFunction( const QString &name ) const;
/** Retrieves a function from the scope.
* @param name function name
* @returns function, or null if matching function could not be found
* @see hasFunction()
* @see variable()
*/
QgsExpression::Function* function( const QString &name ) const;
/** Adds a function to the scope.
* @param name function name
* @param function function to insert. Ownership is transferred to the scope.
* @see addVariable()
*/
void addFunction( const QString& name, QgsScopedExpressionFunction* function /Transfer/ );
/** Convenience function for setting a feature for the scope. Any existing
* feature set by the scope will be overwritten.
* @param feature feature for scope
*/
void setFeature( const QgsFeature& feature );
/** Convenience function for setting a fields for the scope. Any existing
* fields set by the scope will be overwritten.
* @param fields fields for scope
*/
void setFields( const QgsFields& fields );
};
/** \ingroup core
* \class QgsExpressionContext
* \brief Expression contexts are used to encapsulate the parameters around which a QgsExpression should
* be evaluated. QgsExpressions can then utilise the information stored within a context to contextualise
* their evaluated result. A QgsExpressionContext consists of a stack of QgsExpressionContextScope objects,
* where scopes added later to the stack will override conflicting variables and functions from scopes
* lower in the stack.
* \note added in QGIS 2.12
*/
class QgsExpressionContext
{
%TypeHeaderCode
#include <qgsexpressioncontext.h>
%End
public:
QgsExpressionContext( );
QgsExpressionContext( const QgsExpressionContext& other );
~QgsExpressionContext();
/** Check whether a variable is specified by any scope within the context.
* @param name variable name
* @returns true if variable is set
* @see variable()
* @see variableNames()
*/
bool hasVariable( const QString& name ) const;
/** Fetches a matching variable from the context. The variable will be fetched
* from the last scope contained within the context which has a matching
* variable set.
* @param name variable name
* @returns variable value if matching variable exists in the context, otherwise an invalid QVariant
* @see hasVariable()
* @see variableNames()
*/
QVariant variable( const QString& name ) const;
/** Returns the currently active scope from the context for a specified variable name.
* As scopes later in the stack override earlier contexts, this will be the last matching
* scope which contains a matching variable.
* @param name variable name
* @returns matching scope containing variable, or null if none found
*/
QgsExpressionContextScope* activeScopeForVariable( const QString& name );
/** Returns the currently active scope from the context for a specified variable name.
* As scopes later in the stack override earlier contexts, this will be the last matching
* scope which contains a matching variable.
* @param name variable name
* @returns matching scope containing variable, or null if none found
*/
//const QgsExpressionContextScope* activeScopeForVariable( const QString& name ) const;
/** Returns the scope at the specified index within the context.
* @param index index of scope
* @returns matching scope, or null if none found
* @see lastScope()
*/
QgsExpressionContextScope* scope( int index );
/** Returns the last scope added to the context.
* @see scope()
*/
QgsExpressionContextScope* lastScope();
/** Returns a list of scopes contained within the stack.
* @returns list of pointers to scopes
*/
QList< QgsExpressionContextScope* > scopes();
/** Returns the index of the specified scope if it exists within the context.
* @param scope scope to find
* @returns index of scope, or -1 if scope was not found within the context.
*/
int indexOfScope( QgsExpressionContextScope* scope ) const;
/** Returns a list of variables names set by all scopes in the context.
* @returns list of unique variable names
* @see hasVariable
* @see variable
*/
QStringList variableNames() const;
/** Returns whether a variable is read only, and should not be modifiable by users.
* @param name variable name
* @returns true if variable is read only. Read only status will be taken from last
* matching scope which contains a matching variable.
*/
bool isReadOnly( const QString& name ) const;
/** Checks whether a specified function is contained in the context.
* @param name function name
* @returns true if context provides a matching function
* @see function
*/
bool hasFunction( const QString& name ) const;
/** Fetches a matching function from the context. The function will be fetched
* from the last scope contained within the context which has a matching
* function set.
* @param name function name
* @returns function if contained by the context, otherwise null.
* @see hasFunction
*/
QgsExpression::Function* function( const QString& name ) const;
/** Returns the number of scopes contained in the context.
*/
int scopeCount() const;
/** Appends a scope to the end of the context. This scope will override
* any matching variables or functions provided by existing scopes within the
* context. Ownership of the scope is transferred to the stack.
* @param scope expression context to append to context
*/
void appendScope( QgsExpressionContextScope* scope /Transfer/ );
/** Appends a scope to the end of the context. This scope will override
* any matching variables or functions provided by existing scopes within the
* context. Ownership of the scope is transferred to the stack.
*/
QgsExpressionContext& operator<< ( QgsExpressionContextScope* scope /Transfer/ );
/** Convenience function for setting a feature for the context. The feature
* will be set within the last scope of the context, so will override any
* existing features within the context.
* @param feature feature for context
*/
void setFeature( const QgsFeature& feature );
/** Convenience function for setting a fields for the context. The fields
* will be set within the last scope of the context, so will override any
* existing fields within the context.
* @param fields fields for context
*/
void setFields( const QgsFields& fields );
};
/** \ingroup core
* \class QgsExpressionContextUtils
* \brief Contains utilities for working with QgsExpressionContext objects, including methods
* for creating scopes for specific uses (eg project scopes, layer scopes).
* \note added in QGIS 2.12
*/
class QgsExpressionContextUtils
{
%TypeHeaderCode
#include <qgsexpressioncontext.h>
%End
public:
/** Creates a new scope which contains variables and functions relating to the global QGIS context.
* For instance, QGIS version numbers and variables specified through QGIS options.
* @see setGlobalVariable()
*/
static QgsExpressionContextScope* globalScope() /Factory/;
/** Sets a global context variable. This variable will be contained within scopes retrieved via
* globalScope().
* @param name variable name
* @param value variable value
* @see setGlobalVariable()
* @see globalScope()
*/
static void setGlobalVariable( const QString& name, const QVariant& value );
/** Sets all global context variables. Existing global variables will be removed and replaced
* with the variables specified.
* @param variables new set of global variables
* @see setGlobalVariable()
* @see globalScope()
*/
static void setGlobalVariables( const QgsStringMap& variables );
/** Creates a new scope which contains variables and functions relating to the current QGIS project.
* For instance, project path and title, and variables specified through the project properties.
* @see setProjectVariable()
*/
static QgsExpressionContextScope* projectScope() /Factory/;
/** Sets a project context variable. This variable will be contained within scopes retrieved via
* projectScope().
* @param name variable name
* @param value variable value
* @see setProjectVariables()
* @see projectScope()
*/
static void setProjectVariable( const QString& name, const QVariant& value );
/** Sets all project context variables. Existing project variables will be removed and replaced
* with the variables specified.
* @param variables new set of project variables
* @see setProjectVariable()
* @see projectScope()
*/
static void setProjectVariables( const QgsStringMap& variables );
/** Creates a new scope which contains variables and functions relating to a QgsMapLayer.
* For instance, layer name, id and fields.
*/
static QgsExpressionContextScope* layerScope( QgsMapLayer* layer ) /Factory/;
/** Helper function for creating an expression context which contains just a feature and fields
* collection. Generally this method should not be used as the created context does not include
* standard scopes such as the global and project scopes.
*/
static QgsExpressionContext createFeatureBasedContext( const QgsFeature& feature, const QgsFields& fields );
/** Registers all known core functions provided by QgsExpressionContextScope objects.
*/
static void registerContextFunctions();
};

View File

@ -75,6 +75,11 @@ class QgsProject : QObject
QString fileName() const;
//@}
/** Returns QFileInfo object for the project's associated file.
* @note added in QGIS 2.9
*/
QFileInfo fileInfo() const;
/** Clear the project
* @note added in 2.4
*/

View File

@ -94,6 +94,7 @@ SET(QGIS_CORE_SRCS
qgsdistancearea.cpp
qgserror.cpp
qgsexpression.cpp
qgsexpressioncontext.cpp
qgsexpression_texts.cpp
qgsexpressionfieldbuffer.cpp
qgsfeature.cpp
@ -537,6 +538,7 @@ SET(QGIS_CORE_HDRS
qgserror.h
qgsexception.h
qgsexpression.h
qgsexpressioncontext.h
qgsexpressionfieldbuffer.h
qgsfeature.h
qgsfeature_p.h

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,7 @@
#include <QVariant>
#include <QList>
#include <QDomDocument>
#include "qgis.h"
class QgsFeature;
class QgsGeometry;
@ -31,6 +32,7 @@ class QgsField;
class QgsFields;
class QgsDistanceArea;
class QDomElement;
class QgsExpressionContext;
/**
Class for parsing and evaluation of expressions (formerly called "search strings").
@ -100,7 +102,13 @@ class CORE_EXPORT QgsExpression
const Node* rootNode() const { return mRootNode; }
//! Get the expression ready for evaluation - find out column indexes.
bool prepare( const QgsFields &fields );
Q_DECL_DEPRECATED bool prepare( const QgsFields &fields );
/** Get the expression ready for evaluation - find out column indexes.
* @param context context for preparing expression
* @note added in QGIS 2.12
*/
bool prepare( const QgsExpressionContext *context );
/**
* Get list of columns referenced by the expression.
@ -117,28 +125,41 @@ class CORE_EXPORT QgsExpression
//! Evaluate the feature and return the result
//! @note prepare() should be called before calling this method
QVariant evaluate( const QgsFeature* f = NULL );
Q_DECL_DEPRECATED QVariant evaluate( const QgsFeature* f );
//! Evaluate the feature and return the result
//! @note prepare() should be called before calling this method
//! @note available in python bindings as evaluatePrepared
inline QVariant evaluate( const QgsFeature& f ) { return evaluate( &f ); }
Q_DECL_DEPRECATED inline QVariant evaluate( const QgsFeature& f ) { Q_NOWARN_DEPRECATED_PUSH return evaluate( &f ); Q_NOWARN_DEPRECATED_POP }
//! Evaluate the feature and return the result
//! @note this method does not expect that prepare() has been called on this instance
QVariant evaluate( const QgsFeature* f, const QgsFields& fields );
Q_DECL_DEPRECATED QVariant evaluate( const QgsFeature* f, const QgsFields& fields );
//! Evaluate the feature and return the result
//! @note this method does not expect that prepare() has been called on this instance
//! @note not available in python bindings
inline QVariant evaluate( const QgsFeature& f, const QgsFields& fields ) { return evaluate( &f, fields ); }
Q_DECL_DEPRECATED inline QVariant evaluate( const QgsFeature& f, const QgsFields& fields );
/** Evaluate the feature and return the result.
* @note this method does not expect that prepare() has been called on this instance
* @note added in QGIS 2.12
*/
QVariant evaluate();
/** Evaluate the expression against the specified context and return the result.
* @param context context for evaluating expression
* @note prepare() should be called before calling this method.
* @note added in QGIS 2.12
*/
QVariant evaluate( const QgsExpressionContext* context );
//! Returns true if an error occurred when evaluating last input
bool hasEvalError() const { return !mEvalErrorString.isNull(); }
//! Returns evaluation error
QString evalErrorString() const { return mEvalErrorString; }
//! Set evaluation error (used internally by evaluation functions)
void setEvalErrorString( QString str ) { mEvalErrorString = str; }
void setEvalErrorString( const QString& str ) { mEvalErrorString = str; }
//! Set the number for $rownum special column
void setCurrentRowNumber( int rowNumber ) { mRowNumber = rowNumber; }
@ -160,7 +181,16 @@ class CORE_EXPORT QgsExpression
*/
bool isField() const { return rootNode() && dynamic_cast<const NodeColumnRef*>( rootNode() ) ;}
static bool isValid( const QString& text, const QgsFields& fields, QString &errorMessage );
Q_DECL_DEPRECATED static bool isValid( const QString& text, const QgsFields& fields, QString &errorMessage );
/** Tests whether a string is a valid expression.
* @param text string to test
* @param context optional expression context
* @param errorMessage will be filled with any error message from the validation
* @returns true if string is a valid expression
* @note added in QGIS 2.12
*/
static bool isValid( const QString& text, const QgsExpressionContext* context, QString &errorMessage );
void setScale( double scale ) { mScale = scale; }
@ -199,8 +229,24 @@ class CORE_EXPORT QgsExpression
@param distanceArea optional QgsDistanceArea. If specified, the QgsDistanceArea is used for distance
and area conversion
*/
static QString replaceExpressionText( const QString &action, const QgsFeature *feat,
QgsVectorLayer *layer,
Q_DECL_DEPRECATED static QString replaceExpressionText( const QString &action, const QgsFeature *feat,
QgsVectorLayer *layer,
const QMap<QString, QVariant> *substitutionMap = 0,
const QgsDistanceArea* distanceArea = 0
);
/** This function replaces each expression between [% and %]
in the string with the result of its evaluation with the specified context
Additional substitutions can be passed through the substitutionMap parameter
@param action
@param context expression context
@param substitutionMap
@param distanceArea optional QgsDistanceArea. If specified, the QgsDistanceArea is used for distance
and area conversion
@note added in QGIS 2.12
*/
static QString replaceExpressionText( const QString &action, const QgsExpressionContext* context,
const QMap<QString, QVariant> *substitutionMap = 0,
const QgsDistanceArea* distanceArea = 0
);
@ -211,6 +257,8 @@ class CORE_EXPORT QgsExpression
* @param fallbackValue value to return if text can not be evaluated as a double
* @returns evaluated double value, or fallback value
* @note added in QGIS 2.7
* @note this method is inefficient for bulk evaluation of expressions, it is intended
* for one-off evaluations only.
*/
static double evaluateToDouble( const QString& text, const double fallbackValue );
@ -278,7 +326,7 @@ class CORE_EXPORT QgsExpression
static const char* UnaryOperatorText[];
typedef QVariant( *FcnEval )( const QVariantList& values, const QgsFeature* f, QgsExpression* parent );
typedef QVariant( *FcnEvalContext )( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent );
/**
* A abstract base class for defining QgsExpression functions.
@ -332,7 +380,15 @@ class CORE_EXPORT QgsExpression
/** The help text for the function. */
const QString helptext() { return mHelpText.isEmpty() ? QgsExpression::helptext( mName ) : mHelpText; }
virtual QVariant func( const QVariantList& values, const QgsFeature* f, QgsExpression* parent ) = 0;
Q_DECL_DEPRECATED virtual QVariant func( const QVariantList&, const QgsFeature*, QgsExpression* );
/** Returns result of evaluating the function.
* @param values list of values passed to the function
* @param context context expression is being evaluated against
* @param parent parent expression
* @returns result of function
*/
virtual QVariant func( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent );
bool operator==( const Function& other ) const
{
@ -358,9 +414,28 @@ class CORE_EXPORT QgsExpression
class StaticFunction : public Function
{
public:
Q_DECL_DEPRECATED StaticFunction( QString fnname,
int params,
FcnEval fcn,
QString group,
QString helpText = QString(),
bool usesGeometry = false,
QStringList referencedColumns = QStringList(),
bool lazyEval = false,
const QStringList& aliases = QStringList(),
bool handlesNull = false )
: Function( fnname, params, group, helpText, usesGeometry, referencedColumns, lazyEval, handlesNull )
, mFnc( fcn )
, mAliases( aliases )
{}
virtual ~StaticFunction() {}
/** Static function for evaluation against a QgsExpressionContext
*/
StaticFunction( QString fnname,
int params,
FcnEval fcn,
FcnEvalContext fcn,
QString group,
QString helpText = QString(),
bool usesGeometry = false,
@ -369,21 +444,32 @@ class CORE_EXPORT QgsExpression
const QStringList& aliases = QStringList(),
bool handlesNull = false )
: Function( fnname, params, group, helpText, usesGeometry, referencedColumns, lazyEval, handlesNull )
, mFnc( fcn )
, mContextFnc( fcn )
, mAliases( aliases )
{}
virtual ~StaticFunction() {}
virtual QVariant func( const QVariantList& values, const QgsFeature* f, QgsExpression* parent ) override
Q_DECL_DEPRECATED virtual QVariant func( const QVariantList& values, const QgsFeature* f, QgsExpression* parent ) override
{
Q_NOWARN_DEPRECATED_PUSH
return mFnc( values, f, parent );
Q_NOWARN_DEPRECATED_POP
}
/** Returns result of evaluating the function.
* @param values list of values passed to the function
* @param context context expression is being evaluated against
* @param parent parent expression
* @returns result of function
*/
virtual QVariant func( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent ) override
{
return mContextFnc( values, context, parent );
}
virtual QStringList aliases() const override { return mAliases; }
private:
FcnEval mFnc;
FcnEvalContext mContextFnc;
QStringList mAliases;
};
@ -397,7 +483,7 @@ class CORE_EXPORT QgsExpression
static bool unregisterFunction( QString name );
// tells whether the identifier is a name of existing function
static bool isFunctionName( QString name );
static bool isFunctionName( const QString& name );
// return index of the function in Functions array
static int functionIndex( const QString& name );
@ -443,17 +529,32 @@ class CORE_EXPORT QgsExpression
* @return The type of this node
*/
virtual NodeType nodeType() const = 0;
/**
/**
* Abstract virtual eval method
* Errors are reported to the parent
*/
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f ) = 0;
Q_DECL_DEPRECATED virtual QVariant eval( QgsExpression* parent, const QgsFeature* f );
/**
* Abstract virtual eval method
* Errors are reported to the parent
* @note added in QGIS 2.12
*/
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context );
/**
* Abstract virtual preparation method
* Errors are reported to the parent
*/
virtual bool prepare( QgsExpression* parent, const QgsFields &fields ) = 0;
Q_DECL_DEPRECATED virtual bool prepare( QgsExpression* parent, const QgsFields &fields );
/**
* Abstract virtual preparation method
* Errors are reported to the parent
* @note added in QGIS 2.12
*/
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context );
/**
* Abstract virtual dump method
@ -558,8 +659,8 @@ class CORE_EXPORT QgsExpression
Node* operand() const { return mOperand; }
virtual NodeType nodeType() const override { return ntUnaryOperator; }
virtual bool prepare( QgsExpression* parent, const QgsFields &fields ) override;
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f ) override;
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QString dump() const override;
virtual QStringList referencedColumns() const override { return mOperand->referencedColumns(); }
@ -582,8 +683,8 @@ class CORE_EXPORT QgsExpression
Node* opRight() const { return mOpRight; }
virtual NodeType nodeType() const override { return ntBinaryOperator; }
virtual bool prepare( QgsExpression* parent, const QgsFields &fields ) override;
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f ) override;
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QString dump() const override;
virtual QStringList referencedColumns() const override { return mOpLeft->referencedColumns() + mOpRight->referencedColumns(); }
@ -615,8 +716,8 @@ class CORE_EXPORT QgsExpression
NodeList* list() const { return mList; }
virtual NodeType nodeType() const override { return ntInOperator; }
virtual bool prepare( QgsExpression* parent, const QgsFields &fields ) override;
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f ) override;
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QString dump() const override;
virtual QStringList referencedColumns() const override { QStringList lst( mNode->referencedColumns() ); foreach ( Node* n, mList->list() ) lst.append( n->referencedColumns() ); return lst; }
@ -640,8 +741,8 @@ class CORE_EXPORT QgsExpression
NodeList* args() const { return mArgs; }
virtual NodeType nodeType() const override { return ntFunction; }
virtual bool prepare( QgsExpression* parent, const QgsFields &fields ) override;
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f ) override;
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QString dump() const override;
virtual QStringList referencedColumns() const override;
@ -652,6 +753,10 @@ class CORE_EXPORT QgsExpression
//QString mName;
int mFnIndex;
NodeList* mArgs;
private:
QgsExpression::Function* getFunc() const;
};
class CORE_EXPORT NodeLiteral : public Node
@ -662,8 +767,8 @@ class CORE_EXPORT QgsExpression
inline QVariant value() const { return mValue; }
virtual NodeType nodeType() const override { return ntLiteral; }
virtual bool prepare( QgsExpression* parent, const QgsFields &fields ) override;
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f ) override;
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QString dump() const override;
virtual QStringList referencedColumns() const override { return QStringList(); }
@ -682,8 +787,8 @@ class CORE_EXPORT QgsExpression
QString name() const { return mName; }
virtual NodeType nodeType() const override { return ntColumnRef; }
virtual bool prepare( QgsExpression* parent, const QgsFields &fields ) override;
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f ) override;
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QString dump() const override;
virtual QStringList referencedColumns() const override { return QStringList( mName ); }
@ -715,8 +820,8 @@ class CORE_EXPORT QgsExpression
~NodeCondition() { delete mElseExp; qDeleteAll( mConditions ); }
virtual NodeType nodeType() const override { return ntCondition; }
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f ) override;
virtual bool prepare( QgsExpression* parent, const QgsFields &fields ) override;
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual bool prepare( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QString dump() const override;
virtual QStringList referencedColumns() const override;

View File

@ -0,0 +1,523 @@
/***************************************************************************
qgsexpressioncontext.cpp
------------------------
Date : April 2015
Copyright : (C) 2015 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsexpressioncontext.h"
#include "qgslogger.h"
#include "qgsexpression.h"
#include "qgsfield.h"
#include "qgsvectorlayer.h"
#include "qgsproject.h"
#include "qgssymbollayerv2utils.h"
#include <QSettings>
#include <QDir>
const QString QgsExpressionContext::EXPR_FIELDS( "_fields_" );
const QString QgsExpressionContext::EXPR_FEATURE( "_feature_" );
//
// QgsExpressionContextScope
//
QgsExpressionContextScope::QgsExpressionContextScope( const QString& name )
: mName( name )
{
}
QgsExpressionContextScope::QgsExpressionContextScope( const QgsExpressionContextScope& other )
: mName( other.mName )
, mVariables( other.mVariables )
{
Q_FOREACH ( QString key, other.mFunctions.keys() )
{
mFunctions.insert( key, other.mFunctions.value( key )->clone() );
}
}
QgsExpressionContextScope& QgsExpressionContextScope::operator=( const QgsExpressionContextScope & other )
{
mName = other.mName;
mVariables = other.mVariables;
qDeleteAll( mFunctions );
mFunctions.clear();
Q_FOREACH ( QString key, other.mFunctions.keys() )
{
mFunctions.insert( key, other.mFunctions.value( key )->clone() );
}
return *this;
}
QgsExpressionContextScope::~QgsExpressionContextScope()
{
qDeleteAll( mFunctions );
}
void QgsExpressionContextScope::setVariable( const QString &name, const QVariant &value )
{
if ( mVariables.contains( name ) )
{
StaticVariable existing = mVariables.value( name );
existing.value = value;
addVariable( existing );
}
else
{
addVariable( QgsExpressionContextScope::StaticVariable( name, value ) );
}
}
void QgsExpressionContextScope::addVariable( const QgsExpressionContextScope::StaticVariable &variable )
{
mVariables.insert( variable.name, variable );
}
bool QgsExpressionContextScope::removeVariable( const QString &name )
{
return mVariables.remove( name ) > 0;
}
bool QgsExpressionContextScope::hasVariable( const QString &name ) const
{
return mVariables.contains( name );
}
QVariant QgsExpressionContextScope::variable( const QString &name ) const
{
return hasVariable( name ) ? mVariables.value( name ).value : QVariant();
}
QStringList QgsExpressionContextScope::variableNames() const
{
QStringList names = mVariables.keys();
return names;
}
bool QgsExpressionContextScope::isReadOnly( const QString &name ) const
{
return hasVariable( name ) ? mVariables.value( name ).readOnly : false;
}
bool QgsExpressionContextScope::hasFunction( const QString& name ) const
{
return mFunctions.contains( name );
}
QgsExpression::Function* QgsExpressionContextScope::function( const QString& name ) const
{
return mFunctions.contains( name ) ? mFunctions.value( name ) : 0;
}
void QgsExpressionContextScope::addFunction( const QString& name, QgsScopedExpressionFunction* function )
{
mFunctions.insert( name, function );
}
void QgsExpressionContextScope::setFeature( const QgsFeature &feature )
{
setVariable( QgsExpressionContext::EXPR_FEATURE, QVariant::fromValue( feature ) );
}
void QgsExpressionContextScope::setFields( const QgsFields &fields )
{
setVariable( QgsExpressionContext::EXPR_FIELDS, QVariant::fromValue( fields ) );
}
//
// QgsExpressionContext
//
QgsExpressionContext::QgsExpressionContext( const QgsExpressionContext& other )
{
Q_FOREACH ( const QgsExpressionContextScope* scope, other.mStack )
{
mStack << new QgsExpressionContextScope( *scope );
}
}
QgsExpressionContext& QgsExpressionContext::operator=( const QgsExpressionContext & other )
{
qDeleteAll( mStack );
mStack.clear();
Q_FOREACH ( const QgsExpressionContextScope* scope, other.mStack )
{
mStack << new QgsExpressionContextScope( *scope );
}
return *this;
}
QgsExpressionContext::~QgsExpressionContext()
{
qDeleteAll( mStack );
mStack.clear();
}
bool QgsExpressionContext::hasVariable( const QString& name ) const
{
Q_FOREACH ( const QgsExpressionContextScope* scope, mStack )
{
if ( scope->hasVariable( name ) )
return true;
}
return false;
}
QVariant QgsExpressionContext::variable( const QString& name ) const
{
const QgsExpressionContextScope* scope = activeScopeForVariable( name );
return scope ? scope->variable( name ) : QVariant();
}
const QgsExpressionContextScope* QgsExpressionContext::activeScopeForVariable( const QString& name ) const
{
//iterate through stack backwards, so that higher priority variables take precedence
QList< QgsExpressionContextScope* >::const_iterator it = mStack.constEnd();
while ( it != mStack.constBegin() )
{
--it;
if (( *it )->hasVariable( name ) )
return ( *it );
}
return 0;
}
QgsExpressionContextScope* QgsExpressionContext::activeScopeForVariable( const QString& name )
{
//iterate through stack backwards, so that higher priority variables take precedence
QList< QgsExpressionContextScope* >::iterator it = mStack.end();
while ( it != mStack.begin() )
{
--it;
if (( *it )->hasVariable( name ) )
return ( *it );
}
return 0;
}
QgsExpressionContextScope* QgsExpressionContext::scope( int index )
{
if ( index < 0 || index >= mStack.count() )
return 0;
return mStack[index];
}
QgsExpressionContextScope *QgsExpressionContext::lastScope()
{
if ( mStack.count() < 1 )
return 0;
return mStack.last();
}
int QgsExpressionContext::indexOfScope( QgsExpressionContextScope* scope ) const
{
if ( !scope )
return -1;
return mStack.indexOf( scope );
}
QStringList QgsExpressionContext::variableNames() const
{
QStringList names;
Q_FOREACH ( const QgsExpressionContextScope* scope, mStack )
{
names << scope->variableNames();
}
return names.toSet().toList();
}
bool QgsExpressionContext::isReadOnly( const QString& name ) const { Q_UNUSED( name ); return true; }
bool QgsExpressionContext::hasFunction( const QString &name ) const
{
Q_FOREACH ( const QgsExpressionContextScope* scope, mStack )
{
if ( scope->hasFunction( name ) )
return true;
}
return false;
}
QgsExpression::Function *QgsExpressionContext::function( const QString &name ) const
{
//iterate through stack backwards, so that higher priority variables take precedence
QList< QgsExpressionContextScope* >::const_iterator it = mStack.constEnd();
while ( it != mStack.constBegin() )
{
--it;
if (( *it )->hasFunction( name ) )
return ( *it )->function( name );
}
return 0;
}
int QgsExpressionContext::scopeCount() const
{
return mStack.count();
}
void QgsExpressionContext::appendScope( QgsExpressionContextScope* scope )
{
mStack.append( scope );
}
QgsExpressionContext& QgsExpressionContext::operator<<( QgsExpressionContextScope* scope )
{
mStack.append( scope );
return *this;
}
void QgsExpressionContext::setFeature( const QgsFeature &feature )
{
if ( mStack.isEmpty() )
mStack.append( new QgsExpressionContextScope() );
mStack.last()->setFeature( feature );
}
void QgsExpressionContext::setFields( const QgsFields &fields )
{
if ( mStack.isEmpty() )
mStack.append( new QgsExpressionContextScope() );
mStack.last()->setFields( fields );
}
//
// QgsExpressionContextUtils
//
QgsExpressionContextScope* QgsExpressionContextUtils::globalScope()
{
QgsExpressionContextScope* scope = new QgsExpressionContextScope( QObject::tr( "Global" ) );
//read values from QSettings
QSettings settings;
//check if settings contains any variables
if ( settings.contains( QString( "/variables/values" ) ) )
{
QList< QVariant > customVariableVariants = settings.value( QString( "/variables/values" ) ).toList();
QList< QVariant > customVariableNames = settings.value( QString( "/variables/names" ) ).toList();
int variableIndex = 0;
for ( QList< QVariant >::const_iterator it = customVariableVariants.constBegin();
it != customVariableVariants.constEnd(); ++it )
{
if ( variableIndex >= customVariableNames.length() )
{
break;
}
QVariant value = ( *it );
QString name = customVariableNames.at( variableIndex ).toString();
scope->setVariable( name, value );
variableIndex++;
}
}
//add some extra global variables
scope->addVariable( QgsExpressionContextScope::StaticVariable( "qgis_version", QGis::QGIS_VERSION, true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "qgis_version_no", QGis::QGIS_VERSION_INT, true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "qgis_release_name", QGis::QGIS_RELEASE_NAME, true ) );
return scope;
}
void QgsExpressionContextUtils::setGlobalVariable( const QString& name, const QVariant& value )
{
// save variable to settings
QSettings settings;
QList< QVariant > customVariableVariants = settings.value( QString( "/variables/values" ) ).toList();
QList< QVariant > customVariableNames = settings.value( QString( "/variables/names" ) ).toList();
customVariableVariants << value;
customVariableNames << name;
settings.setValue( QString( "/variables/names" ), customVariableNames );
settings.setValue( QString( "/variables/values" ), customVariableVariants );
}
void QgsExpressionContextUtils::setGlobalVariables( const QgsStringMap &variables )
{
QSettings settings;
QList< QVariant > customVariableVariants;
QList< QVariant > customVariableNames;
Q_FOREACH ( QString variable, variables.keys() )
{
customVariableNames << variable;
customVariableVariants << variables.value( variable );
}
settings.setValue( QString( "/variables/names" ), customVariableNames );
settings.setValue( QString( "/variables/values" ), customVariableVariants );
}
class GetNamedProjectColor : public QgsScopedExpressionFunction
{
public:
GetNamedProjectColor()
: QgsScopedExpressionFunction( "project_color", 1, "Colors" )
{
//build up color list from project. Do this in advance for speed
QStringList colorStrings = QgsProject::instance()->readListEntry( "Palette", "/Colors" );
QStringList colorLabels = QgsProject::instance()->readListEntry( "Palette", "/Labels" );
//generate list from custom colors
int colorIndex = 0;
for ( QStringList::iterator it = colorStrings.begin();
it != colorStrings.end(); ++it )
{
QColor color = QgsSymbolLayerV2Utils::decodeColor( *it );
QString label;
if ( colorLabels.length() > colorIndex )
{
label = colorLabels.at( colorIndex );
}
mColors.insert( label.toLower(), color );
colorIndex++;
}
}
virtual QVariant func( const QVariantList& values, const QgsExpressionContext*, QgsExpression* ) override
{
QString colorName = values.at( 0 ).toString().toLower();
if ( mColors.contains( colorName ) )
{
return QString( "%1,%2,%3" ).arg( mColors.value( colorName ).red() ).arg( mColors.value( colorName ).green() ).arg( mColors.value( colorName ).blue() );
}
else
return QVariant();
}
QgsScopedExpressionFunction* clone() const override
{
return new GetNamedProjectColor();
}
private:
QHash< QString, QColor > mColors;
};
QgsExpressionContextScope* QgsExpressionContextUtils::projectScope()
{
QgsProject* project = QgsProject::instance();
QgsExpressionContextScope* scope = new QgsExpressionContextScope( QObject::tr( "Project" ) );
//add variables defined in project file
QStringList variableNames = project->readListEntry( "Variables", "/variableNames" );
QStringList variableValues = project->readListEntry( "Variables", "/variableValues" );
int varIndex = 0;
foreach ( QString variableName, variableNames )
{
if ( varIndex >= variableValues.length() )
{
break;
}
QString varValueString = variableValues.at( varIndex );
varIndex++;
scope->setVariable( variableName, varValueString );
}
//add other known project variables
scope->addVariable( QgsExpressionContextScope::StaticVariable( "project_title", project->title(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "project_path", project->fileInfo().filePath(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "project_folder", project->fileInfo().dir().path(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "project_filename", project->fileInfo().fileName(), true ) );
scope->addFunction( "project_color", new GetNamedProjectColor() );
return scope;
}
void QgsExpressionContextUtils::setProjectVariable( const QString& name, const QVariant& value )
{
QgsProject* project = QgsProject::instance();
//write variable to project
QStringList variableNames = project->readListEntry( "Variables", "/variableNames" );
QStringList variableValues = project->readListEntry( "Variables", "/variableValues" );
variableNames << name;
variableValues << value.toString();
project->writeEntry( "Variables", "/variableNames", variableNames );
project->writeEntry( "Variables", "/variableValues", variableValues );
}
void QgsExpressionContextUtils::setProjectVariables( const QgsStringMap &variables )
{
QgsProject* project = QgsProject::instance();
//write variable to project
QStringList variableNames;
QStringList variableValues;
Q_FOREACH ( QString variable, variables.keys() )
{
variableNames << variable;
variableValues << variables.value( variable );
}
project->writeEntry( "Variables", "/variableNames", variableNames );
project->writeEntry( "Variables", "/variableValues", variableValues );
}
QgsExpressionContextScope* QgsExpressionContextUtils::layerScope( QgsMapLayer* layer )
{
QgsExpressionContextScope* scope = new QgsExpressionContextScope( QObject::tr( "Layer" ) );
if ( !layer )
return scope;
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_name", layer->name(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_id", layer->id(), true ) );
QgsVectorLayer* vLayer = dynamic_cast< QgsVectorLayer* >( layer );
if ( vLayer )
{
scope->addVariable( QgsExpressionContextScope::StaticVariable( "_fields_", QVariant::fromValue( vLayer->pendingFields() ), true ) );
}
return scope;
}
QgsExpressionContext QgsExpressionContextUtils::createFeatureBasedContext( const QgsFeature &feature, const QgsFields &fields )
{
QgsExpressionContextScope* scope = new QgsExpressionContextScope();
scope->setVariable( QString( "_feature_" ), QVariant::fromValue( feature ) );
scope->setVariable( QString( "_fields_" ), QVariant::fromValue( fields ) );
return QgsExpressionContext() << scope;
}
void QgsExpressionContextUtils::registerContextFunctions()
{
QgsExpression::registerFunction( new GetNamedProjectColor() );
}

View File

@ -0,0 +1,426 @@
/***************************************************************************
qgsexpressioncontext.h
----------------------
Date : April 2015
Copyright : (C) 2015 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSEXPRESSIONCONTEXT_H
#define QGSEXPRESSIONCONTEXT_H
#include <QVariant>
#include <QHash>
#include <QString>
#include <QStringList>
#include <QSet>
#include "qgsexpression.h"
class QgsExpression;
class QgsMapLayer;
/** \ingroup core
* \class QgsScopedExpressionFunction
* \brief Expression function for use within a QgsExpressionContextScope. This differs from a
* standard QgsExpression::Function in that it requires an implemented
* clone() method.
* \note added in QGIS 2.12
*/
class CORE_EXPORT QgsScopedExpressionFunction : public QgsExpression::Function
{
public:
QgsScopedExpressionFunction( QString fnname,
int params,
QString group,
QString helpText = QString(),
bool usesGeometry = false,
QStringList referencedColumns = QStringList(),
bool lazyEval = false,
bool handlesNull = false )
: QgsExpression::Function( fnname, params, group, helpText, usesGeometry, referencedColumns, lazyEval, handlesNull )
{}
virtual ~QgsScopedExpressionFunction() {}
virtual QVariant func( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent ) override = 0;
/** Returns a clone of the function.
*/
virtual QgsScopedExpressionFunction* clone() const = 0;
};
/** \ingroup core
* \class QgsExpressionContextScope
* \brief Single scope for storing variables and functions for use within a QgsExpressionContext.
* Examples include a project's scope, which could contain information about the current project such as
* the project file's location. QgsExpressionContextScope can encapsulate both variables (static values)
* and functions(which are calculated only when an expression is evaluated).
* \note added in QGIS 2.12
*/
class CORE_EXPORT QgsExpressionContextScope
{
public:
/** Single variable definition for use within a QgsExpressionContextScope.
*/
struct StaticVariable
{
/** Constructor for StaticVariable.
* @param name variable name (should be unique within the QgsExpressionContextScope)
* @param value intial variable value
* @param readOnly true if variable should not be editable by users
*/
StaticVariable( const QString& name = QString(), const QVariant& value = QVariant(), bool readOnly = false ) : name( name ), value( value ), readOnly( readOnly ) {}
/** Variable name */
QString name;
/** Variable value */
QVariant value;
/** True if variable should not be editable by users */
bool readOnly;
};
/** Constructor for QgsExpressionContextScope
* @param name friendly display name for the context scope
*/
QgsExpressionContextScope( const QString& name = QString() );
QgsExpressionContextScope( const QgsExpressionContextScope& other );
QgsExpressionContextScope& operator=( const QgsExpressionContextScope& other );
~QgsExpressionContextScope();
/** Returns the friendly display name of the context scope.
*/
QString name() const { return mName; }
/** Convenience method for setting a variable in the context scope by name and value. If a variable
* with the same name is already set then its value is overwritten, otherwise a new variable is added to the scope.
* @param name variable name
* @param value variable value
* @see addVariable()
*/
void setVariable( const QString& name, const QVariant& value );
/** Adds a variable into the context scope. If a variable with the same name is already set then its
* value is overwritten, otherwise a new variable is added to the scope.
* @param variable definition of variable to insert
* @see setVariable()
* @see addFunction()
*/
void addVariable( const QgsExpressionContextScope::StaticVariable& variable );
/** Removes a variable from the context scope, if found.
* @param name name of variable to remove
* @returns true if variable was removed from the scope, false if matching variable was not
* found within the scope
*/
bool removeVariable( const QString& name );
/** Tests whether a variable with the specified name exists in the scope.
* @param name variable name
* @returns true if matching variable was found in the scope
* @see variable()
* @see hasFunction()
*/
bool hasVariable( const QString& name ) const;
/** Retrieves a variable's value from the scope.
* @param name variable name
* @returns variable value, or invalid QVariant if matching variable could not be found
* @see hasVariable()
* @see function()
*/
QVariant variable( const QString& name ) const;
/** Returns a list of variable names contained within the scope.
*/
QStringList variableNames() const;
/** Tests whether the specified variable is read only and should not be editable
* by users.
* @param name variable name
* @returns true if variable is read only
*/
bool isReadOnly( const QString& name ) const;
/** Returns the count of variables contained within the scope.
*/
int variableCount() const { return mVariables.count(); }
/** Tests whether a function with the specified name exists in the scope.
* @param name function name
* @returns true if matching function was found in the scope
* @see function()
* @see hasFunction()
*/
bool hasFunction( const QString &name ) const;
/** Retrieves a function from the scope.
* @param name function name
* @returns function, or null if matching function could not be found
* @see hasFunction()
* @see variable()
*/
QgsExpression::Function* function( const QString &name ) const;
/** Adds a function to the scope.
* @param name function name
* @param function function to insert. Ownership is transferred to the scope.
* @see addVariable()
*/
void addFunction( const QString& name, QgsScopedExpressionFunction* function );
/** Convenience function for setting a feature for the scope. Any existing
* feature set by the scope will be overwritten.
* @param feature feature for scope
*/
void setFeature( const QgsFeature& feature );
/** Convenience function for setting a fields for the scope. Any existing
* fields set by the scope will be overwritten.
* @param fields fields for scope
*/
void setFields( const QgsFields& fields );
private:
QString mName;
QHash<QString, StaticVariable> mVariables;
QHash<QString, QgsScopedExpressionFunction* > mFunctions;
};
/** \ingroup core
* \class QgsExpressionContext
* \brief Expression contexts are used to encapsulate the parameters around which a QgsExpression should
* be evaluated. QgsExpressions can then utilise the information stored within a context to contextualise
* their evaluated result. A QgsExpressionContext consists of a stack of QgsExpressionContextScope objects,
* where scopes added later to the stack will override conflicting variables and functions from scopes
* lower in the stack.
* \note added in QGIS 2.12
*/
class CORE_EXPORT QgsExpressionContext
{
public:
QgsExpressionContext( ) {}
QgsExpressionContext( const QgsExpressionContext& other );
QgsExpressionContext& operator=( const QgsExpressionContext& other );
~QgsExpressionContext();
/** Check whether a variable is specified by any scope within the context.
* @param name variable name
* @returns true if variable is set
* @see variable()
* @see variableNames()
*/
bool hasVariable( const QString& name ) const;
/** Fetches a matching variable from the context. The variable will be fetched
* from the last scope contained within the context which has a matching
* variable set.
* @param name variable name
* @returns variable value if matching variable exists in the context, otherwise an invalid QVariant
* @see hasVariable()
* @see variableNames()
*/
QVariant variable( const QString& name ) const;
/** Returns the currently active scope from the context for a specified variable name.
* As scopes later in the stack override earlier contexts, this will be the last matching
* scope which contains a matching variable.
* @param name variable name
* @returns matching scope containing variable, or null if none found
*/
QgsExpressionContextScope* activeScopeForVariable( const QString& name );
/** Returns the currently active scope from the context for a specified variable name.
* As scopes later in the stack override earlier contexts, this will be the last matching
* scope which contains a matching variable.
* @param name variable name
* @returns matching scope containing variable, or null if none found
*/
const QgsExpressionContextScope* activeScopeForVariable( const QString& name ) const;
/** Returns the scope at the specified index within the context.
* @param index index of scope
* @returns matching scope, or null if none found
* @see lastScope()
*/
QgsExpressionContextScope* scope( int index );
/** Returns the last scope added to the context.
* @see scope()
*/
QgsExpressionContextScope* lastScope();
/** Returns a list of scopes contained within the stack.
* @returns list of pointers to scopes
*/
QList< QgsExpressionContextScope* > scopes() { return mStack; }
/** Returns the index of the specified scope if it exists within the context.
* @param scope scope to find
* @returns index of scope, or -1 if scope was not found within the context.
*/
int indexOfScope( QgsExpressionContextScope* scope ) const;
/** Returns a list of variables names set by all scopes in the context.
* @returns list of unique variable names
* @see hasVariable
* @see variable
*/
QStringList variableNames() const;
/** Returns whether a variable is read only, and should not be modifiable by users.
* @param name variable name
* @returns true if variable is read only. Read only status will be taken from last
* matching scope which contains a matching variable.
*/
bool isReadOnly( const QString& name ) const;
/** Checks whether a specified function is contained in the context.
* @param name function name
* @returns true if context provides a matching function
* @see function
*/
bool hasFunction( const QString& name ) const;
/** Fetches a matching function from the context. The function will be fetched
* from the last scope contained within the context which has a matching
* function set.
* @param name function name
* @returns function if contained by the context, otherwise null.
* @see hasFunction
*/
QgsExpression::Function* function( const QString& name ) const;
/** Returns the number of scopes contained in the context.
*/
int scopeCount() const;
/** Appends a scope to the end of the context. This scope will override
* any matching variables or functions provided by existing scopes within the
* context. Ownership of the scope is transferred to the stack.
* @param scope expression context to append to context
*/
void appendScope( QgsExpressionContextScope* scope );
/** Appends a scope to the end of the context. This scope will override
* any matching variables or functions provided by existing scopes within the
* context. Ownership of the scope is transferred to the stack.
*/
QgsExpressionContext& operator<< ( QgsExpressionContextScope* scope );
/** Convenience function for setting a feature for the context. The feature
* will be set within the last scope of the context, so will override any
* existing features within the context.
* @param feature feature for context
*/
void setFeature( const QgsFeature& feature );
/** Convenience function for setting a fields for the context. The fields
* will be set within the last scope of the context, so will override any
* existing fields within the context.
* @param fields fields for context
*/
void setFields( const QgsFields& fields );
static const QString EXPR_FIELDS;
static const QString EXPR_FEATURE;
private:
QList< QgsExpressionContextScope* > mStack;
};
/** \ingroup core
* \class QgsExpressionContextUtils
* \brief Contains utilities for working with QgsExpressionContext objects, including methods
* for creating scopes for specific uses (eg project scopes, layer scopes).
* \note added in QGIS 2.12
*/
class CORE_EXPORT QgsExpressionContextUtils
{
public:
/** Creates a new scope which contains variables and functions relating to the global QGIS context.
* For instance, QGIS version numbers and variables specified through QGIS options.
* @see setGlobalVariable()
*/
static QgsExpressionContextScope* globalScope();
/** Sets a global context variable. This variable will be contained within scopes retrieved via
* globalScope().
* @param name variable name
* @param value variable value
* @see setGlobalVariable()
* @see globalScope()
*/
static void setGlobalVariable( const QString& name, const QVariant& value );
/** Sets all global context variables. Existing global variables will be removed and replaced
* with the variables specified.
* @param variables new set of global variables
* @see setGlobalVariable()
* @see globalScope()
*/
static void setGlobalVariables( const QgsStringMap& variables );
/** Creates a new scope which contains variables and functions relating to the current QGIS project.
* For instance, project path and title, and variables specified through the project properties.
* @see setProjectVariable()
*/
static QgsExpressionContextScope* projectScope();
/** Sets a project context variable. This variable will be contained within scopes retrieved via
* projectScope().
* @param name variable name
* @param value variable value
* @see setProjectVariables()
* @see projectScope()
*/
static void setProjectVariable( const QString& name, const QVariant& value );
/** Sets all project context variables. Existing project variables will be removed and replaced
* with the variables specified.
* @param variables new set of project variables
* @see setProjectVariable()
* @see projectScope()
*/
static void setProjectVariables( const QgsStringMap& variables );
/** Creates a new scope which contains variables and functions relating to a QgsMapLayer.
* For instance, layer name, id and fields.
*/
static QgsExpressionContextScope* layerScope( QgsMapLayer* layer );
/** Helper function for creating an expression context which contains just a feature and fields
* collection. Generally this method should not be used as the created context does not include
* standard scopes such as the global and project scopes.
*/
static QgsExpressionContext createFeatureBasedContext( const QgsFeature& feature, const QgsFields& fields );
/** Registers all known core functions provided by QgsExpressionContextScope objects.
*/
static void registerContextFunctions();
};
#endif // QGSEXPRESSIONCONTEXT_H

View File

@ -108,6 +108,7 @@ col_next [A-Za-z0-9_]|{non_ascii}
column_ref {col_first}{col_next}*
special_col "$"{column_ref}
variable "@"{column_ref}
col_str_char "\"\""|[^\"]
column_ref_quoted "\""{col_str_char}*"\""
@ -195,6 +196,8 @@ string "'"{str_char}*"'"
{special_col} { TEXT; return SPECIAL_COL; }
{variable} { TEXT; return VARIABLE; }
{column_ref} { TEXT; return QgsExpression::isFunctionName(*yylval->text) ? FUNCTION : COLUMN_REF; }
{column_ref_quoted} { TEXT_FILTER(stripColumnRef); return COLUMN_REF; }

View File

@ -108,7 +108,7 @@ struct expression_parser_context
// tokens for conditional expressions
%token CASE WHEN THEN ELSE END
%token <text> STRING COLUMN_REF FUNCTION SPECIAL_COL
%token <text> STRING COLUMN_REF FUNCTION SPECIAL_COL VARIABLE
%token COMMA
@ -253,6 +253,17 @@ expression:
}
}
// variables
| VARIABLE
{
// @var is equivalent to var( "var" )
QgsExpression::NodeList* args = new QgsExpression::NodeList();
QgsExpression::NodeLiteral* literal = new QgsExpression::NodeLiteral( QString(*$1).mid(1) );
args->append( literal );
$$ = new QgsExpression::NodeFunction( QgsExpression::functionIndex( "var" ), args );
delete $1;
}
// literals
| NUMBER_FLOAT { $$ = new QgsExpression::NodeLiteral( QVariant($1) ); }
| NUMBER_INT { $$ = new QgsExpression::NodeLiteral( QVariant($1) ); }

View File

@ -43,6 +43,7 @@
#include <QDomNode>
#include <QObject>
#include <QTextStream>
#include <QDir>
// canonical project instance
QgsProject *QgsProject::theProject_ = 0;
@ -333,7 +334,6 @@ QgsProject::QgsProject()
// whenever layers are added to or removed from the registry,
// layer tree will be updated
mLayerTreeRegistryBridge = new QgsLayerTreeRegistryBridge( mRootGroup, this );
} // QgsProject ctor
@ -402,7 +402,12 @@ void QgsProject::setFileName( QString const &name )
QString QgsProject::fileName() const
{
return imp_->file.fileName();
} // QString QgsProject::fileName() const
}
QFileInfo QgsProject::fileInfo() const
{
return QFileInfo( imp_->file );
}
void QgsProject::clear()
{

View File

@ -27,6 +27,7 @@
#include <QList>
#include <QObject>
#include <QPair>
#include <QFileInfo>
//for the snap settings
#include "qgssnapper.h"
@ -121,6 +122,11 @@ class CORE_EXPORT QgsProject : public QObject
QString fileName() const;
//@}
/** Returns QFileInfo object for the project's associated file.
* @note added in QGIS 2.9
*/
QFileInfo fileInfo() const;
/** Clear the project
* @note added in 2.4
*/

View File

@ -78,6 +78,7 @@ ENDMACRO (ADD_QGIS_TEST)
# Tests:
ADD_QGIS_TEST(qgistest testqgis.cpp)
ADD_QGIS_TEST(expressioncontext testqgsexpressioncontext.cpp)
ADD_QGIS_TEST(clippertest testqgsclipper.cpp)
ADD_QGIS_TEST(distanceareatest testqgsdistancearea.cpp)
ADD_QGIS_TEST(applicationtest testqgsapplication.cpp)

View File

@ -0,0 +1,579 @@
/***************************************************************************
testqgsexpressioncontext.cpp
----------------------------
begin : April 2015
copyright : (C) 2015 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsexpressioncontext.h"
#include "qgsexpression.h"
#include "qgsvectorlayer.h"
#include "qgsapplication.h"
#include "qgsproject.h"
#include "qgscolorscheme.h"
#include <QObject>
#include <QtTest/QtTest>
class TestQgsExpressionContext : public QObject
{
Q_OBJECT
private slots:
void initTestCase();// will be called before the first testfunction is executed.
void cleanupTestCase();// will be called after the last testfunction was executed.
void init();// will be called before each testfunction is executed.
void cleanup();// will be called after every testfunction.
void contextScope();
void contextScopeCopy();
void contextScopeFunctions();
void contextStack();
void contextCopy();
void contextStackFunctions();
void evaluate();
void setFeature();
void setFields();
void globalScope();
void projectScope();
void layerScope();
void featureBasedContext();
private:
class GetTestValueFunction : public QgsScopedExpressionFunction
{
public:
GetTestValueFunction()
: QgsScopedExpressionFunction( "get_test_value", 1, "test" ) {}
virtual QVariant func( const QVariantList&, const QgsExpressionContext*, QgsExpression* ) override
{
return 42;
}
QgsScopedExpressionFunction* clone() const override
{
return new GetTestValueFunction();
}
};
class GetTestValueFunction2 : public QgsScopedExpressionFunction
{
public:
GetTestValueFunction2()
: QgsScopedExpressionFunction( "get_test_value", 1, "test" ) {}
virtual QVariant func( const QVariantList&, const QgsExpressionContext*, QgsExpression* ) override
{
return 43;
}
QgsScopedExpressionFunction* clone() const override
{
return new GetTestValueFunction2();
}
};
class ModifiableFunction : public QgsScopedExpressionFunction
{
public:
ModifiableFunction( int* v )
: QgsScopedExpressionFunction( "test_function", 1, "test" )
, mVal( v )
{}
virtual QVariant func( const QVariantList&, const QgsExpressionContext*, QgsExpression* ) override
{
if ( !mVal )
return QVariant();
return ++( *mVal );
}
QgsScopedExpressionFunction* clone() const override
{
return new ModifiableFunction( mVal );
}
private:
int* mVal;
};
};
void TestQgsExpressionContext::initTestCase()
{
QgsApplication::init();
QgsApplication::initQgis();
// Set up the QSettings environment
QCoreApplication::setOrganizationName( "QGIS" );
QCoreApplication::setOrganizationDomain( "qgis.org" );
QCoreApplication::setApplicationName( "QGIS-TEST" );
}
void TestQgsExpressionContext::cleanupTestCase()
{
QgsApplication::exitQgis();
}
void TestQgsExpressionContext::init()
{
}
void TestQgsExpressionContext::cleanup()
{
}
void TestQgsExpressionContext::contextScope()
{
QgsExpressionContextScope scope( "scope name" );
QCOMPARE( scope.name(), QString( "scope name" ) );
QVERIFY( !scope.hasVariable( "test" ) );
QVERIFY( !scope.variable( "test" ).isValid() );
QCOMPARE( scope.variableNames().length(), 0 );
QCOMPARE( scope.variableCount(), 0 );
scope.setVariable( "test", 5 );
QVERIFY( scope.hasVariable( "test" ) );
QVERIFY( scope.variable( "test" ).isValid() );
QCOMPARE( scope.variable( "test" ).toInt(), 5 );
QCOMPARE( scope.variableNames().length(), 1 );
QCOMPARE( scope.variableCount(), 1 );
QCOMPARE( scope.variableNames().at( 0 ), QString( "test" ) );
scope.addVariable( QgsExpressionContextScope::StaticVariable( "readonly", QString( "readonly_test" ), true ) );
QVERIFY( scope.isReadOnly( "readonly" ) );
scope.addVariable( QgsExpressionContextScope::StaticVariable( "notreadonly", QString( "not_readonly_test" ), false ) );
QVERIFY( !scope.isReadOnly( "notreadonly" ) );
//updating a read only variable should remain read only
scope.setVariable( "readonly", "newvalue" );
QVERIFY( scope.isReadOnly( "readonly" ) );
//removal
scope.setVariable( "toremove", 5 );
QVERIFY( scope.hasVariable( "toremove" ) );
QVERIFY( !scope.removeVariable( "missing" ) );
QVERIFY( scope.removeVariable( "toremove" ) );
QVERIFY( !scope.hasVariable( "toremove" ) );
}
void TestQgsExpressionContext::contextScopeCopy()
{
QgsExpressionContextScope scope( "scope name" );
scope.setVariable( "test", 5 );
scope.addFunction( "get_test_value", new GetTestValueFunction() );
//copy constructor
QgsExpressionContextScope copy( scope );
QCOMPARE( copy.name(), QString( "scope name" ) );
QVERIFY( copy.hasVariable( "test" ) );
QCOMPARE( copy.variable( "test" ).toInt(), 5 );
QCOMPARE( copy.variableNames().length(), 1 );
QCOMPARE( copy.variableCount(), 1 );
QCOMPARE( copy.variableNames().at( 0 ), QString( "test" ) );
QVERIFY( copy.hasFunction( "get_test_value" ) );
QVERIFY( copy.function( "get_test_value" ) );
}
void TestQgsExpressionContext::contextScopeFunctions()
{
QgsExpressionContextScope scope;
QVERIFY( !scope.hasFunction( "get_test_value" ) );
QVERIFY( !scope.function( "get_test_value" ) );
scope.addFunction( "get_test_value", new GetTestValueFunction() );
QVERIFY( scope.hasFunction( "get_test_value" ) );
QVERIFY( scope.function( "get_test_value" ) );
QgsExpressionContext temp;
QCOMPARE( scope.function( "get_test_value" )->func( QVariantList(), &temp, 0 ).toInt(), 42 );
}
void TestQgsExpressionContext::contextStack()
{
QgsExpressionContext context;
//test retrieving from empty context
QVERIFY( !context.hasVariable( "test" ) );
QVERIFY( !context.variable( "test" ).isValid() );
QCOMPARE( context.variableNames().length(), 0 );
QCOMPARE( context.scopeCount(), 0 );
QVERIFY( !context.scope( 0 ) );
QVERIFY( !context.lastScope() );
//add a scope to the context
QgsExpressionContextScope* testScope = new QgsExpressionContextScope();
context << testScope;
QVERIFY( !context.hasVariable( "test" ) );
QVERIFY( !context.variable( "test" ).isValid() );
QCOMPARE( context.variableNames().length(), 0 );
QCOMPARE( context.scopeCount(), 1 );
QCOMPARE( context.scope( 0 ), testScope );
QCOMPARE( context.lastScope(), testScope );
//some general context scope tests
QVERIFY( !context.scope( -1 ) );
QVERIFY( !context.scope( 5 ) );
QgsExpressionContextScope* scope1 = context.scope( 0 );
QCOMPARE( context.indexOfScope( scope1 ), 0 );
QgsExpressionContextScope scopeNotInContext;
QCOMPARE( context.indexOfScope( &scopeNotInContext ), -1 );
//now add a variable to the first scope
scope1->setVariable( "test", 1 );
QVERIFY( context.hasVariable( "test" ) );
QCOMPARE( context.variable( "test" ).toInt(), 1 );
QCOMPARE( context.variableNames().length(), 1 );
//add a second scope, should override the first
QgsExpressionContextScope* scope2 = new QgsExpressionContextScope();
context << scope2;
QCOMPARE( context.scopeCount(), 2 );
QCOMPARE( context.scope( 1 ), scope2 );
QCOMPARE( context.lastScope(), scope2 );
QCOMPARE( context.indexOfScope( scope2 ), 1 );
//test without setting variable first...
QVERIFY( context.hasVariable( "test" ) );
QCOMPARE( context.variable( "test" ).toInt(), 1 );
QCOMPARE( context.variableNames().length(), 1 );
scope2->setVariable( "test", 2 );
QVERIFY( context.hasVariable( "test" ) );
QCOMPARE( context.variable( "test" ).toInt(), 2 );
QCOMPARE( context.variableNames().length(), 1 );
//make sure context falls back to earlier scopes
scope1->setVariable( "test2", 11 );
QVERIFY( context.hasVariable( "test2" ) );
QCOMPARE( context.variable( "test2" ).toInt(), 11 );
QCOMPARE( context.variableNames().length(), 2 );
//test scopes method
QList< QgsExpressionContextScope*> scopes = context.scopes();
QCOMPARE( scopes.length(), 2 );
QCOMPARE( scopes.at( 0 ), scope1 );
QCOMPARE( scopes.at( 1 ), scope2 );
}
void TestQgsExpressionContext::contextCopy()
{
QgsExpressionContext context;
context << new QgsExpressionContextScope();
context.scope( 0 )->setVariable( "test", 1 );
//copy constructor
QgsExpressionContext copy( context );
QCOMPARE( copy.scopeCount(), 1 );
QVERIFY( copy.hasVariable( "test" ) );
QCOMPARE( copy.variable( "test" ).toInt(), 1 );
QCOMPARE( copy.variableNames().length(), 1 );
}
void TestQgsExpressionContext::contextStackFunctions()
{
QgsExpression::registerFunction( new GetTestValueFunction() );
QgsExpression::registerFunction( new GetTestValueFunction2() );
QgsExpressionContext context;
//test retrieving from empty stack
QVERIFY( !context.hasFunction( "get_test_value" ) );
QVERIFY( !context.function( "get_test_value" ) );
//add a scope to the context
context << new QgsExpressionContextScope();
QVERIFY( !context.hasFunction( "get_test_value" ) );
QVERIFY( !context.function( "get_test_value" ) );
//now add a function to the first scope
QgsExpressionContextScope* scope1 = context.scope( 0 );
scope1->addFunction( "get_test_value", new GetTestValueFunction() );
QVERIFY( context.hasFunction( "get_test_value" ) );
QVERIFY( context.function( "get_test_value" ) );
QgsExpressionContext temp;
QCOMPARE( context.function( "get_test_value" )->func( QVariantList(), &temp, 0 ).toInt(), 42 );
//add a second scope, should override the first
context << new QgsExpressionContextScope();
//test without setting function first...
QVERIFY( context.hasFunction( "get_test_value" ) );
QVERIFY( context.function( "get_test_value" ) );
QCOMPARE( context.function( "get_test_value" )->func( QVariantList(), &temp, 0 ).toInt(), 42 );
//then set the variable so it overrides
QgsExpressionContextScope* scope2 = context.scope( 1 );
scope2->addFunction( "get_test_value", new GetTestValueFunction2() );
QVERIFY( context.hasFunction( "get_test_value" ) );
QVERIFY( context.function( "get_test_value" ) );
QCOMPARE( context.function( "get_test_value" )->func( QVariantList(), &temp, 0 ).toInt(), 43 );
//make sure stack falls back to earlier contexts
scope2->addFunction( "get_test_value2", new GetTestValueFunction() );
QVERIFY( context.hasFunction( "get_test_value2" ) );
QVERIFY( context.function( "get_test_value2" ) );
QCOMPARE( context.function( "get_test_value2" )->func( QVariantList(), &temp, 0 ).toInt(), 42 );
}
void TestQgsExpressionContext::evaluate()
{
QgsExpression exp( "1 + 2" );
QCOMPARE( exp.evaluate().toInt(), 3 );
QgsExpressionContext context;
context << new QgsExpressionContextScope();
QgsExpressionContextScope* s = context.scope( 0 );
s->setVariable( "test", 5 );
QCOMPARE( exp.evaluate( &context ).toInt(), 3 );
QgsExpression expWithVariable( "var('test')" );
QCOMPARE( expWithVariable.evaluate( &context ).toInt(), 5 );
s->setVariable( "test", 7 );
QCOMPARE( expWithVariable.evaluate( &context ).toInt(), 7 );
QgsExpression expWithVariable2( "var('test') + var('test2')" );
s->setVariable( "test2", 9 );
QCOMPARE( expWithVariable2.evaluate( &context ).toInt(), 16 );
QgsExpression expWithVariableBad( "var('bad')" );
QVERIFY( !expWithVariableBad.evaluate( &context ).isValid() );
//test shorthand variables
QgsExpression expShorthand( "@test" );
QCOMPARE( expShorthand.evaluate( &context ).toInt(), 7 );
QgsExpression expShorthandBad( "@bad" );
QVERIFY( !expShorthandBad.evaluate( &context ).isValid() );
//test with a function provided by a context
QgsExpression::registerFunction( new ModifiableFunction( 0 ) );
QgsExpression testExpWContextFunction( "test_function(1)" );
QVERIFY( !testExpWContextFunction.evaluate( ).isValid() );
int val1 = 5;
s->addFunction( "test_function", new ModifiableFunction( &val1 ) );
testExpWContextFunction.prepare( &context );
QCOMPARE( testExpWContextFunction.evaluate( &context ).toInt(), 6 );
QCOMPARE( testExpWContextFunction.evaluate( &context ).toInt(), 7 );
QCOMPARE( val1, 7 );
//test with another context to ensure that expressions are evaulated against correct context
QgsExpressionContext context2;
context2 << new QgsExpressionContextScope();
QgsExpressionContextScope* s2 = context2.scope( 0 );
int val2 = 50;
s2->addFunction( "test_function", new ModifiableFunction( &val2 ) );
QCOMPARE( testExpWContextFunction.evaluate( &context2 ).toInt(), 51 );
QCOMPARE( testExpWContextFunction.evaluate( &context2 ).toInt(), 52 );
}
void TestQgsExpressionContext::setFeature()
{
QgsFeature feature( 50LL );
QgsExpressionContextScope scope;
scope.setFeature( feature );
QVERIFY( scope.hasVariable( QgsExpressionContext::EXPR_FEATURE ) );
QCOMPARE(( qvariant_cast<QgsFeature>( scope.variable( QgsExpressionContext::EXPR_FEATURE ) ) ).id(), 50LL );
//test setting a feature in a context with no scopes
QgsExpressionContext emptyContext;
emptyContext.setFeature( feature );
//setFeature should have created a scope
QCOMPARE( emptyContext.scopeCount(), 1 );
QVERIFY( emptyContext.hasVariable( QgsExpressionContext::EXPR_FEATURE ) );
QCOMPARE(( qvariant_cast<QgsFeature>( emptyContext.variable( QgsExpressionContext::EXPR_FEATURE ) ) ).id(), 50LL );
QgsExpressionContext contextWithScope;
contextWithScope << new QgsExpressionContextScope();
contextWithScope.setFeature( feature );
QCOMPARE( contextWithScope.scopeCount(), 1 );
QVERIFY( contextWithScope.hasVariable( QgsExpressionContext::EXPR_FEATURE ) );
QCOMPARE(( qvariant_cast<QgsFeature>( contextWithScope.variable( QgsExpressionContext::EXPR_FEATURE ) ) ).id(), 50LL );
}
void TestQgsExpressionContext::setFields()
{
QgsFields fields;
QgsField field( "testfield" );
fields.append( field );
QgsExpressionContextScope scope;
scope.setFields( fields );
QVERIFY( scope.hasVariable( QgsExpressionContext::EXPR_FIELDS ) );
QCOMPARE(( qvariant_cast<QgsFields>( scope.variable( QgsExpressionContext::EXPR_FIELDS ) ) ).at( 0 ).name(), QString( "testfield" ) );
//test setting a fields in a context with no scopes
QgsExpressionContext emptyContext;
emptyContext.setFields( fields );
//setFeature should have created a scope
QCOMPARE( emptyContext.scopeCount(), 1 );
QVERIFY( emptyContext.hasVariable( QgsExpressionContext::EXPR_FIELDS ) );
QCOMPARE(( qvariant_cast<QgsFields>( emptyContext.variable( QgsExpressionContext::EXPR_FIELDS ) ) ).at( 0 ).name(), QString( "testfield" ) );
QgsExpressionContext contextWithScope;
contextWithScope << new QgsExpressionContextScope();
contextWithScope.setFields( fields );
QCOMPARE( contextWithScope.scopeCount(), 1 );
QVERIFY( contextWithScope.hasVariable( QgsExpressionContext::EXPR_FIELDS ) );
QCOMPARE(( qvariant_cast<QgsFields>( contextWithScope.variable( QgsExpressionContext::EXPR_FIELDS ) ) ).at( 0 ).name(), QString( "testfield" ) );
}
void TestQgsExpressionContext::globalScope()
{
QgsExpressionContextUtils::setGlobalVariable( "test", "testval" );
QgsExpressionContext context;
QgsExpressionContextScope* globalScope = QgsExpressionContextUtils::globalScope();
context << globalScope;
QCOMPARE( globalScope->name(), tr( "Global" ) );
QCOMPARE( context.variable( "test" ).toString(), QString( "testval" ) );
QgsExpression expGlobal( "var('test')" );
QCOMPARE( expGlobal.evaluate( &context ).toString(), QString( "testval" ) );
//test some other recognized global variables
QgsExpression expVersion( "var('qgis_version')" );
QgsExpression expVersionNo( "var('qgis_version_no')" );
QgsExpression expReleaseName( "var('qgis_release_name')" );
QCOMPARE( expVersion.evaluate( &context ).toString(), QString( QGis::QGIS_VERSION ) );
QCOMPARE( expVersionNo.evaluate( &context ).toInt(), QGis::QGIS_VERSION_INT );
QCOMPARE( expReleaseName.evaluate( &context ).toString(), QString( QGis::QGIS_RELEASE_NAME ) );
//test setGlobalVariables
QgsStringMap vars;
vars.insert( "newvar1", "val1" );
vars.insert( "newvar2", "val2" );
QgsExpressionContextUtils::setGlobalVariables( vars );
QgsExpressionContextScope* globalScope2 = QgsExpressionContextUtils::globalScope();
QVERIFY( !globalScope2->hasVariable( "test" ) );
QCOMPARE( globalScope2->variable( "newvar1" ).toString(), QString( "val1" ) );
QCOMPARE( globalScope2->variable( "newvar2" ).toString(), QString( "val2" ) );
delete globalScope2;
}
void TestQgsExpressionContext::projectScope()
{
QgsExpressionContextUtils::setProjectVariable( "test", "testval" );
QgsExpressionContextUtils::setProjectVariable( "testdouble", 5.2 );
QgsExpressionContext context;
QgsExpressionContextScope* scope = QgsExpressionContextUtils::projectScope();
context << scope;
QCOMPARE( scope->name(), tr( "Project" ) );
QCOMPARE( context.variable( "test" ).toString(), QString( "testval" ) );
QCOMPARE( context.variable( "testdouble" ).toDouble(), 5.2 );
QgsExpression expProject( "var('test')" );
QCOMPARE( expProject.evaluate( &context ).toString(), QString( "testval" ) );
//test clearing project variables
QgsExpressionContextScope* projectScope = QgsExpressionContextUtils::projectScope();
QVERIFY( projectScope->hasVariable( "test" ) );
QgsProject::instance()->clear();
delete projectScope;
projectScope = QgsExpressionContextUtils::projectScope();
QVERIFY( !projectScope->hasVariable( "test" ) );
//test a preset project variable
QgsProject::instance()->setTitle( "test project" );
delete projectScope;
projectScope = QgsExpressionContextUtils::projectScope();
QCOMPARE( projectScope->variable( "project_title" ).toString(), QString( "test project" ) );
delete projectScope;
projectScope = 0;
//test setProjectVariables
QgsStringMap vars;
vars.insert( "newvar1", "val1" );
vars.insert( "newvar2", "val2" );
QgsExpressionContextUtils::setProjectVariables( vars );
projectScope = QgsExpressionContextUtils::projectScope();
QVERIFY( !projectScope->hasVariable( "test" ) );
QCOMPARE( projectScope->variable( "newvar1" ).toString(), QString( "val1" ) );
QCOMPARE( projectScope->variable( "newvar2" ).toString(), QString( "val2" ) );
delete projectScope;
projectScope = 0;
//test project scope functions
//project_color function
QgsProjectColorScheme s;
QgsNamedColorList colorList;
colorList << qMakePair( QColor( 200, 255, 0 ), QString( "vomit yellow" ) );
colorList << qMakePair( QColor( 30, 60, 20 ), QString( "murky depths of hades" ) );
s.setColors( colorList );
QgsExpressionContext contextColors;
contextColors << QgsExpressionContextUtils::projectScope();
QgsExpression expProjectColor( "project_color('murky depths of hades')" );
QCOMPARE( expProjectColor.evaluate( &contextColors ).toString(), QString( "30,60,20" ) );
//matching color names should be case insensitive
QgsExpression expProjectColorCaseInsensitive( "project_color('Murky Depths of hades')" );
QCOMPARE( expProjectColorCaseInsensitive.evaluate( &contextColors ).toString(), QString( "30,60,20" ) );
QgsExpression badProjectColor( "project_color('dusk falls in san juan del sur')" );
QCOMPARE( badProjectColor.evaluate( &contextColors ), QVariant() );
}
void TestQgsExpressionContext::layerScope()
{
//test passing no layer - should be no crash
QgsExpressionContextScope* layerScope = QgsExpressionContextUtils::layerScope( 0 );
QCOMPARE( layerScope->name(), tr( "Layer" ) );
QCOMPARE( layerScope->variableCount(), 0 );
delete layerScope;
layerScope = 0;
//create a map layer
QScopedPointer<QgsVectorLayer> vectorLayer( new QgsVectorLayer( "Point?field=col1:integer&field=col2:integer&field=col3:integer", "test layer", "memory" ) );
QgsExpressionContext context;
context << QgsExpressionContextUtils::layerScope( vectorLayer.data() );
QCOMPARE( context.variable( "layer_name" ).toString(), vectorLayer->name() );
QCOMPARE( context.variable( "layer_id" ).toString(), vectorLayer->id() );
QgsExpression expProject( "var('layer_name')" );
QCOMPARE( expProject.evaluate( &context ).toString(), vectorLayer->name() );
//check that fields were set
QgsFields fromVar = qvariant_cast<QgsFields>( context.variable( QgsExpressionContext::EXPR_FIELDS ) );
QCOMPARE( fromVar, vectorLayer->pendingFields() );
}
void TestQgsExpressionContext::featureBasedContext()
{
QgsFields fields;
fields.append( QgsField( "x1" ) );
fields.append( QgsField( "x2" ) );
fields.append( QgsField( "foo", QVariant::Int ) );
QgsFeature f;
f.initAttributes( 3 );
f.setAttribute( 2, QVariant( 20 ) );
QgsExpressionContext context = QgsExpressionContextUtils::createFeatureBasedContext( f, fields );
QgsFeature evalFeature = qvariant_cast<QgsFeature>( context.variable( "_feature_" ) );
QgsFields evalFields = qvariant_cast<QgsFields>( context.variable( "_fields_" ) );
QCOMPARE( evalFeature.attributes(), f.attributes() );
QCOMPARE( evalFields, fields );
}
QTEST_MAIN( TestQgsExpressionContext )
#include "testqgsexpressioncontext.moc"