[FEATURE] new QgsExpression class that replaces QgsSearchString

The new class fixes various flaws in search strings:
- fixed and simplified grammar in parser
- simplified lexer
- only one evaluation routine (instead of separate getValue/checkAgainst)
- SQL compliant three-value logic (true, false, unknown)
- correct error handling
- easily extensible list of functions, saner evaluation of expressions
- tests included
This commit is contained in:
Martin Dobias 2011-08-07 22:39:45 +02:00
parent 0053426f97
commit 4d0e59c183
7 changed files with 2025 additions and 2 deletions

View File

@ -53,6 +53,7 @@ SET(QGIS_CORE_SRCS
qgsdiagram.cpp
qgsdiagramrendererv2.cpp
qgsdistancearea.cpp
qgsexpression.cpp
qgsfeature.cpp
qgsfield.cpp
qgsgeometry.cpp
@ -216,9 +217,9 @@ IF (WITH_INTERNAL_SPATIALITE)
INCLUDE_DIRECTORIES(BEFORE spatialite/headers/spatialite)
ENDIF (WITH_INTERNAL_SPATIALITE)
ADD_FLEX_FILES(QGIS_CORE_SRCS qgssearchstringlexer.ll)
ADD_FLEX_FILES(QGIS_CORE_SRCS qgssearchstringlexer.ll qgsexpressionlexer.ll)
ADD_BISON_FILES(QGIS_CORE_SRCS qgssearchstringparser.yy)
ADD_BISON_FILES(QGIS_CORE_SRCS qgssearchstringparser.yy qgsexpressionparser.yy)
SET(QGIS_CORE_MOC_HDRS
qgsapplication.h
@ -286,6 +287,7 @@ SET(QGIS_CORE_HDRS
qgsdistancearea.h
qgscsexception.h
qgsexception.h
qgsexpression.h
qgsfeature.h
qgsfield.h
qgsgeometry.h

904
src/core/qgsexpression.cpp Normal file
View File

@ -0,0 +1,904 @@
/***************************************************************************
qgsexpression.cpp
-------------------
begin : August 2011
copyright : (C) 2011 Martin Dobias
email : wonder.sk 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 "qgsexpression.h"
#include <QtDebug>
#include <QSettings>
#include <math.h>
#include "qgsdistancearea.h"
#include "qgsfeature.h"
#include "qgsgeometry.h"
// from parser
extern QgsExpression::Node* parseExpression( const QString& str, QString& parserErrorMsg );
///////////////////////////////////////////////
// three-value logic
class TVL
{
public:
enum Value
{
False,
True,
Unknown
};
static TVL::Value AND[3][3];
static TVL::Value OR[3][3];
static TVL::Value NOT[3];
TVL() : val( Unknown ) {}
TVL( bool v ) : val( v ? True : False ) {}
TVL( Value v ) : val( v ) {}
TVL operator!() { return NOT[val]; }
TVL operator&( const TVL& other ) { return AND[val][other.val]; }
TVL operator|( const TVL& other ) { return OR[val][other.val]; }
// conversion for result
QVariant toVariant()
{
if ( val == Unknown )
return QVariant();
else
return ( val == True ? 1 : 0 );
}
Value val;
};
// false true unknown
TVL::Value TVL::AND[3][3] = { { TVL::False, TVL::False, TVL::False }, // false
{ TVL::False, TVL::True, TVL::Unknown }, // true
{ TVL::False, TVL::Unknown, TVL::Unknown }
};// unknown
TVL::Value TVL::OR[3][3] = { { TVL::False, TVL::True, TVL::Unknown }, // false
{ TVL::True, TVL::True, TVL::True }, // true
{ TVL::Unknown, TVL::True, TVL::Unknown }
};// unknown
TVL::Value TVL::NOT[3] = { TVL::True, TVL::False, TVL::Unknown };
Q_DECLARE_METATYPE( TVL )
#define TVL_True QVariant::fromValue(TVL(true))
#define TVL_False QVariant::fromValue(TVL(false))
#define TVL_Unknown QVariant::fromValue(TVL())
///////////////////////////////////////////////
// QVariant checks and conversions
inline bool isInt( const QVariant& v ) { return v.type() == QVariant::Int; }
inline bool isDouble( const QVariant& v ) { return v.type() == QVariant::Double; }
inline bool isNumeric( const QVariant& v ) { return v.type() == QVariant::Int || v.type() == QVariant::Double; }
inline bool isString( const QVariant& v ) { return v.type() == QVariant::String; }
inline bool isNull( const QVariant& v ) { return v.type() == QVariant::Invalid; }
inline bool isLogic( const QVariant& v ) { return v.canConvert<TVL>(); }
inline bool isNumericOrNull( const QVariant& v ) { return v.type() == QVariant::Int || v.type() == QVariant::Double; }
inline int qvInt( const QVariant& v ) { return v.toInt(); }
inline double qvDouble( const QVariant& v ) { return v.toDouble(); }
inline QString qvString( const QVariant& v ) { return v.toString(); }
inline TVL qvLogic( const QVariant& v ) { return v.value<TVL>(); }
///////////////////////////////////////////////
// evaluation error macros
#define ENSURE_NO_EVAL_ERROR { if (!parent->mEvalErrorString.isNull()) return QVariant(); }
#define SET_EVAL_ERROR(x) { parent->mEvalErrorString = x; return QVariant(); }
///////////////////////////////////////////////
// operators
const char* QgsExpression::BinaryOperatorText[] =
{
"OR", "AND",
"=", "<>", "<=", ">=", "<", ">", "~", "LIKE", "ILIKE", "IS", "IS NOT",
"+", "-", "*", "/", "%", "^",
"||"
};
const char* QgsExpression::UnaryOperatorText[] =
{
"NOT", "-"
};
///////////////////////////////////////////////
// functions
static int getIntArg( int i, const QVariantList& values, QgsExpression* parent )
{
QVariant v = values.at( i );
if ( !isInt( v ) )
{
parent->setEvalErrorString( "Function needs integer value" );
return 0;
}
return v.toInt();
}
static double getNumericArg( int i, const QVariantList& values, QgsExpression* parent )
{
QVariant v = values.at( i );
if ( !isNumeric( v ) )
{
parent->setEvalErrorString( "Function needs numeric value" );
return NAN;
}
return v.toDouble();
}
static QString getStringArg( int i, const QVariantList& values, QgsExpression* parent )
{
QVariant v = values.at( i );
if ( !isString( v ) )
{
parent->setEvalErrorString( "Function needs string value" );
return QString();
}
return v.toString();
}
QVariant fcnSqrt( const QVariantList& values, QgsFeature* /*f*/, QgsExpression* parent )
{
double x = getNumericArg( 0, values, parent );
return isnan( x ) ? QVariant() : QVariant( sqrt( x ) );
}
QVariant fcnSin( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getNumericArg( 0, values, parent );
return isnan( x ) ? QVariant() : QVariant( sin( x ) );
}
QVariant fcnCos( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getNumericArg( 0, values, parent );
return isnan( x ) ? QVariant() : QVariant( cos( x ) );
}
QVariant fcnTan( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getNumericArg( 0, values, parent );
return isnan( x ) ? QVariant() : QVariant( tan( x ) );
}
QVariant fcnAsin( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getNumericArg( 0, values, parent );
return isnan( x ) ? QVariant() : QVariant( asin( x ) );
}
QVariant fcnAcos( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getNumericArg( 0, values, parent );
return isnan( x ) ? QVariant() : QVariant( acos( x ) );
}
QVariant fcnAtan( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getNumericArg( 0, values, parent );
return isnan( x ) ? QVariant() : QVariant( atan( x ) );
}
QVariant fcnAtan2( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double y = getNumericArg( 0, values, parent );
double x = getNumericArg( 1, values, parent );
if ( isnan( y ) || isnan( x ) ) return QVariant();
return QVariant( atan2( y, x ) );
}
QVariant fcnToInt( const QVariantList& values, QgsFeature* , QgsExpression* /*parent*/ )
{
QVariant v = values.at( 0 );
if ( v.type() == QVariant::Invalid ) return QVariant();
return QVariant( v.toInt() );
}
QVariant fcnToReal( const QVariantList& values, QgsFeature* , QgsExpression* /*parent*/ )
{
QVariant v = values.at( 0 );
if ( v.type() == QVariant::Invalid ) return QVariant();
return QVariant( v.toDouble() );
}
QVariant fcnToString( const QVariantList& values, QgsFeature* , QgsExpression* /*parent*/ )
{
QVariant v = values.at( 0 );
if ( v.type() == QVariant::Invalid ) return QVariant();
return QVariant( v.toString() );
}
QVariant fcnLower( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringArg( 0, values, parent );
if ( str.isNull() ) return QVariant(); // error or null string
return QVariant( str.toLower() );
}
QVariant fcnUpper( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringArg( 0, values, parent );
if ( str.isNull() ) return QVariant(); // error or null string
return QVariant( str.toUpper() );
}
QVariant fcnLength( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringArg( 0, values, parent );
if ( str.isNull() ) return QVariant(); // error or null string
return QVariant( str.length() );
}
QVariant fcnReplace( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringArg( 0, values, parent );
QString before = getStringArg( 1, values, parent );
QString after = getStringArg( 2, values, parent );
if ( str.isNull() || before.isNull() || after.isNull() ) return QVariant(); // error or null string
return QVariant( str.replace( before, after ) );
}
QVariant fcnRegexpReplace( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringArg( 0, values, parent );
QString regexp = getStringArg( 1, values, parent );
QString after = getStringArg( 2, values, parent );
if ( str.isNull() || regexp.isNull() || after.isNull() ) return QVariant(); // error or null string
QRegExp re( regexp );
if ( !re.isValid() )
{
parent->setEvalErrorString( QString( "Invalid regular expression '%1': %2" ).arg( regexp ).arg( re.errorString() ) );
return QVariant();
}
return QVariant( str.replace( re, after ) );
}
QVariant fcnSubstr( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringArg( 0, values, parent );
if ( str.isNull() ) return QVariant(); // error or null string
int from = getIntArg( 1, values, parent );
if ( parent->hasEvalError() ) return QVariant();
int len = getIntArg( 2, values, parent );
if ( parent->hasEvalError() ) return QVariant();
return QVariant( str.mid( from -1, len ) );
}
QVariant fcnRowNumber( const QVariantList& , QgsFeature* , QgsExpression* parent )
{
return QVariant( parent->currentRowNumber() );
}
QVariant fcnFeatureId( const QVariantList& , QgsFeature* f, QgsExpression* )
{
// TODO: handling of 64-bit feature ids?
return f ? QVariant(( int )f->id() ) : QVariant();
}
#define ENSURE_GEOM_TYPE(f, g, geomtype) if (!f) return QVariant(); \
QgsGeometry* g = f->geometry(); \
if (!g || g->type() != geomtype) return QVariant();
QVariant fcnX( const QVariantList& , QgsFeature* f, QgsExpression* )
{
ENSURE_GEOM_TYPE( f, g, QGis::Point );
return g->asPoint().x();
}
QVariant fcnY( const QVariantList& , QgsFeature* f, QgsExpression* )
{
ENSURE_GEOM_TYPE( f, g, QGis::Point );
return g->asPoint().y();
}
static QVariant pointAt( const QVariantList& values, QgsFeature* f, QgsExpression* parent ) // helper function
{
int idx = getIntArg( 0, values, parent );
ENSURE_GEOM_TYPE( f, g, QGis::Line );
QgsPolyline polyline = g->asPolyline();
if ( idx < 0 )
idx += polyline.count();
if ( idx < 0 || idx >= polyline.count() )
{
parent->setEvalErrorString( "Index is out of range" );
return QVariant();
}
return QVariant( QPointF( polyline[idx].x(), polyline[idx].y() ) );
}
QVariant fcnXat( const QVariantList& values, QgsFeature* f, QgsExpression* parent )
{
QVariant v = pointAt( values, f, parent );
if ( v.type() == QVariant::PointF )
return QVariant( v.toPointF().x() );
else
return QVariant();
}
QVariant fcnYat( const QVariantList& values, QgsFeature* f, QgsExpression* parent )
{
QVariant v = pointAt( values, f, parent );
if ( v.type() == QVariant::PointF )
return QVariant( v.toPointF().y() );
else
return QVariant();
}
QVariant fcnGeomArea( const QVariantList& , QgsFeature* f, QgsExpression* parent )
{
ENSURE_GEOM_TYPE( f, g, QGis::Polygon );
QgsDistanceArea* calc = parent->geomCalculator();
return QVariant( calc->measure( f->geometry() ) );
}
QVariant fcnGeomLength( const QVariantList& , QgsFeature* f, QgsExpression* parent )
{
ENSURE_GEOM_TYPE( f, g, QGis::Line );
QgsDistanceArea* calc = parent->geomCalculator();
return QVariant( calc->measure( f->geometry() ) );
}
QVariant fcnGeomPerimeter( const QVariantList& , QgsFeature* f, QgsExpression* parent )
{
ENSURE_GEOM_TYPE( f, g, QGis::Polygon );
QgsDistanceArea* calc = parent->geomCalculator();
return QVariant( calc->measurePerimeter( f->geometry() ) );
}
typedef QgsExpression::FunctionDef FnDef;
FnDef QgsExpression::BuiltinFunctions[] =
{
// math
FnDef( "sqrt", 1, fcnSqrt ),
FnDef( "sin", 1, fcnSin ),
FnDef( "cos", 1, fcnCos ),
FnDef( "tan", 1, fcnTan ),
FnDef( "asin", 1, fcnAsin ),
FnDef( "acos", 1, fcnAcos ),
FnDef( "atan", 1, fcnAtan ),
FnDef( "atan2", 2, fcnAtan2 ),
// casts
FnDef( "toint", 1, fcnToInt ),
FnDef( "toreal", 1, fcnToReal ),
FnDef( "tostring", 1, fcnToString ),
// string manipulation
FnDef( "lower", 1, fcnLower ),
FnDef( "upper", 1, fcnUpper ),
FnDef( "length", 1, fcnLength ),
FnDef( "replace", 3, fcnReplace ),
FnDef( "regexp_replace", 3, fcnRegexpReplace ),
FnDef( "substr", 3, fcnSubstr ),
// geometry accessors
FnDef( "xat", 1, fcnXat, true ),
FnDef( "yat", 1, fcnYat, true ),
// special columns
FnDef( "$rownum", 0, fcnRowNumber ),
FnDef( "$area", 0, fcnGeomArea, true ),
FnDef( "$length", 0, fcnGeomLength, true ),
FnDef( "$perimeter", 0, fcnGeomPerimeter, true ),
FnDef( "$x", 0, fcnX, true ),
FnDef( "$y", 0, fcnY, true ),
FnDef( "$id", 0, fcnFeatureId ),
};
bool QgsExpression::isFunctionName( QString name )
{
return functionIndex( name ) != -1;
}
int QgsExpression::functionIndex( QString name )
{
int count = sizeof( BuiltinFunctions ) / sizeof( FunctionDef );
for ( int i = 0; i < count; i++ )
{
if ( QString::compare( name, BuiltinFunctions[i].mName, Qt::CaseInsensitive ) == 0 )
return i;
}
return -1;
}
QgsExpression::QgsExpression( const QString& expr )
: mExpression( expr ), mRowNumber( 0 ), mCalc( NULL )
{
mRootNode = ::parseExpression( mExpression, mParserErrorString );
if ( mParserErrorString.isNull() )
{
Q_ASSERT( mRootNode != NULL );
}
}
QgsExpression::~QgsExpression()
{
delete mRootNode;
delete mCalc;
}
QStringList QgsExpression::referencedColumns()
{
if ( !mRootNode )
return QStringList();
QStringList columns = mRootNode->referencedColumns();
// filter out duplicates
for ( int i = 0; i < columns.count(); i++ )
{
QString col = columns.at( i );
for ( int j = i + 1; j < columns.count(); j++ )
{
if ( QString::compare( col, columns[j], Qt::CaseInsensitive ) == 0 )
{
// this column is repeated: remove it!
columns.removeAt( j-- );
}
}
}
return columns;
}
bool QgsExpression::needsGeometry()
{
if ( !mRootNode )
return false;
return mRootNode->needsGeometry();
}
void QgsExpression::initGeomCalculator()
{
mCalc = new QgsDistanceArea;
mCalc->setProjectionsEnabled( false );
QSettings settings;
QString ellipsoid = settings.value( "/qgis/measure/ellipsoid", "WGS84" ).toString();
mCalc->setEllipsoid( ellipsoid );
}
bool QgsExpression::prepare( const QgsFieldMap& fields )
{
mEvalErrorString = QString();
if ( !mRootNode )
{
mEvalErrorString = "No root node! Parsing failed?";
return false;
}
return mRootNode->prepare( this, fields );
}
QVariant QgsExpression::evaluate( QgsFeature* f )
{
mEvalErrorString = QString();
if ( !mRootNode )
{
mEvalErrorString = "No root node! Parsing failed?";
return QVariant();
}
QVariant res = mRootNode->eval( this, f );
if ( res.canConvert<TVL>() )
{
// convert 3-value logic to int (0/1) or null
return res.value<TVL>().toVariant();
}
return res;
}
QVariant QgsExpression::evaluate( QgsFeature* f, const QgsFieldMap& fields )
{
// first prepare
bool res = prepare( fields );
if ( !res )
return QVariant();
// then evaluate
return evaluate( f );
}
QString QgsExpression::dump() const
{
if ( !mRootNode ) return "(no root)";
return mRootNode->dump();
}
///////////////////////////////////////////////
// nodes
QString QgsExpression::NodeList::dump() const
{
QString msg; bool first = true;
foreach( Node* n, mList )
{
if ( !first ) msg += ", "; else first = false;
msg += n->dump();
}
return msg;
}
//
QVariant QgsExpression::NodeUnaryOperator::eval( QgsExpression* parent, QgsFeature* f )
{
QVariant val = mOperand->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
switch ( mOp )
{
case uoNot:
if ( isLogic( val ) )
val.setValue( ! qvLogic( val ) );
else
SET_EVAL_ERROR( "NOT applicable only on boolean" );
break;
case uoMinus:
if ( isInt( val ) )
val.setValue( - qvInt( val ) );
else if ( isDouble( val ) )
val.setValue( - qvDouble( val ) );
else
SET_EVAL_ERROR( "Unary minus only for numeric values." );
break;
default:
Q_ASSERT( 0 && "unknown unary operation" );
}
return val;
}
bool QgsExpression::NodeUnaryOperator::prepare( QgsExpression* parent, const QgsFieldMap& fields )
{
return mOperand->prepare( parent, fields );
}
QString QgsExpression::NodeUnaryOperator::dump() const
{
return QString( "%1 %2" ).arg( UnaryOperatorText[mOp] ).arg( mOperand->dump() );
}
//
QVariant QgsExpression::NodeBinaryOperator::eval( QgsExpression* parent, QgsFeature* f )
{
QVariant vL = mOpLeft->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
QVariant vR = mOpRight->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
switch ( mOp )
{
case boPlus:
case boMinus:
case boMul:
case boDiv:
case boMod:
if ( isNull( vL ) || isNull( vR ) )
return QVariant();
else if ( isNumeric( vL ) && isNumeric( vR ) )
{
if ( isInt( vL ) && isInt( vR ) )
return QVariant( computeInt( qvInt( vL ), qvInt( vR ) ) );
else
return QVariant( computeDouble( qvDouble( vL ), qvDouble( vR ) ) );
}
else
SET_EVAL_ERROR( "Arithmetic possible only with numeric values" );
case boPow:
if ( isNull( vL ) || isNull( vR ) )
return QVariant();
else if ( isNumeric( vL ) && isNumeric( vR ) )
return QVariant( pow( qvDouble( vL ), qvDouble( vR ) ) );
else
SET_EVAL_ERROR( "Arithmetic possible only with numeric values" );
case boAnd:
if ( isLogic( vL ) && isLogic( vR ) )
return QVariant::fromValue( qvLogic( vL ) & qvLogic( vR ) );
else
SET_EVAL_ERROR( "AND applicable only on boolean" );
case boOr:
if ( isLogic( vL ) && isLogic( vR ) )
return QVariant::fromValue( qvLogic( vL ) | qvLogic( vR ) );
else
SET_EVAL_ERROR( "OR applicable only on boolean" );
case boEQ:
case boNE:
case boLT:
case boGT:
case boLE:
case boGE:
if ( isNull( vL ) || isNull( vR ) )
{
return TVL_Unknown;
}
else if ( isNumeric( vL ) && isNumeric( vR ) )
{
double diff = qvDouble( vL ) - qvDouble( vR );
return compare( diff ) ? TVL_True : TVL_False;
}
else if ( isString( vL ) && isString( vR ) )
{
int diff = QString::compare( qvString( vL ), qvString( vR ) );
return compare( diff ) ? TVL_True : TVL_False;
}
else
SET_EVAL_ERROR( "Invalid arguments for comparison" );
case boIs:
case boIsNot:
if ( isNull( vL ) && isNull( vR ) ) // both operators null
return ( mOp == boIs ? TVL_True : TVL_False );
else if ( isNull( vL ) || isNull( vR ) ) // one operator null
return ( mOp == boIs ? TVL_False : TVL_True );
else // both operators non-null
{
bool equal = false;
if ( isNumeric( vL ) && isNumeric( vR ) )
equal = qvDouble( vL ) == qvDouble( vR );
else if ( isString( vL ) && isString( vR ) )
equal = QString::compare( qvString( vL ), qvString( vR ) ) == 0;
else
SET_EVAL_ERROR( "Invalid arguments for comparison" );
if ( equal )
return mOp == boIs ? TVL_True : TVL_False;
else
return mOp == boIs ? TVL_False : TVL_True;
}
case boRegexp:
case boLike:
case boILike:
if ( isNull( vL ) || isNull( vR ) )
return TVL_Unknown;
else if ( isString( vL ) && isString( vR ) )
{
QString str = qvString( vL ), regexp = qvString( vR );
// TODO: cache QRegExp in case that regexp is a literal string (i.e. it will stay constant)
bool matches;
if ( mOp == boLike || mOp == boILike ) // change from LIKE syntax to regexp
{
// XXX escape % and _ ???
regexp.replace( "%", ".*" );
regexp.replace( "_", "." );
matches = QRegExp( regexp, mOp == boLike ? Qt::CaseSensitive : Qt::CaseInsensitive ).exactMatch( str );
}
else
{
matches = QRegExp( regexp ).indexIn( str ) != -1;
}
return matches ? TVL_True : TVL_False;
}
else
SET_EVAL_ERROR( "Invalid arguments for regexp" );
case boConcat:
if ( isNull( vL ) || isNull( vR ) )
return QVariant();
else if ( isString( vL ) || isString( vR ) )
{
return QVariant( qvString( vL ) + qvString( vR ) );
}
else
SET_EVAL_ERROR( "Invalid arguments for concatenation" );
default: break;
}
Q_ASSERT( false );
return QVariant();
}
bool QgsExpression::NodeBinaryOperator::compare( double diff )
{
switch ( mOp )
{
case boEQ: return diff == 0;
case boNE: return diff != 0;
case boLT: return diff < 0;
case boGT: return diff > 0;
case boLE: return diff <= 0;
case boGE: return diff >= 0;
default: Q_ASSERT( false ); return false;
}
}
int QgsExpression::NodeBinaryOperator::computeInt( int x, int y )
{
switch ( mOp )
{
case boPlus: return x+y;
case boMinus: return x-y;
case boMul: return x*y;
case boDiv: return x/y;
case boMod: return x%y;
default: Q_ASSERT( false ); return 0;
}
}
double QgsExpression::NodeBinaryOperator::computeDouble( double x, double y )
{
switch ( mOp )
{
case boPlus: return x+y;
case boMinus: return x-y;
case boMul: return x*y;
case boDiv: return x/y;
case boMod: return fmod( x,y );
default: Q_ASSERT( false ); return 0;
}
}
bool QgsExpression::NodeBinaryOperator::prepare( QgsExpression* parent, const QgsFieldMap& fields )
{
bool resL = mOpLeft->prepare( parent, fields );
bool resR = mOpRight->prepare( parent, fields );
return resL && resR;
}
QString QgsExpression::NodeBinaryOperator::dump() const
{
return QString( "%1 %2 %3" ).arg( mOpLeft->dump() ).arg( BinaryOperatorText[mOp] ).arg( mOpRight->dump() );
}
//
QVariant QgsExpression::NodeInOperator::eval( QgsExpression* parent, QgsFeature* f )
{
if ( mList->count() == 0 )
return mNotIn ? TVL_True : TVL_False;
QVariant v1 = mNode->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
if ( isNull( v1 ) )
return TVL_Unknown;
bool listHasNull = false;
foreach( Node* n, mList->list() )
{
QVariant v2 = n->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
if ( isNull( v2 ) )
listHasNull = true;
else
{
bool equal = false;
// check whether they are equal
if ( isNumeric( v1 ) && isNumeric( v2 ) )
equal = ( qvDouble( v1 ) == qvDouble( v2 ) );
else if ( isString( v1 ) && isString( v2 ) )
equal = ( QString::compare( qvString( v1 ), qvString( v2 ) ) == 0 );
else
SET_EVAL_ERROR( "Invalid arguments for comparison (IN operator)" );
if ( equal ) // we know the result
return mNotIn ? TVL_False : TVL_True;
}
}
// item not found
if ( listHasNull )
return TVL_Unknown;
else
return mNotIn ? TVL_True : TVL_False;
}
bool QgsExpression::NodeInOperator::prepare( QgsExpression* parent, const QgsFieldMap& fields )
{
bool res = true;
foreach( Node* n, mList->list() )
{
res = res && n->prepare( parent, fields );
}
return res;
}
QString QgsExpression::NodeInOperator::dump() const
{
return QString( "%1 IN (%2)" ).arg( mNode->dump() ).arg( mList->dump() );
}
//
QVariant QgsExpression::NodeFunction::eval( QgsExpression* parent, QgsFeature* f )
{
const FunctionDef& fd = BuiltinFunctions[mFnIndex];
// evaluate arguments
QVariantList argValues;
if ( mArgs )
{
foreach( Node* n, mArgs->list() )
{
argValues.append( n->eval( parent, f ) );
ENSURE_NO_EVAL_ERROR;
}
}
// run the function
QVariant res = fd.mFcn( argValues, f, parent );
ENSURE_NO_EVAL_ERROR;
// everything went fine
return res;
}
bool QgsExpression::NodeFunction::prepare( QgsExpression* parent, const QgsFieldMap& fields )
{
bool res = true;
foreach( Node* n, mArgs->list() )
{
res = res && n->prepare( parent, fields );
}
return res;
}
QString QgsExpression::NodeFunction::dump() const
{
const FnDef& fd = BuiltinFunctions[mFnIndex];
if ( fd.mParams == 0 )
return fd.mName; // special column
else
return QString( "%1(%2)" ).arg( fd.mName ).arg( mArgs->dump() ); // function
}
//
QVariant QgsExpression::NodeLiteral::eval( QgsExpression* , QgsFeature* )
{
return mValue;
}
bool QgsExpression::NodeLiteral::prepare( QgsExpression* /*parent*/, const QgsFieldMap& /*fields*/ )
{
return true;
}
QString QgsExpression::NodeLiteral::dump() const
{
switch ( mValue.type() )
{
case QVariant::Invalid: return "NULL";
case QVariant::Int: return QString::number( mValue.toInt() );
case QVariant::Double: return QString::number( mValue.toDouble() );
case QVariant::String: return QString( "'%1'" ).arg( mValue.toString() );
default: return "[unsupported value]";
}
}
//
QVariant QgsExpression::NodeColumnRef::eval( QgsExpression* /*parent*/, QgsFeature* f )
{
return f->attributeMap()[mIndex];
}
bool QgsExpression::NodeColumnRef::prepare( QgsExpression* parent, const QgsFieldMap& fields )
{
foreach( int i, fields.keys() )
{
if ( QString::compare( fields[i].name(), mName, Qt::CaseInsensitive ) == 0 )
{
mIndex = i;
return true;
}
}
parent->mEvalErrorString = QString( "Column %1 not found" ).arg( mName );
mIndex = -1;
return false;
}
QString QgsExpression::NodeColumnRef::dump() const
{
return mName;
}

318
src/core/qgsexpression.h Normal file
View File

@ -0,0 +1,318 @@
/***************************************************************************
qgsexpression.h
-------------------
begin : August 2011
copyright : (C) 2011 Martin Dobias
email : wonder.sk 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 QGSEXPRESSION_H
#define QGSEXPRESSION_H
#include <QStringList>
#include <QVariant>
#include "qgsfield.h"
class QgsDistanceArea;
class QgsFeature;
/**
Class for parsing and evaluation of expressions (formerly called "search strings")
Usage:
QgsExpression exp("gid*2 > 10 and type not in ('D','F');
if (exp.hasParserError())
{
// show error message with parserErrorString() and exit
}
QVariant result = exp.evaluate(feature, fields);
if (exp.hasEvalError())
{
// show error message with evalErrorString()
}
else
{
// examine the result
}
Possible QVariant value types:
- invalid (null)
- int
- double
- string
For better performance with many evaluations you may first call prepare(fields) function
to find out indices of columns and then repeatedly call evaluate(feature).
@note added in 2.0
*/
class QgsExpression
{
public:
QgsExpression( const QString& expr );
~QgsExpression();
//! Returns true if an error occurred when parsing the input expression
bool hasParserError() const { return !mParserErrorString.isNull(); }
//! Returns parser error
QString parserErrorString() const { return mParserErrorString; }
//! Get the expression ready for evaluation - find out column indexes.
bool prepare( const QgsFieldMap& fields );
//! Get list of columns referenced by the expression
QStringList referencedColumns();
//! Returns true if the expression uses feature geometry for some computation
bool needsGeometry();
// evaluation
//! Evaluate the feature and return the result
//! @note prepare() should be called before calling this method
QVariant evaluate( QgsFeature* f = NULL );
//! Evaluate the feature and return the result
//! @note this method does not expect that prepare() has been called on this instance
QVariant evaluate( QgsFeature* f, const QgsFieldMap& fields );
//! 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; }
//! Set the number for $rownum special column
void setCurrentRowNumber( int rowNumber ) { mRowNumber = rowNumber; }
//! Return the number used for $rownum special column
int currentRowNumber() { return mRowNumber; }
//! Return the parsed expression as a string - useful for debugging
QString dump() const;
//! Return calculator used for distance and area calculations
//! (used by internal functions)
QgsDistanceArea* geomCalculator() { if ( mCalc == NULL ) initGeomCalculator(); return mCalc; }
//
enum UnaryOperator
{
uoNot,
uoMinus,
};
enum BinaryOperator
{
// logical
boOr,
boAnd,
// comparison
boEQ, // =
boNE, // <>
boLE, // <=
boGE, // >=
boLT, // <
boGT, // >
boRegexp,
boLike,
boILike,
boIs,
boIsNot,
// math
boPlus,
boMinus,
boMul,
boDiv,
boMod,
boPow,
// strings
boConcat,
};
static const char* BinaryOperatorText[];
static const char* UnaryOperatorText[];
typedef QVariant( *FcnEval )( const QVariantList& values, QgsFeature* f, QgsExpression* parent );
struct FunctionDef
{
FunctionDef( QString fnname, int params, FcnEval fcn, bool usesGeometry = false )
: mName( fnname ), mParams( params ), mFcn( fcn ), mUsesGeometry( usesGeometry ) {}
QString mName;
int mParams;
FcnEval mFcn;
bool mUsesGeometry;
};
static FunctionDef BuiltinFunctions[];
// tells whether the identifier is a name of existing function
static bool isFunctionName( QString name );
// return index of the function in BuiltinFunctions array
static int functionIndex( QString name );
//////
class Node
{
public:
virtual ~Node() {}
// abstract virtual eval function
// errors are reported to the parent
virtual QVariant eval( QgsExpression* parent, QgsFeature* f ) = 0;
// abstract virtual preparation function
// errors are reported to the parent
virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ) = 0;
virtual QString dump() const = 0;
virtual QStringList referencedColumns() const = 0;
virtual bool needsGeometry() const = 0;
};
class NodeList
{
public:
NodeList() {}
~NodeList() { foreach( Node* n, mList ) delete n; }
void append( Node* node ) { mList.append( node ); }
int count() { return mList.count(); }
QList<Node*> list() { return mList; }
virtual QString dump() const;
protected:
QList<Node*> mList;
};
class NodeUnaryOperator : public Node
{
public:
NodeUnaryOperator( UnaryOperator op, Node* operand ) : mOp( op ), mOperand( operand ) {}
~NodeUnaryOperator() { delete mOperand; }
virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields );
virtual QVariant eval( QgsExpression* parent, QgsFeature* f );
virtual QString dump() const;
virtual QStringList referencedColumns() const { return mOperand->referencedColumns(); }
virtual bool needsGeometry() const { return mOperand->needsGeometry(); }
protected:
UnaryOperator mOp;
Node* mOperand;
};
class NodeBinaryOperator : public Node
{
public:
NodeBinaryOperator( BinaryOperator op, Node* opLeft, Node* opRight ) : mOp( op ), mOpLeft( opLeft ), mOpRight( opRight ) {}
~NodeBinaryOperator() { delete mOpLeft; delete mOpRight; }
virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields );
virtual QVariant eval( QgsExpression* parent, QgsFeature* f );
virtual QString dump() const;
virtual QStringList referencedColumns() const { return mOpLeft->referencedColumns() + mOpRight->referencedColumns(); }
virtual bool needsGeometry() const { return mOpLeft->needsGeometry() || mOpRight->needsGeometry(); }
protected:
bool compare( double diff );
int computeInt( int x, int y );
double computeDouble( double x, double y );
BinaryOperator mOp;
Node* mOpLeft;
Node* mOpRight;
};
class NodeInOperator : public Node
{
public:
NodeInOperator( Node* node, NodeList* list, bool notin = false ) : mNode( node ), mList( list ), mNotIn( notin ) {}
~NodeInOperator() { delete mNode; delete mList; }
virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields );
virtual QVariant eval( QgsExpression* parent, QgsFeature* f );
virtual QString dump() const;
virtual QStringList referencedColumns() const { QStringList lst( mNode->referencedColumns() ); foreach( Node* n, mList->list() ) lst.append( n->referencedColumns() ); return lst; }
virtual bool needsGeometry() const { bool needs = false; foreach( Node* n, mList->list() ) needs |= n->needsGeometry(); return needs; }
protected:
Node* mNode;
NodeList* mList;
bool mNotIn;
};
class NodeFunction : public Node
{
public:
NodeFunction( int fnIndex, NodeList* args ): mFnIndex( fnIndex ), mArgs( args ) {}
//NodeFunction( QString name, NodeList* args ) : mName(name), mArgs(args) {}
~NodeFunction() { delete mArgs; }
virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields );
virtual QVariant eval( QgsExpression* parent, QgsFeature* f );
virtual QString dump() const;
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 { return BuiltinFunctions[mFnIndex].mUsesGeometry; }
protected:
//QString mName;
int mFnIndex;
NodeList* mArgs;
};
class NodeLiteral : public Node
{
public:
NodeLiteral( QVariant value ) : mValue( value ) {}
virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields );
virtual QVariant eval( QgsExpression* parent, QgsFeature* f );
virtual QString dump() const;
virtual QStringList referencedColumns() const { return QStringList(); }
virtual bool needsGeometry() const { return false; }
protected:
QVariant mValue;
};
class NodeColumnRef : public Node
{
public:
NodeColumnRef( QString name ) : mName( name ), mIndex( -1 ) {}
virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields );
virtual QVariant eval( QgsExpression* parent, QgsFeature* f );
virtual QString dump() const;
virtual QStringList referencedColumns() const { return QStringList( mName ); }
virtual bool needsGeometry() const { return false; }
protected:
QString mName;
int mIndex;
};
protected:
QString mExpression;
Node* mRootNode;
QString mParserErrorString;
QString mEvalErrorString;
int mRowNumber;
void initGeomCalculator();
QgsDistanceArea* mCalc;
};
#endif // QGSEXPRESSION_H

View File

@ -0,0 +1,171 @@
/***************************************************************************
qgsexpressionlexer.ll
--------------------
begin : August 2011
copyright : (C) 2011 by Martin Dobias
email : wonder.sk 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. *
* *
***************************************************************************/
%option noyywrap
%option case-insensitive
%option never-interactive
%option nounput
%option prefix="exp_"
// ensure that lexer will be 8-bit (and not just 7-bit)
%option 8bit
%{
#include <stdlib.h> // atof()
#include "qgsexpression.h"
#include "qgsexpressionparser.hpp"
#include <QRegExp>
// if not defined, searches for isatty()
// which doesn't in MSVC compiler
#define YY_NEVER_INTERACTIVE 1
#ifndef YY_NO_UNPUT
#define YY_NO_UNPUT // unused
#endif
#ifdef _MSC_VER
#define YY_NO_UNISTD_H
#endif
#define B_OP(x) exp_lval.b_op = QgsExpression::x
#define U_OP(x) exp_lval.u_op = QgsExpression::x
//#define TEXT exp_lval.text = new QString(); *exp_lval.text = QString::fromUtf8(yytext);
#define TEXT_FILTER(filter_fn) exp_lval.text = new QString(); *exp_lval.text = filter_fn( QString::fromUtf8(yytext) );
#define TEXT TEXT_FILTER()
static QString stripText(QString text)
{
// strip single quotes on start,end
text = text.mid( 1, text.length() - 2 );
// make single "single quotes" from double "single quotes"
text.replace( QRegExp( "''" ), "'" );
// strip \n \' etc.
int index = 0;
while (( index = text.indexOf( '\\', index ) ) != -1 )
{
text.remove( index, 1 ); // delete backslash
QChar chr;
switch ( text[index].toLatin1() ) // evaluate backslashed character
{
case 'n': chr = '\n'; break;
case 't': chr = '\t'; break;
case '\\': chr = '\\'; break;
case '\'': chr = '\''; break;
default: chr = '?'; break;
}
text[index++] = chr; // set new character and push index +1
}
return text;
}
static QString stripColumnRef(QString text)
{
// strip double quotes on start,end
text = text.mid( 1, text.length() - 2 );
// make single "double quotes" from double "double quotes"
text.replace( QRegExp( "\"\"" ), "\"" );
return text;
}
%}
white [ \t\r\n]+
non_ascii [\x80-\xFF]
col_first [A-Za-z_]|{non_ascii}
col_next [A-Za-z0-9_]|{non_ascii}
column_ref {col_first}{col_next}*
special_col "$"{column_ref}
col_str_char "\"\""|[^\"]
column_ref_quoted "\""{col_str_char}*"\""
dig [0-9]
num_int {dig}+
num_float {dig}*\.{dig}+([eE][-+]?{dig}+)?
str_char ('')|(\\.)|[^'\\]
string "'"{str_char}*"'"
%%
"NOT" { U_OP(uoNot); return NOT; }
"AND" { B_OP(boAnd); return AND; }
"OR" { B_OP(boOr); return OR; }
"=" { B_OP(boEQ); return EQ; }
"!=" { B_OP(boNE); return NE; }
"<=" { B_OP(boLE); return LE; }
">=" { B_OP(boGE); return GE; }
"<>" { B_OP(boNE); return NE; }
"<" { B_OP(boLT); return LT; }
">" { B_OP(boGT); return GT; }
"~" { B_OP(boRegexp); return REGEXP; }
"LIKE" { B_OP(boLike); return LIKE; }
"ILIKE" { B_OP(boILike); return ILIKE; }
"IS" { B_OP(boIs); return IS; }
"IS NOT" { B_OP(boIsNot); return ISNOT; }
"||" { B_OP(boConcat); return CONCAT; }
"+" { B_OP(boPlus); return PLUS; }
"-" { B_OP(boMinus); return MINUS; }
"*" { B_OP(boMul); return MUL; }
"/" { B_OP(boDiv); return DIV; }
"%" { B_OP(boMod); return MOD; }
"^" { B_OP(boPow); return POW; }
"IN" { return IN; }
"NULL" { return NULLVALUE; }
[()] { return yytext[0]; }
"," { return COMMA; }
{num_float} { exp_lval.numberFloat = atof(yytext); return NUMBER_FLOAT; }
{num_int} { exp_lval.numberInt = atoi(yytext); return NUMBER_INT; }
{string} { TEXT_FILTER(stripText); return STRING; }
{special_col} { TEXT; return SPECIAL_COL; }
{column_ref} { TEXT; return QgsExpression::isFunctionName(*exp_lval.text) ? FUNCTION : COLUMN_REF; }
{column_ref_quoted} { TEXT_FILTER(stripColumnRef); return COLUMN_REF; }
{white} /* skip blanks and tabs */
. { return Unknown_CHARACTER; }
%%
void exp_set_input_buffer(const char* buffer)
{
exp__scan_string(buffer);
}

View File

@ -0,0 +1,232 @@
/***************************************************************************
qgsexpressionparser.yy
--------------------
begin : August 2011
copyright : (C) 2011 by Martin Dobias
email : wonder.sk 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 <qglobal.h>
#include <QList>
#include <cstdlib>
#include "qgsexpression.h"
#ifdef _MSC_VER
# pragma warning( disable: 4065 ) // switch statement contains 'default' but no 'case' labels
# pragma warning( disable: 4702 ) // unreachable code
#endif
// don't redeclare malloc/free
#define YYINCLUDED_STDLIB_H 1
//! from lexer
extern int exp_lex();
extern void exp_set_input_buffer(const char* buffer);
/** returns parsed tree, otherwise returns NULL and sets parserErrorMsg
(interface function to be called from QgsExpression)
*/
QgsExpression::Node* parseExpression(const QString& str, QString& parserErrorMsg);
/** error handler for bison */
void exp_error(const char* msg);
//! varible where the parser error will be stored
QString gExpParserErrorMsg;
QgsExpression::Node* gExpParserRootNode;
// we want verbose error messages
#define YYERROR_VERBOSE 1
#define BINOP(x, y, z) new QgsExpression::NodeBinaryOperator(x, y, z)
%}
%name-prefix "exp_"
%union
{
QgsExpression::Node* node;
QgsExpression::NodeList* nodelist;
double numberFloat;
int numberInt;
QString* text;
QgsExpression::BinaryOperator b_op;
QgsExpression::UnaryOperator u_op;
}
%start root
//
// token definitions
//
// operator tokens
%token <b_op> OR AND EQ NE LE GE LT GT REGEXP LIKE ILIKE IS ISNOT PLUS MINUS MUL DIV MOD CONCAT POW
%token <u_op> NOT
%token IN
// literals
%token <numberFloat> NUMBER_FLOAT
%token <numberInt> NUMBER_INT
%token NULLVALUE
%token <text> STRING COLUMN_REF FUNCTION SPECIAL_COL
%token COMMA
%token Unknown_CHARACTER
//
// definition of non-terminal types
//
%type <node> expression
%type <nodelist> exp_list
// debugging
%error-verbose
//
// operator precedence
//
// left associativity means that 1+2+3 translates to (1+2)+3
// the order of operators here determines their precedence
%left OR
%left AND
%right NOT
%left EQ NE LE GE LT GT REGEXP LIKE ILIKE IS ISNOT IN
%left PLUS MINUS
%left MUL DIV MOD
%right POW
%left CONCAT
%right UMINUS // fictitious symbol (for unary minus)
%left COMMA
%destructor { delete $$; } <node>
%destructor { delete $$; } <nodelist>
%destructor { delete $$; } <text>
%%
root: expression { gExpParserRootNode = $1; }
;
expression:
expression AND expression { $$ = BINOP($2, $1, $3); }
| expression OR expression { $$ = BINOP($2, $1, $3); }
| expression EQ expression { $$ = BINOP($2, $1, $3); }
| expression NE expression { $$ = BINOP($2, $1, $3); }
| expression LE expression { $$ = BINOP($2, $1, $3); }
| expression GE expression { $$ = BINOP($2, $1, $3); }
| expression LT expression { $$ = BINOP($2, $1, $3); }
| expression GT expression { $$ = BINOP($2, $1, $3); }
| expression REGEXP expression { $$ = BINOP($2, $1, $3); }
| expression LIKE expression { $$ = BINOP($2, $1, $3); }
| expression ILIKE expression { $$ = BINOP($2, $1, $3); }
| expression IS expression { $$ = BINOP($2, $1, $3); }
| expression ISNOT expression { $$ = BINOP($2, $1, $3); }
| expression PLUS expression { $$ = BINOP($2, $1, $3); }
| expression MINUS expression { $$ = BINOP($2, $1, $3); }
| expression MUL expression { $$ = BINOP($2, $1, $3); }
| expression DIV expression { $$ = BINOP($2, $1, $3); }
| expression MOD expression { $$ = BINOP($2, $1, $3); }
| expression POW expression { $$ = BINOP($2, $1, $3); }
| expression CONCAT expression { $$ = BINOP($2, $1, $3); }
| NOT expression { $$ = new QgsExpression::NodeUnaryOperator($1, $2); }
| '(' expression ')' { $$ = $2; }
| FUNCTION '(' exp_list ')'
{
int fnIndex = QgsExpression::functionIndex(*$1);
if (fnIndex == -1)
{
// this should not actually happen because already in lexer we check whether an identifier is a known function
// (if the name is not known the token is parsed as a column)
exp_error("Function is not known");
YYERROR;
}
if (QgsExpression::BuiltinFunctions[fnIndex].mParams != $3->count())
{
exp_error("Function is called with wrong number of arguments");
YYERROR;
}
$$ = new QgsExpression::NodeFunction(fnIndex, $3);
delete $1;
}
| expression IN '(' exp_list ')' { $$ = new QgsExpression::NodeInOperator($1, $4, false); }
| expression NOT IN '(' exp_list ')' { $$ = new QgsExpression::NodeInOperator($1, $5, true); }
| PLUS expression %prec UMINUS { $$ = $2; }
| MINUS expression %prec UMINUS { $$ = new QgsExpression::NodeUnaryOperator( QgsExpression::uoMinus, $2); }
// columns
| COLUMN_REF { $$ = new QgsExpression::NodeColumnRef( *$1 ); delete $1; }
// special columns (actually functions with no arguments)
| SPECIAL_COL
{
int fnIndex = QgsExpression::functionIndex(*$1);
if (fnIndex == -1)
{
exp_error("Special column is not known");
YYERROR;
}
$$ = new QgsExpression::NodeFunction( fnIndex, NULL );
delete $1;
}
// literals
| NUMBER_FLOAT { $$ = new QgsExpression::NodeLiteral( QVariant($1) ); }
| NUMBER_INT { $$ = new QgsExpression::NodeLiteral( QVariant($1) ); }
| STRING { $$ = new QgsExpression::NodeLiteral( QVariant(*$1) ); delete $1; }
| NULLVALUE { $$ = new QgsExpression::NodeLiteral( QVariant() ); }
;
exp_list:
exp_list COMMA expression { $$ = $1; $1->append($3); }
| expression { $$ = new QgsExpression::NodeList(); $$->append($1); }
;
%%
// returns parsed tree, otherwise returns NULL and sets parserErrorMsg
QgsExpression::Node* parseExpression(const QString& str, QString& parserErrorMsg)
{
gExpParserRootNode = NULL;
exp_set_input_buffer(str.toUtf8().constData());
int res = exp_parse();
// list should be empty when parsing was OK
if (res == 0) // success?
{
return gExpParserRootNode;
}
else // error?
{
parserErrorMsg = gExpParserErrorMsg;
return NULL;
}
}
void exp_error(const char* msg)
{
gExpParserErrorMsg = msg;
}

View File

@ -67,6 +67,7 @@ ENDMACRO (ADD_QGIS_TEST)
# Tests:
ADD_QGIS_TEST(applicationtest testqgsapplication.cpp)
ADD_QGIS_TEST(expressiontest testqgsexpression.cpp)
ADD_QGIS_TEST(filewritertest testqgsvectorfilewriter.cpp)
ADD_QGIS_TEST(regression992 regression992.cpp)
ADD_QGIS_TEST(regression1141 regression1141.cpp)

View File

@ -0,0 +1,395 @@
/***************************************************************************
test_template.cpp
--------------------------------------
Date : Sun Sep 16 12:22:23 AKDT 2007
Copyright : (C) 2007 by Gary E. Sherman
Email : sherman at mrcc 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 <QtTest>
#include <QObject>
#include <QString>
#include <QObject>
//header for class being tested
#include <qgsexpression.h>
#include <qgsfeature.h>
#include <qgsgeometry.h>
class TestQgsExpression: public QObject
{
Q_OBJECT;
private slots:
void parsing_data()
{
QTest::addColumn<QString>( "string" );
QTest::addColumn<bool>( "valid" );
// invalid strings
QTest::newRow( "empty string" ) << "" << false;
QTest::newRow( "invalid character" ) << "@" << false;
QTest::newRow( "invalid column reference" ) << "my col" << false;
QTest::newRow( "invalid binary operator" ) << "1+" << false;
QTest::newRow( "invalid function no params" ) << "cos" << false;
QTest::newRow( "invalid function not known" ) << "coz(1)" << false;
QTest::newRow( "invalid operator IN" ) << "x in y" << false;
QTest::newRow( "empty node list" ) << "1 in ()" << false;
QTest::newRow( "invalid sqrt params" ) << "sqrt(2,4)" << false;
QTest::newRow( "special column as function" ) << "$id()" << false;
QTest::newRow( "unknown special column" ) << "$idx" << false;
// valid strings
QTest::newRow( "null" ) << "NULL" << true;
QTest::newRow( "int literal" ) << "1" << true;
QTest::newRow( "float literal" ) << "1.23" << true;
QTest::newRow( "string literal" ) << "'test'" << true;
QTest::newRow( "column reference" ) << "my_col" << true;
QTest::newRow( "quoted column" ) << "\"my col\"" << true;
QTest::newRow( "unary minus" ) << "--3" << true;
QTest::newRow( "function" ) << "cos(0)" << true;
QTest::newRow( "function2" ) << "atan2(0,1)" << true;
QTest::newRow( "operator IN" ) << "x in (a,b)" << true;
QTest::newRow( "pow" ) << "2 ^ 8" << true;
QTest::newRow( "$id" ) << "$id + 1" << true;
QTest::newRow( "arithmetics" ) << "1+2*3" << true;
QTest::newRow( "logic" ) << "be or not be" << true;
}
void parsing()
{
QFETCH( QString, string );
QFETCH( bool, valid );
QgsExpression exp( string );
if ( exp.hasParserError() )
qDebug() << "Parser error: " << exp.parserErrorString();
else
qDebug() << "Parsed string: " << exp.dump();
QCOMPARE( !exp.hasParserError(), valid );
}
void evaluation_data()
{
QTest::addColumn<QString>( "string" );
QTest::addColumn<bool>( "evalError" );
QTest::addColumn<QVariant>( "result" );
// literal evaluation
QTest::newRow( "literal null" ) << "NULL" << false << QVariant();
QTest::newRow( "literal int" ) << "123" << false << QVariant( 123 );
QTest::newRow( "literal double" ) << "1.2" << false << QVariant( 1.2 );
QTest::newRow( "literal text" ) << "'hello'" << false << QVariant( "hello" );
// unary minus
QTest::newRow( "unary minus double" ) << "-1.3" << false << QVariant( -1.3 );
QTest::newRow( "unary minus int" ) << "-1" << false << QVariant( -1 );
QTest::newRow( "unary minus text" ) << "-'hello'" << true << QVariant();
QTest::newRow( "unary minus null" ) << "-null" << true << QVariant();
// arithmetics
QTest::newRow( "plus int" ) << "1+3" << false << QVariant( 4 );
QTest::newRow( "plus double" ) << "1+1.3" << false << QVariant( 2.3 );
QTest::newRow( "plus with null" ) << "null+3" << false << QVariant();
QTest::newRow( "plus invalid" ) << "1+'foo'" << true << QVariant();
QTest::newRow( "minus int" ) << "1-3" << false << QVariant( -2 );
QTest::newRow( "mul int" ) << "8*7" << false << QVariant( 56 );
QTest::newRow( "div int" ) << "20/6" << false << QVariant( 3 );
QTest::newRow( "mod int" ) << "20%6" << false << QVariant( 2 );
QTest::newRow( "minus double" ) << "5.2-3.1" << false << QVariant( 2.1 );
QTest::newRow( "mul double" ) << "2.1*5" << false << QVariant( 10.5 );
QTest::newRow( "div double" ) << "11.0/2" << false << QVariant( 5.5 );
QTest::newRow( "mod double" ) << "6.1 % 2.5" << false << QVariant( 1.1 );
QTest::newRow( "pow" ) << "2^8" << false << QVariant( 256. );
// comparison
QTest::newRow( "eq int" ) << "1+1 = 2" << false << QVariant( 1 );
QTest::newRow( "eq double" ) << "3.2 = 2.2+1" << false << QVariant( 1 );
QTest::newRow( "eq string" ) << "'a' = 'b'" << false << QVariant( 0 );
QTest::newRow( "eq null" ) << "2 = null" << false << QVariant();
QTest::newRow( "eq invalid" ) << "'a' = 1" << true << QVariant();
QTest::newRow( "ne int 1" ) << "3 != 4" << false << QVariant( 1 );
QTest::newRow( "ne int 2" ) << "3 != 3" << false << QVariant( 0 );
QTest::newRow( "lt int 1" ) << "3 < 4" << false << QVariant( 1 );
QTest::newRow( "lt int 2" ) << "3 < 3" << false << QVariant( 0 );
QTest::newRow( "gt int 1" ) << "3 > 4" << false << QVariant( 0 );
QTest::newRow( "gt int 2" ) << "3 > 3" << false << QVariant( 0 );
QTest::newRow( "le int 1" ) << "3 <= 4" << false << QVariant( 1 );
QTest::newRow( "le int 2" ) << "3 <= 3" << false << QVariant( 1 );
QTest::newRow( "ge int 1" ) << "3 >= 4" << false << QVariant( 0 );
QTest::newRow( "ge int 2" ) << "3 >= 3" << false << QVariant( 1 );
QTest::newRow( "lt text 1" ) << "'bar' < 'foo'" << false << QVariant( 1 );
QTest::newRow( "lt text 2" ) << "'foo' < 'bar'" << false << QVariant( 0 );
// is, is not
QTest::newRow( "is null,null" ) << "null is null" << false << QVariant( 1 );
QTest::newRow( "is not null,null" ) << "null is not null" << false << QVariant( 0 );
QTest::newRow( "is null" ) << "1 is null" << false << QVariant( 0 );
QTest::newRow( "is not null" ) << "1 is not null" << false << QVariant( 1 );
QTest::newRow( "is int" ) << "1 is 1" << false << QVariant( 1 );
QTest::newRow( "is not int" ) << "1 is not 1" << false << QVariant( 0 );
QTest::newRow( "is text" ) << "'x' is 'y'" << false << QVariant( 0 );
QTest::newRow( "is not text" ) << "'x' is not 'y'" << false << QVariant( 1 );
// logical
QTest::newRow( "T or F" ) << "1=1 or 2=3" << false << QVariant( 1 );
QTest::newRow( "F or F" ) << "1=2 or 2=3" << false << QVariant( 0 );
QTest::newRow( "T and F" ) << "1=1 and 2=3" << false << QVariant( 0 );
QTest::newRow( "T and T" ) << "1=1 and 2=2" << false << QVariant( 1 );
QTest::newRow( "not T" ) << "not 1=1" << false << QVariant( 0 );
QTest::newRow( "not F" ) << "not 2=3" << false << QVariant( 1 );
QTest::newRow( "U or F" ) << "null=1 or 2=3" << false << QVariant();
QTest::newRow( "U and F" ) << "null=1 and 2=3" << false << QVariant( 0 );
QTest::newRow( "invalid and" ) << "'foo' and 2=3" << true << QVariant();
QTest::newRow( "invalid or" ) << "'foo' or 2=3" << true << QVariant();
QTest::newRow( "invalid not" ) << "not 'foo'" << true << QVariant();
// in, not in
QTest::newRow( "in 1" ) << "1 in (1,2,3)" << false << QVariant( 1 );
QTest::newRow( "in 2" ) << "1 in (1,null,3)" << false << QVariant( 1 );
QTest::newRow( "in 3" ) << "1 in (null,2,3)" << false << QVariant();
QTest::newRow( "in 4" ) << "null in (1,2,3)" << false << QVariant();
QTest::newRow( "not in 1" ) << "1 not in (1,2,3)" << false << QVariant( 0 );
QTest::newRow( "not in 2" ) << "1 not in (1,null,3)" << false << QVariant( 0 );
QTest::newRow( "not in 3" ) << "1 not in (null,2,3)" << false << QVariant();
QTest::newRow( "not in 4" ) << "null not in (1,2,3)" << false << QVariant();
// regexp, like
QTest::newRow( "like 1" ) << "'hello' like '%ll_'" << false << QVariant( 1 );
QTest::newRow( "like 2" ) << "'hello' like 'lo'" << false << QVariant( 0 );
QTest::newRow( "like 3" ) << "'hello' like '%LO'" << false << QVariant( 0 );
QTest::newRow( "ilike" ) << "'hello' ilike '%LO'" << false << QVariant( 1 );
QTest::newRow( "regexp 1" ) << "'hello' ~ 'll'" << false << QVariant( 1 );
QTest::newRow( "regexp 2" ) << "'hello' ~ '^ll'" << false << QVariant( 0 );
QTest::newRow( "regexp 3" ) << "'hello' ~ 'llo$'" << false << QVariant( 1 );
// concatenation
QTest::newRow( "concat" ) << "'a' || 'b'" << false << QVariant( "ab" );
QTest::newRow( "concat with int" ) << "'a' || 1" << false << QVariant( "a1" );
QTest::newRow( "concat with int" ) << "2 || 'b'" << false << QVariant( "2b" );
QTest::newRow( "concat with null" ) << "'a' || null" << false << QVariant();
QTest::newRow( "invalid concat" ) << "1 || 2" << true << QVariant();
// math functions
QTest::newRow( "sqrt" ) << "sqrt(16)" << false << QVariant( 4. );
QTest::newRow( "invalid sqrt value" ) << "sqrt('a')" << true << QVariant();
QTest::newRow( "sin 0" ) << "sin(0)" << false << QVariant( 0. );
QTest::newRow( "cos 0" ) << "cos(0)" << false << QVariant( 1. );
QTest::newRow( "tan 0" ) << "tan(0)" << false << QVariant( 0. );
QTest::newRow( "asin 0" ) << "asin(0)" << false << QVariant( 0. );
QTest::newRow( "acos 1" ) << "acos(1)" << false << QVariant( 0. );
QTest::newRow( "atan 0" ) << "atan(0)" << false << QVariant( 0. );
QTest::newRow( "atan2(0,1)" ) << "atan2(0,1)" << false << QVariant( 0. );
QTest::newRow( "atan2(1,0)" ) << "atan2(1,0)" << false << QVariant( M_PI / 2 );
// cast functions
QTest::newRow( "double to int" ) << "toint(3.2)" << false << QVariant( 3 );
QTest::newRow( "text to int" ) << "toint('53')" << false << QVariant( 53 );
QTest::newRow( "null to int" ) << "toint(null)" << false << QVariant();
QTest::newRow( "int to double" ) << "toreal(3)" << false << QVariant( 3. );
QTest::newRow( "text to double" ) << "toreal('53.1')" << false << QVariant( 53.1 );
QTest::newRow( "null to double" ) << "toreal(null)" << false << QVariant();
QTest::newRow( "int to text" ) << "tostring(6)" << false << QVariant( "6" );
QTest::newRow( "double to text" ) << "tostring(1.23)" << false << QVariant( "1.23" );
QTest::newRow( "null to text" ) << "tostring(null)" << false << QVariant();
// string functions
QTest::newRow( "lower" ) << "lower('HeLLo')" << false << QVariant( "hello" );
QTest::newRow( "upper" ) << "upper('HeLLo')" << false << QVariant( "HELLO" );
QTest::newRow( "length" ) << "length('HeLLo')" << false << QVariant( 5 );
QTest::newRow( "replace" ) << "replace('HeLLo', 'LL', 'xx')" << false << QVariant( "Hexxo" );
QTest::newRow( "regexp_replace" ) << "regexp_replace('HeLLo','[eL]+', '-')" << false << QVariant( "H-o" );
QTest::newRow( "regexp_replace invalid" ) << "regexp_replace('HeLLo','[[[', '-')" << true << QVariant();
QTest::newRow( "substr" ) << "substr('HeLLo', 3,2)" << false << QVariant( "LL" );
QTest::newRow( "substr outside" ) << "substr('HeLLo', -5,2)" << false << QVariant( "" );
}
void evaluation()
{
QFETCH( QString, string );
QFETCH( bool, evalError );
QFETCH( QVariant, result );
QgsExpression exp( string );
QCOMPARE( exp.hasParserError(), false );
QVariant res = exp.evaluate();
if ( exp.hasEvalError() )
qDebug() << exp.evalErrorString();
//qDebug() << res.type() << " " << result.type();
//qDebug() << "type " << res.typeName();
QCOMPARE( exp.hasEvalError(), evalError );
QCOMPARE( res.type(), result.type() );
switch ( res.type() )
{
case QVariant::Invalid: break; // nothing more to check
case QVariant::Int:
QCOMPARE( res.toInt(), result.toInt() );
break;
case QVariant::Double:
QCOMPARE( res.toDouble(), result.toDouble() );
break;
case QVariant::String:
QCOMPARE( res.toString(), result.toString() );
break;
default:
Q_ASSERT( false ); // should never happen
}
}
void eval_columns()
{
QgsFieldMap fields;
fields[4] = QgsField( "foo", QVariant::Int );
QgsFeature f;
f.addAttribute( 4, QVariant( 20 ) );
// good exp
QgsExpression exp( "foo + 1" );
bool prepareRes = exp.prepare( fields );
QCOMPARE( prepareRes, true );
QCOMPARE( exp.hasEvalError(), false );
QVariant res = exp.evaluate( &f );
QCOMPARE( res.type(), QVariant::Int );
QCOMPARE( res.toInt(), 21 );
// bad exp
QgsExpression exp2( "bar + 1" );
bool prepareRes2 = exp2.prepare( fields );
QCOMPARE( prepareRes2, false );
QCOMPARE( exp2.hasEvalError(), true );
QVariant res2 = exp2.evaluate( &f );
QCOMPARE( res2.type(), QVariant::Invalid );
}
void eval_rownum()
{
QgsExpression exp( "$rownum + 1" );
QVariant v1 = exp.evaluate();
QCOMPARE( v1.toInt(), 1 );
exp.setCurrentRowNumber( 100 );
QVariant v2 = exp.evaluate();
QCOMPARE( v2.toInt(), 101 );
}
void eval_feature_id()
{
QgsFeature f( 100 );
QgsExpression exp( "$id * 2" );
QVariant v = exp.evaluate( &f );
QCOMPARE( v.toInt(), 200 );
}
void referenced_columns()
{
QSet<QString> expectedCols;
expectedCols << "foo" << "bar";
QgsExpression exp( "length(Bar || FOO) = 4 or foo + sqrt(bar) > 0" );
QCOMPARE( exp.hasParserError(), false );
QStringList refCols = exp.referencedColumns();
// make sure we have lower case
QSet<QString> refColsSet;
foreach( QString col, refCols )
refColsSet.insert( col.toLower() );
QCOMPARE( refColsSet, expectedCols );
}
void needs_geometry_data()
{
QTest::addColumn<QString>( "string" );
QTest::addColumn<bool>( "needsGeom" );
// literal evaluation
QTest::newRow( "geom 1" ) << "x > 0" << false;
QTest::newRow( "geom 2" ) << "$x > 0" << true;
QTest::newRow( "geom 2" ) << "xat(0) > 0" << true;
}
void needs_geometry()
{
QFETCH( QString, string );
QFETCH( bool, needsGeom );
QgsExpression exp( string );
QCOMPARE( exp.hasParserError(), false );
QCOMPARE( exp.needsGeometry(), needsGeom );
}
void eval_geometry_data()
{
QTest::addColumn<QString>( "string" );
QTest::addColumn<void*>( "geomptr" );
QTest::addColumn<bool>( "evalError" );
QTest::addColumn<double>( "result" );
QgsPoint point( 123, 456 );
QgsPolyline line;
line << QgsPoint( 1, 1 ) << QgsPoint( 4, 2 ) << QgsPoint( 3, 1 );
QTest::newRow( "geom x" ) << "$x" << ( void* ) QgsGeometry::fromPoint( point ) << false << 123.;
QTest::newRow( "geom y" ) << "$y" << ( void* ) QgsGeometry::fromPoint( point ) << false << 456.;
QTest::newRow( "geom xat" ) << "xat(-1)" << ( void* ) QgsGeometry::fromPolyline( line ) << false << 3.;
QTest::newRow( "geom yat" ) << "yat(1)" << ( void* ) QgsGeometry::fromPolyline( line ) << false << 2.;
}
void eval_geometry()
{
QFETCH( QString, string );
QFETCH( void*, geomptr );
QFETCH( bool, evalError );
QFETCH( double, result );
QgsGeometry* geom = ( QgsGeometry* ) geomptr;
QgsFeature f;
f.setGeometry( geom );
QgsExpression exp( string );
QCOMPARE( exp.hasParserError(), false );
QCOMPARE( exp.needsGeometry(), true );
QVariant out = exp.evaluate( &f );
QCOMPARE( exp.hasEvalError(), evalError );
QCOMPARE( out.toDouble(), result );
}
void eval_geometry_calc()
{
QgsPolyline polyline, polygon_ring;
polyline << QgsPoint( 0, 0 ) << QgsPoint( 10, 0 );
polygon_ring << QgsPoint( 1, 1 ) << QgsPoint( 6, 1 ) << QgsPoint( 6, 6 ) << QgsPoint( 1, 6 ) << QgsPoint( 1, 1 );
QgsPolygon polygon;
polygon << polygon_ring;
QgsFeature fPolygon, fPolyline;
fPolyline.setGeometry( QgsGeometry::fromPolyline( polyline ) );
fPolygon.setGeometry( QgsGeometry::fromPolygon( polygon ) );
QgsExpression exp1( "$area" );
QVariant vArea = exp1.evaluate( &fPolygon );
QCOMPARE( vArea.toDouble(), 25. );
QgsExpression exp2( "$length" );
QVariant vLength = exp2.evaluate( &fPolyline );
QCOMPARE( vLength.toDouble(), 10. );
QgsExpression exp3( "$perimeter" );
QVariant vPerimeter = exp3.evaluate( &fPolygon );
QCOMPARE( vPerimeter.toDouble(), 20. );
}
};
QTEST_MAIN( TestQgsExpression )
#include "moc_testqgsexpression.cxx"