mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
[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:
parent
0053426f97
commit
4d0e59c183
@ -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
904
src/core/qgsexpression.cpp
Normal 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
318
src/core/qgsexpression.h
Normal 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
|
171
src/core/qgsexpressionlexer.ll
Normal file
171
src/core/qgsexpressionlexer.ll
Normal 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);
|
||||
}
|
||||
|
232
src/core/qgsexpressionparser.yy
Normal file
232
src/core/qgsexpressionparser.yy
Normal 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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
395
tests/src/core/testqgsexpression.cpp
Normal file
395
tests/src/core/testqgsexpression.cpp
Normal 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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user