218 changes: 130 additions & 88 deletions src/core/qgsexpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -812,84 +812,126 @@ static QVariant fcnSpecialColumn( const QVariantList& values, QgsFeature* /*f*/,
return QgsExpression::specialColumn( varName );
}

QList<QgsExpression::FunctionDef> QgsExpression::gmBuiltinFunctions;
bool QgsExpression::registerFunction( QgsExpression::Function* function )
{
int fnIdx = functionIndex( function->name() );
if ( fnIdx != -1 )
{
return false;
}
QgsExpression::gmFunctions.append( function );
return true;
}

const QList<QgsExpression::FunctionDef> &QgsExpression::BuiltinFunctions()
bool QgsExpression::unregisterFunction( QString name )
{
if ( gmBuiltinFunctions.isEmpty() )
// You can never override the built in functions.
if ( QgsExpression::BuiltinFunctions().contains( name ) )
{
// math
gmBuiltinFunctions
<< FunctionDef( "sqrt", 1, fcnSqrt, QObject::tr( "Math" ) )
<< FunctionDef( "sin", 1, fcnSin, QObject::tr( "Math" ) )
<< FunctionDef( "cos", 1, fcnCos, QObject::tr( "Math" ) )
<< FunctionDef( "tan", 1, fcnTan, QObject::tr( "Math" ) )
<< FunctionDef( "asin", 1, fcnAsin, QObject::tr( "Math" ) )
<< FunctionDef( "acos", 1, fcnAcos, QObject::tr( "Math" ) )
<< FunctionDef( "atan", 1, fcnAtan, QObject::tr( "Math" ) )
<< FunctionDef( "atan2", 2, fcnAtan2, QObject::tr( "Math" ) )
<< FunctionDef( "exp", 1, fcnExp, QObject::tr( "Math" ) )
<< FunctionDef( "ln", 1, fcnLn, QObject::tr( "Math" ) )
<< FunctionDef( "log10", 1, fcnLog10, QObject::tr( "Math" ) )
<< FunctionDef( "log", 2, fcnLog, QObject::tr( "Math" ) )
<< FunctionDef( "round", -1, fcnRound, QObject::tr( "Math" ) )
// casts
<< FunctionDef( "toint", 1, fcnToInt, QObject::tr( "Conversions" ) )
<< FunctionDef( "toreal", 1, fcnToReal, QObject::tr( "Conversions" ) )
<< FunctionDef( "tostring", 1, fcnToString, QObject::tr( "Conversions" ) )
<< FunctionDef( "todatetime", 1, fcnToDateTime, QObject::tr( "Conversions" ) )
<< FunctionDef( "todate", 1, fcnToDate, QObject::tr( "Conversions" ) )
<< FunctionDef( "totime", 1, fcnToTime, QObject::tr( "Conversions" ) )
<< FunctionDef( "tointerval", 1, fcnToInterval, QObject::tr( "Conversions" ) )
// conditionals
<< FunctionDef( "coalesce", -1, fcnCoalesce, QObject::tr( "Conditionals" ) )
// Date and Time
<< FunctionDef( "$now", 0, fcnNow, QObject::tr( "Date and Time" ) )
<< FunctionDef( "age", 2, fcnAge, QObject::tr( "Date and Time" ) )
<< FunctionDef( "year", 1, fcnYear, QObject::tr( "Date and Time" ) )
<< FunctionDef( "month", 1, fcnMonth, QObject::tr( "Date and Time" ) )
<< FunctionDef( "week", 1, fcnWeek, QObject::tr( "Date and Time" ) )
<< FunctionDef( "day", 1, fcnDay, QObject::tr( "Date and Time" ) )
<< FunctionDef( "hour", 1, fcnHour, QObject::tr( "Date and Time" ) )
<< FunctionDef( "minute", 1, fcnMinute, QObject::tr( "Date and Time" ) )
<< FunctionDef( "second", 1, fcnSeconds, QObject::tr( "Date and Time" ) )
// string manipulation
<< FunctionDef( "lower", 1, fcnLower, QObject::tr( "String" ) )
<< FunctionDef( "upper", 1, fcnUpper, QObject::tr( "String" ) )
<< FunctionDef( "title", 1, fcnTitle, QObject::tr( "String" ) )
<< FunctionDef( "length", 1, fcnLength, QObject::tr( "String" ) )
<< FunctionDef( "replace", 3, fcnReplace, QObject::tr( "String" ) )
<< FunctionDef( "regexp_replace", 3, fcnRegexpReplace, QObject::tr( "String" ) )
<< FunctionDef( "substr", 3, fcnSubstr, QObject::tr( "String" ) )
<< FunctionDef( "concat", -1, fcnConcat, QObject::tr( "String" ) )
<< FunctionDef( "strpos", 2, fcnStrpos, QObject::tr( "String" ) )
<< FunctionDef( "left", 2, fcnLeft, QObject::tr( "String" ) )
<< FunctionDef( "right", 2, fcnRight, QObject::tr( "String" ) )
<< FunctionDef( "rpad", 3, fcnRPad, QObject::tr( "String" ) )
<< FunctionDef( "lpad", 3, fcnLPad, QObject::tr( "String" ) )
<< FunctionDef( "format_number", 2, fcnFormatNumber, QObject::tr( "String" ) )
<< FunctionDef( "format_date", 2, fcnFormatDate, QObject::tr( "String" ) )

// geometry accessors
<< FunctionDef( "xat", 1, fcnXat, QObject::tr( "Geometry" ), "", true )
<< FunctionDef( "yat", 1, fcnYat, QObject::tr( "Geometry" ), "", true )
<< FunctionDef( "$area", 0, fcnGeomArea, QObject::tr( "Geometry" ), "", true )
<< FunctionDef( "$length", 0, fcnGeomLength, QObject::tr( "Geometry" ), "", true )
<< FunctionDef( "$perimeter", 0, fcnGeomPerimeter, QObject::tr( "Geometry" ), "", true )
<< FunctionDef( "$x", 0, fcnX, QObject::tr( "Geometry" ), "", true )
<< FunctionDef( "$y", 0, fcnY, QObject::tr( "Geometry" ), "" , true )
// special columns
<< FunctionDef( "$rownum", 0, fcnRowNumber, QObject::tr( "Record" ) )
<< FunctionDef( "$id", 0, fcnFeatureId, QObject::tr( "Record" ) )
<< FunctionDef( "$scale", 0, fcnScale, QObject::tr( "Record" ) )
// private functions
<< FunctionDef( "_specialcol_", 1, fcnSpecialColumn, QObject::tr( "Special" ) )
;
return false;
}
int fnIdx = functionIndex( name );
if ( fnIdx != -1 )
{
QgsExpression::gmFunctions.removeAt( fnIdx );
return true;
}
return false;
}

QStringList QgsExpression::gmBuiltinFunctions;

const QStringList &QgsExpression::BuiltinFunctions()
{
if ( gmBuiltinFunctions.isEmpty() )
{
gmBuiltinFunctions << "sqrt"
<< "sqrt" << "cos" << "sin" << "tan"
<< "asin" << "acos" << "atan" << "atan2"
<< "exp" << "ln" << "log10" << "log"
<< "round" << "toint" << "toreal" << "tostring"
<< "todatetime" << "todate" << "totime" << "tointerval"
<< "coalesce" << "$now" << "age" << "year"
<< "month" << "week" << "day" << "hour"
<< "minute" << "second" << "lower" << "upper"
<< "title" << "length" << "replace" << "regexp_replace"
<< "substr" << "concat" << "strpos" << "left"
<< "right" << "rpad" << "lpad" << "format_number"
<< "format_date" << "xat" << "yat" << "$area"
<< "$length" << "$perimeter" << "$x" << "$y"
<< "$rownum" << "$id" << "$scale" << "_specialcol_";
}
return gmBuiltinFunctions;
}

QList<QgsExpression::Function*> QgsExpression::gmFunctions;

const QList<QgsExpression::Function*> &QgsExpression::Functions()
{
if ( gmFunctions.isEmpty() )
{
gmFunctions
<< new StaticFunction( "sqrt", 1, fcnSqrt, QObject::tr( "Math" ) )
<< new StaticFunction( "cos", 1, fcnCos, QObject::tr( "Math" ) )
<< new StaticFunction( "sin", 1, fcnSin, QObject::tr( "Math" ) )
<< new StaticFunction( "tan", 1, fcnTan, QObject::tr( "Math" ) )
<< new StaticFunction( "asin", 1, fcnAsin, QObject::tr( "Math" ) )
<< new StaticFunction( "acos", 1, fcnAcos, QObject::tr( "Math" ) )
<< new StaticFunction( "atan", 1, fcnAtan, QObject::tr( "Math" ) )
<< new StaticFunction( "atan2", 2, fcnAtan2, QObject::tr( "Math" ) )
<< new StaticFunction( "exp", 1, fcnExp, QObject::tr( "Math" ) )
<< new StaticFunction( "ln", 1, fcnLn, QObject::tr( "Math" ) )
<< new StaticFunction( "log10", 1, fcnLog10, QObject::tr( "Math" ) )
<< new StaticFunction( "log", 2, fcnLog, QObject::tr( "Math" ) )
<< new StaticFunction( "round", -1, fcnRound, QObject::tr( "Math" ) )
<< new StaticFunction( "toint", 1, fcnToInt, QObject::tr( "Conversions" ) )
<< new StaticFunction( "toreal", 1, fcnToReal, QObject::tr( "Conversions" ) )
<< new StaticFunction( "tostring", 1, fcnToString, QObject::tr( "Conversions" ) )
<< new StaticFunction( "todatetime", 1, fcnToDateTime, QObject::tr( "Conversions" ) )
<< new StaticFunction( "todate", 1, fcnToDate, QObject::tr( "Conversions" ) )
<< new StaticFunction( "totime", 1, fcnToTime, QObject::tr( "Conversions" ) )
<< new StaticFunction( "tointerval", 1, fcnToInterval, QObject::tr( "Conversions" ) )
<< new StaticFunction( "coalesce", -1, fcnCoalesce, QObject::tr( "Conditionals" ) )
<< new StaticFunction( "$now", 0, fcnNow, QObject::tr( "Date and Time" ) )
<< new StaticFunction( "age", 2, fcnAge, QObject::tr( "Date and Time" ) )
<< new StaticFunction( "year", 1, fcnYear, QObject::tr( "Date and Time" ) )
<< new StaticFunction( "month", 1, fcnMonth, QObject::tr( "Date and Time" ) )
<< new StaticFunction( "week", 1, fcnWeek, QObject::tr( "Date and Time" ) )
<< new StaticFunction( "day", 1, fcnDay, QObject::tr( "Date and Time" ) )
<< new StaticFunction( "hour", 1, fcnHour, QObject::tr( "Date and Time" ) )
<< new StaticFunction( "minute", 1, fcnMinute, QObject::tr( "Date and Time" ) )
<< new StaticFunction( "second", 1, fcnSeconds, QObject::tr( "Date and Time" ) )
<< new StaticFunction( "lower", 1, fcnLower, QObject::tr( "String" ) )
<< new StaticFunction( "upper", 1, fcnUpper, QObject::tr( "String" ) )
<< new StaticFunction( "title", 1, fcnTitle, QObject::tr( "String" ) )
<< new StaticFunction( "length", 1, fcnLength, QObject::tr( "String" ) )
<< new StaticFunction( "replace", 3, fcnReplace, QObject::tr( "String" ) )
<< new StaticFunction( "regexp_replace", 3, fcnRegexpReplace, QObject::tr( "String" ) )
<< new StaticFunction( "substr", 3, fcnSubstr, QObject::tr( "String" ) )
<< new StaticFunction( "concat", -1, fcnConcat, QObject::tr( "String" ) )
<< new StaticFunction( "strpos", 2, fcnStrpos, QObject::tr( "String" ) )
<< new StaticFunction( "left", 2, fcnLeft, QObject::tr( "String" ) )
<< new StaticFunction( "right", 2, fcnRight, QObject::tr( "String" ) )
<< new StaticFunction( "rpad", 3, fcnRPad, QObject::tr( "String" ) )
<< new StaticFunction( "lpad", 3, fcnLPad, QObject::tr( "String" ) )
<< new StaticFunction( "format_number", 2, fcnFormatNumber, QObject::tr( "String" ) )
<< new StaticFunction( "format_date", 2, fcnFormatDate, QObject::tr( "String" ) )
<< new StaticFunction( "xat", 1, fcnXat, QObject::tr( "Geometry" ), "", true )
<< new StaticFunction( "yat", 1, fcnYat, QObject::tr( "Geometry" ), "", true )
<< new StaticFunction( "$area", 0, fcnGeomArea, QObject::tr( "Geometry" ), "", true )
<< new StaticFunction( "$length", 0, fcnGeomLength, QObject::tr( "Geometry" ), "", true )
<< new StaticFunction( "$perimeter", 0, fcnGeomPerimeter, QObject::tr( "Geometry" ), "", true )
<< new StaticFunction( "$x", 0, fcnX, QObject::tr( "Geometry" ), "", true )
<< new StaticFunction( "$y", 0, fcnY, QObject::tr( "Geometry" ), "" , true )
<< new StaticFunction( "$rownum", 0, fcnRowNumber, QObject::tr( "Record" ) )
<< new StaticFunction( "$id", 0, fcnFeatureId, QObject::tr( "Record" ) )
<< new StaticFunction( "$scale", 0, fcnScale, QObject::tr( "Record" ) )
<< new StaticFunction( "_specialcol_", 1, fcnSpecialColumn, QObject::tr( "Special" ) )
;
}
return gmFunctions;
}

QMap<QString, QVariant> QgsExpression::gmSpecialColumns;

void QgsExpression::setSpecialColumn( const QString& name, QVariant variant )
Expand Down Expand Up @@ -928,12 +970,12 @@ QVariant QgsExpression::specialColumn( const QString& name )
return it.value();
}

QList<QgsExpression::FunctionDef> QgsExpression::specialColumns()
QList<QgsExpression::Function*> QgsExpression::specialColumns()
{
QList<FunctionDef> defs;
QList<Function*> defs;
for ( QMap<QString, QVariant>::const_iterator it = gmSpecialColumns.begin(); it != gmSpecialColumns.end(); ++it )
{
defs << FunctionDef( it.key(), 0, 0, QObject::tr( "Record" ) );
defs << new StaticFunction( it.key(), 0, 0, QObject::tr( "Record" ) );
}
return defs;
}
Expand All @@ -948,15 +990,15 @@ int QgsExpression::functionIndex( QString name )
int count = functionCount();
for ( int i = 0; i < count; i++ )
{
if ( QString::compare( name, BuiltinFunctions()[i].mName, Qt::CaseInsensitive ) == 0 )
if ( QString::compare( name, Functions()[i]->name(), Qt::CaseInsensitive ) == 0 )
return i;
}
return -1;
}

int QgsExpression::functionCount()
{
return BuiltinFunctions().size();
return Functions().size();
}


Expand Down Expand Up @@ -1919,7 +1961,7 @@ void QgsExpression::NodeInOperator::toOgcFilter( QDomDocument &doc, QDomElement

QVariant QgsExpression::NodeFunction::eval( QgsExpression* parent, QgsFeature* f )
{
const FunctionDef& fd = BuiltinFunctions()[mFnIndex];
Function* fd = Functions()[mFnIndex];

// evaluate arguments
QVariantList argValues;
Expand All @@ -1929,14 +1971,14 @@ QVariant QgsExpression::NodeFunction::eval( QgsExpression* parent, QgsFeature* f
{
QVariant v = n->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
if ( isNull( v ) && fd.mFcn != fcnCoalesce )
if ( isNull( v ) && fd->name() != "coalesce" )
return QVariant(); // all "normal" functions return NULL, when any parameter is NULL (so coalesce is abnormal)
argValues.append( v );
}
}

// run the function
QVariant res = fd.mFcn( argValues, f, parent );
QVariant res = fd->func( argValues, f, parent );
ENSURE_NO_EVAL_ERROR;

// everything went fine
Expand All @@ -1958,21 +2000,21 @@ bool QgsExpression::NodeFunction::prepare( QgsExpression* parent, const QgsField

QString QgsExpression::NodeFunction::dump() const
{
const FunctionDef& fd = BuiltinFunctions()[mFnIndex];
if ( fd.mParams == 0 )
return fd.mName; // special column
Function* fd = Functions()[mFnIndex];
if ( fd->params() == 0 )
return fd->name(); // special column
else
return QString( "%1(%2)" ).arg( fd.mName ).arg( mArgs ? mArgs->dump() : QString() ); // function
return QString( "%1(%2)" ).arg( fd->name() ).arg( mArgs ? mArgs->dump() : QString() ); // function
}

void QgsExpression::NodeFunction::toOgcFilter( QDomDocument &doc, QDomElement &element ) const
{
const FunctionDef& fd = BuiltinFunctions()[mFnIndex];
if ( fd.mParams == 0 )
Function* fd = Functions()[mFnIndex];
if ( fd->params() == 0 )
return; // TODO: special column

QDomElement funcElem = doc.createElement( "ogc:Function" );
funcElem.setAttribute( "name", fd.mName );
funcElem.setAttribute( "name", fd->name() );
mArgs->toOgcFilter( doc, funcElem );
element.appendChild( funcElem );
}
Expand All @@ -1988,11 +2030,11 @@ QgsExpression::Node* QgsExpression::NodeFunction::createFromOgcFilter( QDomEleme
return NULL;
}

for ( int i = 0; i < BuiltinFunctions().size(); i++ )
for ( int i = 0; i < Functions().size(); i++ )
{
QgsExpression::FunctionDef funcDef = BuiltinFunctions()[i];
QgsExpression::Function* funcDef = Functions()[i];

if ( element.attribute( "name" ) != funcDef.mName )
if ( element.attribute( "name" ) != funcDef->name() )
continue;

QgsExpression::NodeList *args = new QgsExpression::NodeList();
Expand Down
71 changes: 55 additions & 16 deletions src/core/qgsexpression.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,33 +203,72 @@ class CORE_EXPORT QgsExpression

typedef QVariant( *FcnEval )( const QVariantList& values, QgsFeature* f, QgsExpression* parent );

struct FunctionDef

/**
* A abstract base class for defining QgsExpression functions.
*/
class CORE_EXPORT Function
{
FunctionDef( QString fnname, int params, FcnEval fcn, QString group, QString helpText = QString(), bool usesGeometry = false )
: mName( fnname ), mParams( params ), mFcn( fcn ), mUsesGeometry( usesGeometry ), mGroup( group ), mHelpText( helpText ) {}
public:
Function( QString fnname, int params, QString group, QString helpText = QString(), bool usesGeometry = false )
: mName( fnname ), mParams( params ), mUsesGeometry( usesGeometry ), mGroup( group ), mHelpText( helpText ) {}
/** The name of the function. */
QString mName;
QString name() { return mName; }
/** The number of parameters this function takes. */
int mParams;
/** Pointer to funntion.
* @note not available in python bindings
*/
FcnEval mFcn;
int params() { return mParams; }
/** Does this function use a geometry object. */
bool mUsesGeometry;
bool usesgeometry() { return mUsesGeometry; }
/** The group the function belongs to. */
QString mGroup;
QString group() { return mGroup; }
/** The help text for the function. */
QString helptext() { return mHelpText; }

virtual QVariant func(const QVariantList& values, QgsFeature* f, QgsExpression* parent) = 0;

bool operator==(const Function& other) const
{
if ( QString::compare( mName, other.mName, Qt::CaseInsensitive ) == 0 )
return true;

return false;
}

private:
QString mName;
int mParams;
bool mUsesGeometry;
QString mGroup;
QString mHelpText;
};

static const QList<FunctionDef> &BuiltinFunctions();
static QList<FunctionDef> gmBuiltinFunctions;
class StaticFunction : public Function
{
public:
StaticFunction( QString fnname, int params, FcnEval fcn, QString group, QString helpText = QString(), bool usesGeometry = false )
: Function( fnname, params, group, helpText, usesGeometry), mFnc( fcn ) {}

virtual QVariant func(const QVariantList& values, QgsFeature* f, QgsExpression* parent)
{
return mFnc(values,f, parent);
}

private:
FcnEval mFnc;
};

const static QList<Function*> &Functions();
static QList<Function*> gmFunctions;

static QStringList gmBuiltinFunctions;
const static QStringList &BuiltinFunctions();

static bool registerFunction(Function* function);
static bool unregisterFunction(QString name);

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

// return index of the function in BuiltinFunctions array
// return index of the function in Functions array
static int functionIndex( QString name );

/** Returns the number of functions defined in the parser
Expand All @@ -240,7 +279,7 @@ class CORE_EXPORT QgsExpression
/**
* Returns a list of special Column definitions
*/
static QList<FunctionDef> specialColumns();
static QList<Function*> specialColumns();

//! return quoted column reference (in double quotes)
static QString quotedColumnRef( QString name ) { return QString( "\"%1\"" ).arg( name.replace( "\"", "\"\"" ) ); }
Expand Down Expand Up @@ -422,7 +461,7 @@ class CORE_EXPORT QgsExpression
static QgsExpression::Node* createFromOgcFilter( QDomElement &element, QString &errorMessage );

virtual QStringList referencedColumns() const { QStringList lst; if ( !mArgs ) return lst; foreach ( Node* n, mArgs->list() ) lst.append( n->referencedColumns() ); return lst; }
virtual bool needsGeometry() const { bool needs = BuiltinFunctions()[mFnIndex].mUsesGeometry; if ( mArgs ) { foreach ( Node* n, mArgs->list() ) needs |= n->needsGeometry(); } return needs; }
virtual bool needsGeometry() const { bool needs = Functions()[mFnIndex]->usesgeometry(); if ( mArgs ) { foreach ( Node* n, mArgs->list() ) needs |= n->needsGeometry(); } return needs; }
virtual void accept( Visitor& v ) { v.visit( this ); }

protected:
Expand Down
4 changes: 2 additions & 2 deletions src/core/qgsexpressionparser.yy
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ expression:
exp_error("Function is not known");
YYERROR;
}
if ( QgsExpression::BuiltinFunctions()[fnIndex].mParams != -1
&& QgsExpression::BuiltinFunctions()[fnIndex].mParams != $3->count() )
if ( QgsExpression::Functions()[fnIndex]->params() != -1
&& QgsExpression::Functions()[fnIndex]->params() != $3->count() )
{
exp_error("Function is called with wrong number of arguments");
YYERROR;
Expand Down
25 changes: 16 additions & 9 deletions src/gui/qgsexpressionbuilderwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,20 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
int count = QgsExpression::functionCount();
for ( int i = 0; i < count; i++ )
{
QgsExpression::FunctionDef func = QgsExpression::BuiltinFunctions()[i];
QString name = func.mName;
QgsExpression::Function* func = QgsExpression::Functions()[i];
QString name = func->name();
if ( name.startsWith( "_" ) ) // do not display private functions
continue;
if ( func.mParams >= 1 )
if ( func->params() >= 1 )
name += "(";
registerItem( func.mGroup, func.mName, " " + name + " " );
registerItem( func->group(), func->name(), " " + name + " ", func->helptext() );
}

QList<QgsExpression::FunctionDef> specials = QgsExpression::specialColumns();
QList<QgsExpression::Function*> specials = QgsExpression::specialColumns();
for ( int i = 0; i < specials.size(); ++i )
{
QString name = specials[i].mName;
registerItem( specials[i].mGroup, name, " " + name + " " );
QString name = specials[i]->name();
registerItem( specials[i]->group(), name, " " + name + " " );
}

#if QT_VERSION >= 0x040700
Expand Down Expand Up @@ -405,14 +405,21 @@ QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem* express
if ( expressionItem == NULL )
return "";

QString helpContents;
// Return the function help that is set for the function if there is one.
if ( !expressionItem->getHelpText().isEmpty() )
{
QString myStyle = QgsApplication::reportStyleSheet();
helpContents = "<head><style>" + myStyle + "</style></head><body>" + expressionItem->getHelpText() + "</body>";
return helpContents;
}
// set up the path to the help file
QString helpFilesPath = QgsApplication::pkgDataPath() + "/resources/function_help/";
/*
* determine the locale and create the file name from
* the context id
*/
QString lang = QLocale::system().name();

QSettings settings;
if ( settings.value( "locale/overrideFlag", false ).toBool() )
{
Expand All @@ -437,7 +444,7 @@ QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem* express

QString fullHelpPath = helpFilesPath + name + "-" + lang;
// get the help content and title from the localized file
QString helpContents;

QFile file( fullHelpPath );

QString missingError = tr( "<h3>Oops! QGIS can't find help for this function.</h3>"
Expand Down
1 change: 1 addition & 0 deletions tests/src/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ ADD_PYTHON_TEST(PyQgsSymbolLayerV2 test_qgssymbollayerv2.py)
ADD_PYTHON_TEST(PyQgsPoint test_qgspoint.py)
ADD_PYTHON_TEST(PyQgsAtlasComposition test_qgsatlascomposition.py)
ADD_PYTHON_TEST(PyQgsComposerLabel test_qgscomposerlabel.py)
ADD_PYTHON_TEST(PyQgsExpression test_qgsexpression.py)
#ADD_PYTHON_TEST(PyQgsPalLabeling test_qgspallabeling.py)
ADD_PYTHON_TEST(PyQgsVectorFileWriter test_qgsvectorfilewriter.py)
87 changes: 87 additions & 0 deletions tests/src/python/test_qgsexpression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsExpression.
.. note:: 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.
"""
__author__ = 'Nathan Woodrow'
__date__ = '4/11/2012'
__copyright__ = 'Copyright 2012, The Quantum GIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'

from utilities import unittest, TestCase
from qgis.utils import qgsfunction
from qgis.core import QgsExpression
from PyQt4.QtCore import QString

class TestQgsExpressionCustomFunctions(TestCase):
@qgsfunction(1, 'testing', register=False)
def testfun(values, feature, parent):
""" Function help """
return "Testing_%s" % str(values[0].toString())

@qgsfunction(0, 'testing', register=False)
def special(values, feature, parent):
return "test"

@qgsfunction(1, 'testing', register=False)
def sqrt(values, feature, parent):
pass

def tearDown(self):
QgsExpression.unregisterFunction('testfun')

def testCanBeRegistered(self):
QgsExpression.registerFunction(self.testfun)
index = QgsExpression.functionIndex('testfun')
self.assertTrue(not index == -1)

def testCanUnregisterFunction(self):
QgsExpression.registerFunction(self.testfun)
index = QgsExpression.functionIndex('testfun')
self.assertTrue(not index == -1)
error = QgsExpression.unregisterFunction('testfun')
self.assertTrue(error)
index = QgsExpression.functionIndex('testfun')
self.assertTrue(index == -1)

def testCanEvaluateFunction(self):
QgsExpression.registerFunction(self.testfun)
exp = QgsExpression('testfun(1)')
result = exp.evaluate().toString()
self.assertEqual(QString('Testing_1'), result)

def testZeroArgFunctionsAreSpecialColumns(self):
special = self.special
self.assertEqual(special.name(), '$special')

def testDecoratorPreservesAttributes(self):
func = self.testfun
self.assertEqual(func.name(), 'testfun')
self.assertEqual(func.group(), 'testing')
self.assertEqual(func.params(), 1)

def testCantReregister(self):
QgsExpression.registerFunction(self.testfun)
success = QgsExpression.registerFunction(self.testfun)
self.assertFalse(success)

def testCanReregisterAfterUnregister(self):
QgsExpression.registerFunction(self.testfun)
QgsExpression.unregisterFunction("testfun")
success = QgsExpression.registerFunction(self.testfun)
self.assertTrue(success)

def testCantOverrideBuiltinsWithRegister(self):
success = QgsExpression.registerFunction(self.sqrt)
self.assertFalse(success)

def testCantOverrideBuiltinsWithUnregister(self):
success = QgsExpression.unregisterFunction("sqrt")
self.assertFalse(success)

if __name__ == "__main__":
unittest.main()