QGIS/src/core/qgsexpression.cpp
2012-08-29 22:59:25 +02:00

2116 lines
62 KiB
C++

/***************************************************************************
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 <QDomDocument>
#include <QSettings>
#include <QDate>
#include <QRegExp>
#include <math.h>
#include <limits>
#include "qgsdistancearea.h"
#include "qgsfeature.h"
#include "qgsgeometry.h"
#include "qgslogger.h"
// from parser
extern QgsExpression::Node* parseExpression( const QString& str, QString& parserErrorMsg );
QgsExpression::Interval::~Interval() {}
QgsExpression::Interval QgsExpression::Interval::invalidInterVal()
{
QgsExpression::Interval inter = QgsExpression::Interval();
inter.setValid( false );
return inter;
}
QgsExpression::Interval QgsExpression::Interval::fromString( QString string )
{
int seconds = 0;
QRegExp rx( "(\\d?\\.?\\d+\\s+[a-z]+)", Qt::CaseInsensitive );
QStringList list;
int pos = 0;
while (( pos = rx.indexIn( string, pos ) ) != -1 )
{
list << rx.cap( 1 );
pos += rx.matchedLength();
}
foreach ( QString match, list )
{
QStringList split = match.split( QRegExp( "\\s+" ) );
bool ok;
int value = split.at( 0 ).toInt( &ok );
if ( !ok )
{
continue;
}
if ( match.contains( "day", Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "day", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "days", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) )
seconds += value * QgsExpression::Interval::DAY;
if ( match.contains( "week", Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "week", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "weeks", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) )
seconds += value * QgsExpression::Interval::WEEKS;
if ( match.contains( "month", Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "month", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "months", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) )
seconds += value * QgsExpression::Interval::MONTHS;
if ( match.contains( "year", Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "year", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "years", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) )
seconds += value * QgsExpression::Interval::YEARS;
if ( match.contains( "second", Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "second", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "seconds", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) )
seconds += value;
if ( match.contains( "minute", Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "minute", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "minutes", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) )
seconds += value * QgsExpression::Interval::MINUTE;
if ( match.contains( "hour", Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "hour", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) ||
match.contains( QObject::tr( "hours", "Note: Word is part matched in code" ), Qt::CaseInsensitive ) )
seconds += value * QgsExpression::Interval::HOUR;
}
// If we can't parse the string at all then we just return invalid
if ( seconds == 0 )
return QgsExpression::Interval::invalidInterVal();
return QgsExpression::Interval( seconds );
}
bool QgsExpression::Interval::operator==( const QgsExpression::Interval& other ) const
{
return ( mSeconds == other.mSeconds );
}
///////////////////////////////////////////////
// three-value logic
enum TVL
{
False,
True,
Unknown
};
static TVL AND[3][3] =
{
// false true unknown
{ False, False, False }, // false
{ False, True, Unknown }, // true
{ False, Unknown, Unknown } // unknown
};
static TVL OR[3][3] =
{
{ False, True, Unknown }, // false
{ True, True, True }, // true
{ Unknown, True, Unknown } // unknown
};
static TVL NOT[3] = { True, False, Unknown };
static QVariant tvl2variant( TVL v )
{
switch ( v )
{
case False: return 0;
case True: return 1;
case Unknown:
default:
return QVariant();
}
}
#define TVL_True QVariant(1)
#define TVL_False QVariant(0)
#define TVL_Unknown QVariant()
///////////////////////////////////////////////
// QVariant checks and conversions
inline bool isIntSafe( const QVariant& v )
{
if ( v.type() == QVariant::Int ) return true;
if ( v.type() == QVariant::Double ) return false;
if ( v.type() == QVariant::String ) { bool ok; v.toString().toInt( &ok ); return ok; }
return false;
}
inline bool isDoubleSafe( const QVariant& v )
{
if ( v.type() == QVariant::Double || v.type() == QVariant::Int ) return true;
if ( v.type() == QVariant::String ) { bool ok; v.toString().toDouble( &ok ); return ok; }
return false;
}
inline bool isDateTimeSafe( const QVariant& v )
{
return v.type() == QVariant::DateTime || v.type() == QVariant::Date ||
v.type() == QVariant::Time;
}
inline bool isIntervalSafe( const QVariant& v )
{
if ( v.canConvert<QgsExpression::Interval>() )
{
return true;
}
if ( v.type() == QVariant::String )
{
return QgsExpression::Interval::fromString( v.toString() ).isValid();
}
return false;
}
inline bool isNull( const QVariant& v ) { return v.isNull(); }
///////////////////////////////////////////////
// evaluation error macros
#define ENSURE_NO_EVAL_ERROR { if (parent->hasEvalError()) return QVariant(); }
#define SET_EVAL_ERROR(x) { parent->setEvalErrorString(x); return QVariant(); }
///////////////////////////////////////////////
// operators
const char* QgsExpression::BinaryOperatorText[] =
{
"OR", "AND",
"=", "<>", "<=", ">=", "<", ">", "~", "LIKE", "NOT LIKE", "ILIKE", "NOT ILIKE", "IS", "IS NOT",
"+", "-", "*", "/", "%", "^",
"||"
};
const char* QgsExpression::BinaryOgcOperatorText[] =
{
"Or", "And",
"PropertyIsEqualTo", "PropertyIsNotEqualTo",
"PropertyIsLessThanOrEqualTo", "PropertyIsGreaterThanOrEqualTo",
"PropertyIsLessThan", "PropertyIsGreaterThan",
"", "PropertyIsLike", "", "", "",
"Add", "Sub", "Mul", "Div", "", "",
""
};
const char* QgsExpression::UnaryOperatorText[] =
{
"NOT", "-"
};
const char* QgsExpression::UnaryOgcOperatorText[] =
{
"Not", ""
};
///////////////////////////////////////////////
// functions
// implicit conversion to string
static QString getStringValue( const QVariant& value, QgsExpression* )
{
return value.toString();
}
static double getDoubleValue( const QVariant& value, QgsExpression* parent )
{
bool ok;
double x = value.toDouble( &ok );
if ( !ok )
{
parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to double" ).arg( value.toString() ) );
return 0;
}
return x;
}
static int getIntValue( const QVariant& value, QgsExpression* parent )
{
bool ok;
qint64 x = value.toLongLong( &ok );
if ( ok && x >= std::numeric_limits<int>::min() && x <= std::numeric_limits<int>::max() )
{
return x;
}
else
{
parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to int" ).arg( value.toString() ) );
return 0;
}
}
static QDateTime getDateTimeValue( const QVariant& value, QgsExpression* parent )
{
QDateTime d = value.toDateTime();
if ( d.isValid() )
{
return d;
}
else
{
parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to DateTime" ).arg( value.toString() ) );
return QDateTime();
}
}
static QDate getDateValue( const QVariant& value, QgsExpression* parent )
{
QDate d = value.toDate();
if ( d.isValid() )
{
return d;
}
else
{
parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to Date" ).arg( value.toString() ) );
return QDate();
}
}
static QTime getTimeValue( const QVariant& value, QgsExpression* parent )
{
QTime t = value.toTime();
if ( t.isValid() )
{
return t;
}
else
{
parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to Time" ).arg( value.toString() ) );
return QTime();
}
}
static QgsExpression::Interval getInterval( const QVariant& value, QgsExpression* parent, bool report_error = false )
{
if ( value.canConvert<QgsExpression::Interval>() )
return value.value<QgsExpression::Interval>();
QgsExpression::Interval inter = QgsExpression::Interval::fromString( value.toString() );
if ( inter.isValid() )
{
return inter;
}
// If we get here then we can't convert so we just error and return invalid.
if ( report_error )
parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to Interval" ).arg( value.toString() ) );
return QgsExpression::Interval::invalidInterVal();
}
// this handles also NULL values
static TVL getTVLValue( const QVariant& value, QgsExpression* parent )
{
// we need to convert to TVL
if ( value.isNull() )
return Unknown;
if ( value.type() == QVariant::Int )
return value.toInt() != 0 ? True : False;
bool ok;
double x = value.toDouble( &ok );
if ( !ok )
{
parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to boolean" ).arg( value.toString() ) );
return Unknown;
}
return x != 0 ? True : False;
}
//////
static QVariant fcnSqrt( const QVariantList& values, QgsFeature* /*f*/, QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
return QVariant( sqrt( x ) );
}
static QVariant fcnSin( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
return QVariant( sin( x ) );
}
static QVariant fcnCos( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
return QVariant( cos( x ) );
}
static QVariant fcnTan( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
return QVariant( tan( x ) );
}
static QVariant fcnAsin( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
return QVariant( asin( x ) );
}
static QVariant fcnAcos( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
return QVariant( acos( x ) );
}
static QVariant fcnAtan( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
return QVariant( atan( x ) );
}
static QVariant fcnAtan2( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double y = getDoubleValue( values.at( 0 ), parent );
double x = getDoubleValue( values.at( 1 ), parent );
return QVariant( atan2( y, x ) );
}
static QVariant fcnExp( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
return QVariant( exp( x ) );
}
static QVariant fcnLn( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
if ( x <= 0 )
return QVariant();
return QVariant( log( x ) );
}
static QVariant fcnLog10( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
if ( x <= 0 )
return QVariant();
return QVariant( log10( x ) );
}
static QVariant fcnLog( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double b = getDoubleValue( values.at( 0 ), parent );
double x = getDoubleValue( values.at( 1 ), parent );
if ( x <= 0 || b <= 0 )
return QVariant();
return QVariant( log( x ) / log( b ) );
}
static QVariant fcnToInt( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
return QVariant( getIntValue( values.at( 0 ), parent ) );
}
static QVariant fcnToReal( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
return QVariant( getDoubleValue( values.at( 0 ), parent ) );
}
static QVariant fcnToString( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
return QVariant( getStringValue( values.at( 0 ), parent ) );
}
static QVariant fcnToDateTime( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
return QVariant( getDateTimeValue( values.at( 0 ), parent ) );
}
static QVariant fcnCoalesce( const QVariantList& values, QgsFeature* , QgsExpression* )
{
foreach ( const QVariant &value, values )
{
if ( value.isNull() )
continue;
return value;
}
return QVariant();
}
static QVariant fcnLower( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
return QVariant( str.toLower() );
}
static QVariant fcnUpper( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
return QVariant( str.toUpper() );
}
static QVariant fcnTitle( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
QStringList elems = str.split( " " );
for ( int i = 0; i < elems.size(); i++ )
{
if ( elems[i].size() > 1 )
elems[i] = elems[i].left( 1 ).toUpper() + elems[i].mid( 1 ).toLower();
}
return QVariant( elems.join( " " ) );
}
static QVariant fcnLength( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
return QVariant( str.length() );
}
static QVariant fcnReplace( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
QString before = getStringValue( values.at( 1 ), parent );
QString after = getStringValue( values.at( 2 ), parent );
return QVariant( str.replace( before, after ) );
}
static QVariant fcnRegexpReplace( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
QString regexp = getStringValue( values.at( 1 ), parent );
QString after = getStringValue( values.at( 2 ), parent );
QRegExp re( regexp );
if ( !re.isValid() )
{
parent->setEvalErrorString( QObject::tr( "Invalid regular expression '%1': %2" ).arg( regexp ).arg( re.errorString() ) );
return QVariant();
}
return QVariant( str.replace( re, after ) );
}
static QVariant fcnSubstr( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
int from = getIntValue( values.at( 1 ), parent );
int len = getIntValue( values.at( 2 ), parent );
return QVariant( str.mid( from -1, len ) );
}
static QVariant fcnRowNumber( const QVariantList& , QgsFeature* , QgsExpression* parent )
{
return QVariant( parent->currentRowNumber() );
}
static QVariant fcnFeatureId( const QVariantList& , QgsFeature* f, QgsExpression* )
{
// TODO: handling of 64-bit feature ids?
return f ? QVariant(( int )f->id() ) : QVariant();
}
static QVariant fcnConcat( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QString concat;
foreach ( const QVariant &value, values )
{
concat += getStringValue( value, parent );
}
return concat;
}
static QVariant fcnStrpos( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QString string = getStringValue( values.at( 0 ), parent );
return string.indexOf( QRegExp( getStringValue( values.at( 1 ), parent ) ) );
}
static QVariant fcnRight( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QString string = getStringValue( values.at( 0 ), parent );
int pos = getIntValue( values.at( 1 ), parent );
return string.right( pos );
}
static QVariant fcnLeft( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QString string = getStringValue( values.at( 0 ), parent );
int pos = getIntValue( values.at( 1 ), parent );
return string.left( pos );
}
static QVariant fcnRPad( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QString string = getStringValue( values.at( 0 ), parent );
int length = getIntValue( values.at( 1 ), parent );
QString fill = getStringValue( values.at( 2 ), parent );
return string.rightJustified( length, fill.at( 0 ), true );
}
static QVariant fcnLPad( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QString string = getStringValue( values.at( 0 ), parent );
int length = getIntValue( values.at( 1 ), parent );
QString fill = getStringValue( values.at( 2 ), parent );
return string.leftJustified( length, fill.at( 0 ), true );
}
static QVariant fcnNow( const QVariantList&, QgsFeature* , QgsExpression * )
{
return QVariant( QDateTime::currentDateTime() );
}
static QVariant fcnToDate( const QVariantList& values, QgsFeature* , QgsExpression * parent )
{
return QVariant( getDateValue( values.at( 0 ), parent ) );
}
static QVariant fcnToTime( const QVariantList& values, QgsFeature* , QgsExpression * parent )
{
return QVariant( getTimeValue( values.at( 0 ), parent ) );
}
static QVariant fcnToInterval( const QVariantList& values, QgsFeature* , QgsExpression * parent )
{
return QVariant::fromValue( getInterval( values.at( 0 ), parent ) );
}
static QVariant fcnAge( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QDateTime d1 = getDateTimeValue( values.at( 0 ), parent );
QDateTime d2 = getDateTimeValue( values.at( 1 ), parent );
int seconds = d2.secsTo( d1 );
return QVariant::fromValue( QgsExpression::Interval( seconds ) );
}
static QVariant fcnDay( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QVariant value = values.at( 0 );
QgsExpression::Interval inter = getInterval( value, parent, false );
if ( inter.isValid() )
{
return QVariant( inter.days() );
}
else
{
QDateTime d1 = getDateTimeValue( value, parent );
return QVariant( d1.date().day() );
}
}
static QVariant fcnYear( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QVariant value = values.at( 0 );
QgsExpression::Interval inter = getInterval( value, parent, false );
if ( inter.isValid() )
{
return QVariant( inter.years() );
}
else
{
QDateTime d1 = getDateTimeValue( value, parent );
return QVariant( d1.date().year() );
}
}
static QVariant fcnMonth( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QVariant value = values.at( 0 );
QgsExpression::Interval inter = getInterval( value, parent, false );
if ( inter.isValid() )
{
return QVariant( inter.months() );
}
else
{
QDateTime d1 = getDateTimeValue( value, parent );
return QVariant( d1.date().month() );
}
}
static QVariant fcnWeek( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QVariant value = values.at( 0 );
QgsExpression::Interval inter = getInterval( value, parent, false );
if ( inter.isValid() )
{
return QVariant( inter.weeks() );
}
else
{
QDateTime d1 = getDateTimeValue( value, parent );
return QVariant( d1.date().weekNumber() );
}
}
static QVariant fcnHour( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QVariant value = values.at( 0 );
QgsExpression::Interval inter = getInterval( value, parent, false );
if ( inter.isValid() )
{
return QVariant( inter.hours() );
}
else
{
QDateTime d1 = getDateTimeValue( value, parent );
return QVariant( d1.time().hour() );
}
}
static QVariant fcnMinute( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QVariant value = values.at( 0 );
QgsExpression::Interval inter = getInterval( value, parent, false );
if ( inter.isValid() )
{
return QVariant( inter.minutes() );
}
else
{
QDateTime d1 = getDateTimeValue( value, parent );
return QVariant( d1.time().minute() );
}
}
static QVariant fcnSeconds( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
QVariant value = values.at( 0 );
QgsExpression::Interval inter = getInterval( value, parent, false );
if ( inter.isValid() )
{
return QVariant( inter.seconds() );
}
else
{
QDateTime d1 = getDateTimeValue( value, parent );
return QVariant( d1.time().second() );
}
}
#define ENSURE_GEOM_TYPE(f, g, geomtype) if (!f) return QVariant(); \
QgsGeometry* g = f->geometry(); \
if (!g || g->type() != geomtype) return QVariant();
static QVariant fcnX( const QVariantList& , QgsFeature* f, QgsExpression* )
{
ENSURE_GEOM_TYPE( f, g, QGis::Point );
if ( g->isMultipart() )
{
return g->asMultiPoint()[ 0 ].x();
}
else
{
return g->asPoint().x();
}
}
static QVariant fcnY( const QVariantList& , QgsFeature* f, QgsExpression* )
{
ENSURE_GEOM_TYPE( f, g, QGis::Point );
if ( g->isMultipart() )
{
return g->asMultiPoint()[ 0 ].y();
}
else
{
return g->asPoint().y();
}
}
static QVariant pointAt( const QVariantList& values, QgsFeature* f, QgsExpression* parent ) // helper function
{
int idx = getIntValue( values.at( 0 ), 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( QObject::tr( "Index is out of range" ) );
return QVariant();
}
return QVariant( QPointF( polyline[idx].x(), polyline[idx].y() ) );
}
static 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();
}
static 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();
}
static 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() ) );
}
static 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() ) );
}
static 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() ) );
}
static QVariant fcnRound( const QVariantList& values , QgsFeature *f, QgsExpression* parent )
{
Q_UNUSED( f );
if ( values.length() == 2 )
{
double number = getDoubleValue( values.at( 0 ), parent );
double scaler = pow( 10.0, getIntValue( values.at( 1 ), parent ) );
return QVariant( round( number * scaler ) / scaler );
}
if ( values.length() == 1 )
{
double number = getIntValue( values.at( 0 ), parent );
return QVariant( round( number ) ).toInt();
}
return QVariant();
}
QList<QgsExpression::FunctionDef> QgsExpression::gmBuiltinFunctions;
const QList<QgsExpression::FunctionDef> &QgsExpression::BuiltinFunctions()
{
if ( gmBuiltinFunctions.isEmpty() )
{
// math
gmBuiltinFunctions
<< FunctionDef( "sqrt", 1, fcnSqrt, QObject::tr( "Math" ) )
<< FunctionDef( "sin", 1, fcnSin, QObject::tr( "Math" ) )
<< FunctionDef( "cos", 1, fcnCos, QObject::tr( "Math" ) )
<< FunctionDef( "tan", 1, fcnTan, QObject::tr( "Math" ) )
<< FunctionDef( "asin", 1, fcnAsin, QObject::tr( "Math" ) )
<< FunctionDef( "acos", 1, fcnAcos, QObject::tr( "Math" ) )
<< FunctionDef( "atan", 1, fcnAtan, QObject::tr( "Math" ) )
<< FunctionDef( "atan2", 2, fcnAtan2, QObject::tr( "Math" ) )
<< FunctionDef( "exp", 1, fcnExp, QObject::tr( "Math" ) )
<< FunctionDef( "ln", 1, fcnLn, QObject::tr( "Math" ) )
<< FunctionDef( "log10", 1, fcnLog10, QObject::tr( "Math" ) )
<< FunctionDef( "log", 2, fcnLog, QObject::tr( "Math" ) )
<< FunctionDef( "round", -1, fcnRound, QObject::tr( "Math" ) )
// casts
<< FunctionDef( "toint", 1, fcnToInt, QObject::tr( "Conversions" ) )
<< FunctionDef( "toreal", 1, fcnToReal, QObject::tr( "Conversions" ) )
<< FunctionDef( "tostring", 1, fcnToString, QObject::tr( "Conversions" ) )
<< FunctionDef( "todatetime", 1, fcnToDateTime, QObject::tr( "Conversions" ) )
<< FunctionDef( "todate", 1, fcnToDate, QObject::tr( "Conversions" ) )
<< FunctionDef( "totime", 1, fcnToTime, QObject::tr( "Conversions" ) )
<< FunctionDef( "tointerval", 1, fcnToInterval, QObject::tr( "Conversions" ) )
// conditionals
<< FunctionDef( "coalesce", -1, fcnCoalesce, QObject::tr( "Conditionals" ) )
// Date and Time
<< FunctionDef( "$now", 0, fcnNow, QObject::tr( "Date and Time" ) )
<< FunctionDef( "age", 2, fcnAge, QObject::tr( "Date and Time" ) )
<< FunctionDef( "year", 1, fcnYear, QObject::tr( "Date and Time" ) )
<< FunctionDef( "month", 1, fcnMonth, QObject::tr( "Date and Time" ) )
<< FunctionDef( "week", 1, fcnWeek, QObject::tr( "Date and Time" ) )
<< FunctionDef( "day", 1, fcnDay, QObject::tr( "Date and Time" ) )
<< FunctionDef( "hour", 1, fcnHour, QObject::tr( "Date and Time" ) )
<< FunctionDef( "minute", 1, fcnMinute, QObject::tr( "Date and Time" ) )
<< FunctionDef( "second", 1, fcnSeconds, QObject::tr( "Date and Time" ) )
// string manipulation
<< FunctionDef( "lower", 1, fcnLower, QObject::tr( "String" ) )
<< FunctionDef( "upper", 1, fcnUpper, QObject::tr( "String" ) )
<< FunctionDef( "title", 1, fcnTitle, QObject::tr( "String" ) )
<< FunctionDef( "length", 1, fcnLength, QObject::tr( "String" ) )
<< FunctionDef( "replace", 3, fcnReplace, QObject::tr( "String" ) )
<< FunctionDef( "regexp_replace", 3, fcnRegexpReplace, QObject::tr( "String" ) )
<< FunctionDef( "substr", 3, fcnSubstr, QObject::tr( "String" ) )
<< FunctionDef( "concat", -1, fcnConcat, QObject::tr( "String" ) )
<< FunctionDef( "strpos", 2, fcnStrpos, QObject::tr( "String" ) )
<< FunctionDef( "left", 2, fcnLeft, QObject::tr( "String" ) )
<< FunctionDef( "right", 2, fcnRight, QObject::tr( "String" ) )
<< FunctionDef( "rpad", 3, fcnRPad, QObject::tr( "String" ) )
<< FunctionDef( "lpad", 3, fcnLPad, QObject::tr( "String" ) )
// geometry accessors
<< FunctionDef( "xat", 1, fcnXat, QObject::tr( "Geometry" ), "", true )
<< FunctionDef( "yat", 1, fcnYat, QObject::tr( "Geometry" ), "", true )
<< FunctionDef( "$area", 0, fcnGeomArea, QObject::tr( "Geometry" ), "", true )
<< FunctionDef( "$length", 0, fcnGeomLength, QObject::tr( "Geometry" ), "", true )
<< FunctionDef( "$perimeter", 0, fcnGeomPerimeter, QObject::tr( "Geometry" ), "", true )
<< FunctionDef( "$x", 0, fcnX, QObject::tr( "Geometry" ), "", true )
<< FunctionDef( "$y", 0, fcnY, QObject::tr( "Geometry" ), "" , true )
// special columns
<< FunctionDef( "$rownum", 0, fcnRowNumber, QObject::tr( "Record" ) )
<< FunctionDef( "$id", 0, fcnFeatureId, QObject::tr( "Record" ) )
;
}
return gmBuiltinFunctions;
}
bool QgsExpression::isFunctionName( QString name )
{
return functionIndex( name ) != -1;
}
int QgsExpression::functionIndex( QString name )
{
int count = functionCount();
for ( int i = 0; i < count; i++ )
{
if ( QString::compare( name, BuiltinFunctions()[i].mName, Qt::CaseInsensitive ) == 0 )
return i;
}
return -1;
}
int QgsExpression::functionCount()
{
return BuiltinFunctions().size();
}
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;
QSettings settings;
QString ellipsoid = settings.value( "/qgis/measure/ellipsoid", GEO_NONE ).toString();
mCalc->setEllipsoid( ellipsoid );
mCalc->setEllipsoidalMode( false );
}
bool QgsExpression::prepare( const QgsFieldMap& fields )
{
mEvalErrorString = QString();
if ( !mRootNode )
{
mEvalErrorString = QObject::tr( "No root node! Parsing failed?" );
return false;
}
return mRootNode->prepare( this, fields );
}
QVariant QgsExpression::evaluate( QgsFeature* f )
{
mEvalErrorString = QString();
if ( !mRootNode )
{
mEvalErrorString = QObject::tr( "No root node! Parsing failed?" );
return QVariant();
}
return mRootNode->eval( this, f );
}
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 QObject::tr( "(no root)" );
return mRootNode->dump();
}
void QgsExpression::toOgcFilter( QDomDocument &doc, QDomElement &element ) const
{
if ( !mRootNode )
return;
mRootNode->toOgcFilter( doc, element );
}
QgsExpression* QgsExpression::createFromOgcFilter( QDomElement &element )
{
if ( element.isNull() || !element.hasChildNodes() )
return NULL;
QgsExpression *expr = new QgsExpression();
QDomElement childElem = element.firstChildElement();
while ( !childElem.isNull() )
{
QString errorMsg;
QgsExpression::Node *node = QgsExpression::Node::createFromOgcFilter( childElem, errorMsg );
if ( !node )
{
// invalid expression, parser error
expr->mParserErrorString = errorMsg;
return expr;
}
// use the concat binary operator to append to the root node
if ( !expr->mRootNode )
{
expr->mRootNode = node;
}
else
{
expr->mRootNode = new QgsExpression::NodeBinaryOperator( boConcat, expr->mRootNode, node );
}
childElem = childElem.nextSiblingElement();
}
return expr;
}
void QgsExpression::acceptVisitor( QgsExpression::Visitor& v )
{
if ( mRootNode )
mRootNode->accept( v );
}
QgsExpression::Node* QgsExpression::Node::createFromOgcFilter( QDomElement &element, QString &errorMessage )
{
if ( element.isNull() )
return NULL;
// check for unary operators
int unaryOpCount = sizeof( UnaryOgcOperatorText ) / sizeof( UnaryOgcOperatorText[0] );
for ( int i = 0; i < unaryOpCount; i++ )
{
QString ogcOperatorName = UnaryOgcOperatorText[ i ];
if ( ogcOperatorName.isEmpty() )
continue;
if ( element.localName() == ogcOperatorName )
{
QgsExpression::Node *node = QgsExpression::NodeUnaryOperator::createFromOgcFilter( element, errorMessage );
if ( node )
return node;
return NULL;
}
}
// check for binary operators
int binaryOpCount = sizeof( BinaryOgcOperatorText ) / sizeof( BinaryOgcOperatorText[0] );
for ( int i = 0; i < binaryOpCount; i++ )
{
QString ogcOperatorName = BinaryOgcOperatorText[ i ];
if ( ogcOperatorName.isEmpty() )
continue;
if ( element.localName() == ogcOperatorName )
{
QgsExpression::Node *node = QgsExpression::NodeBinaryOperator::createFromOgcFilter( element, errorMessage );
if ( node )
return node;
return NULL;
}
}
// check for other OGC operators, convert them to expressions
if ( element.localName() == "PropertyIsNull" )
{
return QgsExpression::NodeBinaryOperator::createFromOgcFilter( element, errorMessage );
}
else if ( element.localName() == "Literal" )
{
return QgsExpression::NodeLiteral::createFromOgcFilter( element, errorMessage );
}
else if ( element.localName() == "Function" )
{
return QgsExpression::NodeFunction::createFromOgcFilter( element, errorMessage );
}
else if ( element.localName() == "PropertyName" )
{
return QgsExpression::NodeColumnRef::createFromOgcFilter( element, errorMessage );
}
else if ( element.localName() == "PropertyIsBetween" )
{
// <ogc:PropertyIsBetween> encode a Range check
QgsExpression::Node *operand = 0, *lowerBound = 0;
QgsExpression::Node *operand2 = 0, *upperBound = 0;
QDomElement operandElem = element.firstChildElement();
while ( !operandElem.isNull() )
{
if ( operandElem.localName() == "LowerBoundary" )
{
QDomElement lowerBoundElem = operandElem.firstChildElement();
lowerBound = createFromOgcFilter( lowerBoundElem, errorMessage );
}
else if ( operandElem.localName() == "UpperBoundary" )
{
QDomElement upperBoundElem = operandElem.firstChildElement();
upperBound = createFromOgcFilter( upperBoundElem, errorMessage );
}
else
{
// <ogc:expression>
// both operand and operand2 contain the same expression,
// they are respectively compared to lower bound and upper bound
operand = createFromOgcFilter( operandElem, errorMessage );
operand2 = createFromOgcFilter( operandElem, errorMessage );
}
if ( operand && lowerBound && operand2 && upperBound )
break;
operandElem = operandElem.nextSiblingElement();
}
if ( !operand || !lowerBound || !operand2 || !upperBound )
{
if ( operand )
delete operand;
if ( lowerBound )
delete lowerBound;
if ( upperBound )
delete upperBound;
errorMessage = "missing some required sub-elements in ogc:PropertyIsBetween";
return NULL;
}
QgsExpression::Node *geOperator = new QgsExpression::NodeBinaryOperator( boGE, operand, lowerBound );
QgsExpression::Node *leOperator = new QgsExpression::NodeBinaryOperator( boLE, operand2, upperBound );
return new QgsExpression::NodeBinaryOperator( boAnd, geOperator, leOperator );
}
errorMessage += QString( "unable to convert '%1' element to a valid expression: it is not supported yet or it has invalid arguments" ).arg( element.tagName() );
return NULL;
}
///////////////////////////////////////////////
// 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;
}
void QgsExpression::NodeList::toOgcFilter( QDomDocument &doc, QDomElement &element ) const
{
foreach ( Node* n, mList )
{
n->toOgcFilter( doc, element );
}
}
//
QVariant QgsExpression::NodeUnaryOperator::eval( QgsExpression* parent, QgsFeature* f )
{
QVariant val = mOperand->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
switch ( mOp )
{
case uoNot:
{
TVL tvl = getTVLValue( val, parent );
ENSURE_NO_EVAL_ERROR;
return tvl2variant( NOT[tvl] );
}
case uoMinus:
if ( isIntSafe( val ) )
return QVariant( - getIntValue( val, parent ) );
else if ( isDoubleSafe( val ) )
return QVariant( - getDoubleValue( val, parent ) );
else
SET_EVAL_ERROR( QObject::tr( "Unary minus only for numeric values." ) );
break;
default:
Q_ASSERT( 0 && "unknown unary operation" );
}
return QVariant();
}
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() );
}
void QgsExpression::NodeUnaryOperator::toOgcFilter( QDomDocument &doc, QDomElement &element ) const
{
QDomElement uoElem;
switch ( mOp )
{
case uoMinus:
uoElem = doc.createElement( "ogc:Literal" );
uoElem.appendChild( doc.createTextNode( "-" ) );
break;
case uoNot:
uoElem = doc.createElement( "ogc:Not" );
break;
default:
element.appendChild( doc.createComment( QString( "Unary operator %1 not implemented yet" ).arg( UnaryOperatorText[mOp] ) ) );
return;
}
mOperand->toOgcFilter( doc, uoElem );
element.appendChild( uoElem );
}
QgsExpression::Node* QgsExpression::NodeUnaryOperator::createFromOgcFilter( QDomElement &element, QString &errorMessage )
{
if ( element.isNull() )
return NULL;
int unaryOpCount = sizeof( UnaryOgcOperatorText ) / sizeof( UnaryOgcOperatorText[0] );
for ( int i = 0; i < unaryOpCount; i++ )
{
QString ogcOperatorName = UnaryOgcOperatorText[ i ];
if ( ogcOperatorName.isEmpty() )
continue;
if ( element.localName() != ogcOperatorName )
continue;
QDomElement operandElem = element.firstChildElement();
QgsExpression::Node* operand = QgsExpression::Node::createFromOgcFilter( operandElem, errorMessage );
if ( !operand )
{
if ( errorMessage.isEmpty() )
errorMessage = QString( "invalid operand for '%1' unary operator" ).arg( ogcOperatorName );
return NULL;
}
return new QgsExpression::NodeUnaryOperator(( UnaryOperator ) i, operand );
}
errorMessage = QString( "%1 unary operator not supported." ).arg( element.tagName() );
return NULL;
}
//
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 ( isIntSafe( vL ) && isIntSafe( vR ) )
{
// both are integers - let's use integer arithmetics
int iL = getIntValue( vL, parent ); ENSURE_NO_EVAL_ERROR;
int iR = getIntValue( vR, parent ); ENSURE_NO_EVAL_ERROR;
if ( mOp == boDiv && iR == 0 ) return QVariant(); // silently handle division by zero and return NULL
return QVariant( computeInt( iL, iR ) );
}
else if ( isDateTimeSafe( vL ) && isIntervalSafe( vR ) )
{
QDateTime dL = getDateTimeValue( vL, parent ); ENSURE_NO_EVAL_ERROR;
QgsExpression::Interval iL = getInterval( vR, parent ); ENSURE_NO_EVAL_ERROR;
if ( mOp == boDiv || mOp == boMul || mOp == boMod )
{
parent->setEvalErrorString( QObject::tr( "Can't preform /, *, or % on DateTime and Interval" ) );
return QVariant();
}
return QVariant( computeDateTimeFromInterval( dL, &iL ) );
}
else
{
// general floating point arithmetic
double fL = getDoubleValue( vL, parent ); ENSURE_NO_EVAL_ERROR;
double fR = getDoubleValue( vR, parent ); ENSURE_NO_EVAL_ERROR;
if ( mOp == boDiv && fR == 0 )
return QVariant(); // silently handle division by zero and return NULL
return QVariant( computeDouble( fL, fR ) );
}
case boPow:
if ( isNull( vL ) || isNull( vR ) )
return QVariant();
else
{
double fL = getDoubleValue( vL, parent ); ENSURE_NO_EVAL_ERROR;
double fR = getDoubleValue( vR, parent ); ENSURE_NO_EVAL_ERROR;
return QVariant( pow( fL, fR ) );
}
case boAnd:
{
TVL tvlL = getTVLValue( vL, parent ), tvlR = getTVLValue( vR, parent );
ENSURE_NO_EVAL_ERROR;
return tvl2variant( AND[tvlL][tvlR] );
}
case boOr:
{
TVL tvlL = getTVLValue( vL, parent ), tvlR = getTVLValue( vR, parent );
ENSURE_NO_EVAL_ERROR;
return tvl2variant( OR[tvlL][tvlR] );
}
case boEQ:
case boNE:
case boLT:
case boGT:
case boLE:
case boGE:
if ( isNull( vL ) || isNull( vR ) )
{
return TVL_Unknown;
}
else if ( isDoubleSafe( vL ) && isDoubleSafe( vR ) )
{
// do numeric comparison if both operators can be converted to numbers
double fL = getDoubleValue( vL, parent ); ENSURE_NO_EVAL_ERROR;
double fR = getDoubleValue( vR, parent ); ENSURE_NO_EVAL_ERROR;
return compare( fL - fR ) ? TVL_True : TVL_False;
}
else
{
// do string comparison otherwise
QString sL = getStringValue( vL, parent ); ENSURE_NO_EVAL_ERROR;
QString sR = getStringValue( vR, parent ); ENSURE_NO_EVAL_ERROR;
int diff = QString::compare( sL, sR );
return compare( diff ) ? TVL_True : TVL_False;
}
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 ( isDoubleSafe( vL ) && isDoubleSafe( vR ) )
{
double fL = getDoubleValue( vL, parent ); ENSURE_NO_EVAL_ERROR;
double fR = getDoubleValue( vR, parent ); ENSURE_NO_EVAL_ERROR;
equal = fL == fR;
}
else
{
QString sL = getStringValue( vL, parent ); ENSURE_NO_EVAL_ERROR;
QString sR = getStringValue( vR, parent ); ENSURE_NO_EVAL_ERROR;
equal = QString::compare( sL, sR ) == 0;
}
if ( equal )
return mOp == boIs ? TVL_True : TVL_False;
else
return mOp == boIs ? TVL_False : TVL_True;
}
case boRegexp:
case boLike:
case boNotLike:
case boILike:
case boNotILike:
if ( isNull( vL ) || isNull( vR ) )
return TVL_Unknown;
else
{
QString str = getStringValue( vL, parent ); ENSURE_NO_EVAL_ERROR;
QString regexp = getStringValue( vR, parent ); ENSURE_NO_EVAL_ERROR;
// TODO: cache QRegExp in case that regexp is a literal string (i.e. it will stay constant)
bool matches;
if ( mOp == boLike || mOp == boILike || mOp == boNotLike || mOp == boNotILike ) // change from LIKE syntax to regexp
{
// XXX escape % and _ ???
regexp.replace( "%", ".*" );
regexp.replace( "_", "." );
matches = QRegExp( regexp, mOp == boLike || mOp == boNotLike ? Qt::CaseSensitive : Qt::CaseInsensitive ).exactMatch( str );
}
else
{
matches = QRegExp( regexp ).indexIn( str ) != -1;
}
if ( mOp == boNotLike || mOp == boNotILike )
{
matches = !matches;
}
return matches ? TVL_True : TVL_False;
}
case boConcat:
if ( isNull( vL ) || isNull( vR ) )
return QVariant();
else
{
QString sL = getStringValue( vL, parent ); ENSURE_NO_EVAL_ERROR;
QString sR = getStringValue( vR, parent ); ENSURE_NO_EVAL_ERROR;
return QVariant( sL + sR );
}
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;
}
}
QDateTime QgsExpression::NodeBinaryOperator::computeDateTimeFromInterval( QDateTime d, QgsExpression::Interval *i )
{
switch ( mOp )
{
case boPlus: return d.addSecs( i->seconds() );
case boMinus: return d.addSecs( -i->seconds() );
default: Q_ASSERT( false ); return QDateTime();
}
}
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() );
}
void QgsExpression::NodeBinaryOperator::toOgcFilter( QDomDocument &doc, QDomElement &element ) const
{
if ( mOp == boConcat )
{
// the concat binary operator must only convert its operands
mOpLeft->toOgcFilter( doc, element );
mOpRight->toOgcFilter( doc, element );
return;
}
if ( mOp == boIs || mOp == boIsNot )
{
// check if one of the operands is NULL
QgsExpression::NodeLiteral *opLeftLiteral = dynamic_cast<QgsExpression::NodeLiteral *>( mOpLeft );
QgsExpression::NodeLiteral *opRightLiteral = dynamic_cast<QgsExpression::NodeLiteral *>( mOpRight );
if ( opLeftLiteral && opLeftLiteral->value().isNull() &&
opRightLiteral && opRightLiteral->value().isNull() )
{
// why could anybody find useful to use NULL IS NULL???
// BTW avoid issues by converting it to 1 = 1
QDomElement eqElem = doc.createElement( "ogc:PropertyIsEqual" );
QDomElement literalElem = doc.createElement( "ogc:Literal" );
literalElem.appendChild( doc.createTextNode( "1" ) );
eqElem.appendChild( literalElem );
literalElem = doc.createElement( "ogc:Literal" );
literalElem.appendChild( doc.createTextNode( "1" ) );
eqElem.appendChild( literalElem );
element.appendChild( eqElem );
}
else if (( opLeftLiteral && opLeftLiteral->value().isNull() ) ||
( opRightLiteral && opRightLiteral->value().isNull() ) )
{
// at least one operand is NULL, use <ogc:PropertyIsNull> element
QDomElement isNullElem = doc.createElement( "ogc:PropertyIsNull" );
QgsExpression::Node *operand = opLeftLiteral->value().isNull() ? mOpRight : mOpLeft;
operand->toOgcFilter( doc, isNullElem );
if ( mOp == boIsNot )
{
// append to <ogc:Not> element if IS NOT operator was required
QDomElement notOpElem = doc.createElement( "ogc:Not" );
notOpElem.appendChild( isNullElem );
element.appendChild( notOpElem );
}
else
{
element.appendChild( isNullElem );
}
}
else
{
// both operands are not null, use <ogc:PropertyIsEqual> element
QDomElement eqElem = doc.createElement( "ogc:PropertyIsEqual" );
mOpLeft->toOgcFilter( doc, eqElem );
mOpRight->toOgcFilter( doc, eqElem );
element.appendChild( eqElem );
}
return;
}
if ( mOp == boILike )
{
// XXX why ogc:PropertyIsLikeType extends ogc:ComparisonOpsType
// which has no matchCase attribute? Shouldn't it be better if
// would extend BinaryComparisonOpType which has that attribute
// and doesn't require to have a ogc:PropertyName as first parameter?
QgsExpression ilikeExpr( QString( "upper( %1 ) LIKE upper( %2 )" ).arg( mOpLeft->dump() ).arg( mOpRight->dump() ) );
ilikeExpr.toOgcFilter( doc, element );
return;
}
QString opText = BinaryOgcOperatorText[mOp];
if ( opText.isEmpty() )
{
// not implemented binary operators
// TODO: regex, % (mod), ^ (pow) are not supported yet
element.appendChild( doc.createComment( QString( "Binary operator %1 not implemented yet" ).arg( BinaryOperatorText[mOp] ) ) );
return;
}
QDomElement boElem = doc.createElement( "ogc:" + opText );
if ( mOp == boLike )
{
// setup wildcards to <ogc:PropertyIsLike>
boElem.setAttribute( "wildCard", "%" );
boElem.setAttribute( "singleChar", "?" );
boElem.setAttribute( "escapeChar", "!" );
}
mOpLeft->toOgcFilter( doc, boElem );
mOpRight->toOgcFilter( doc, boElem );
element.appendChild( boElem );
}
QgsExpression::Node* QgsExpression::NodeBinaryOperator::createFromOgcFilter( QDomElement &element, QString &errorMessage )
{
if ( element.isNull() )
return NULL;
QgsExpression::Node* opLeft = 0;
QgsExpression::Node* opRight = 0;
// convert ogc:PropertyIsNull to IS operator with NULL right operand
if ( element.localName() == "PropertyIsNull" )
{
QDomElement operandElem = element.firstChildElement();
opLeft = QgsExpression::Node::createFromOgcFilter( operandElem, errorMessage );
if ( !opLeft )
return NULL;
opRight = new QgsExpression::NodeLiteral( QVariant() );
return new QgsExpression::NodeBinaryOperator( boIs, opLeft, opRight );
}
// the other binary operators
int binaryOpCount = sizeof( BinaryOgcOperatorText ) / sizeof( BinaryOgcOperatorText[0] );
for ( int i = 0; i < binaryOpCount; i++ )
{
QString ogcOperatorName = BinaryOgcOperatorText[ i ];
if ( ogcOperatorName.isEmpty() )
continue;
if ( element.localName() != ogcOperatorName )
continue;
QDomElement operandElem = element.firstChildElement();
opLeft = QgsExpression::Node::createFromOgcFilter( operandElem, errorMessage );
if ( !opLeft )
{
if ( errorMessage.isEmpty() )
errorMessage = QString( "invalid left operand for '%1' binary operator" ).arg( ogcOperatorName );
break;
}
operandElem = operandElem.nextSiblingElement();
opRight = QgsExpression::Node::createFromOgcFilter( operandElem, errorMessage );
if ( !opRight )
{
if ( errorMessage.isEmpty() )
errorMessage = QString( "invalid right operand for '%1' binary operator" ).arg( ogcOperatorName );
break;
}
return new QgsExpression::NodeBinaryOperator(( BinaryOperator ) i, opLeft, opRight );
}
if ( !opLeft && !opRight )
{
errorMessage = QString( "'%1' binary operator not supported." ).arg( element.tagName() );
return NULL;
}
if ( opLeft )
delete opLeft;
if ( opRight )
delete opRight;
return NULL;
}
//
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 ( isDoubleSafe( v1 ) && isDoubleSafe( v2 ) )
{
double f1 = getDoubleValue( v1, parent ); ENSURE_NO_EVAL_ERROR;
double f2 = getDoubleValue( v2, parent ); ENSURE_NO_EVAL_ERROR;
equal = f1 == f2;
}
else
{
QString s1 = getStringValue( v1, parent ); ENSURE_NO_EVAL_ERROR;
QString s2 = getStringValue( v2, parent ); ENSURE_NO_EVAL_ERROR;
equal = QString::compare( s1, s2 ) == 0;
}
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 = mNode->prepare( parent, fields );
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() );
}
void QgsExpression::NodeInOperator::toOgcFilter( QDomDocument &doc, QDomElement &element ) const
{
// XXX use a function instead of multiple comparations?
QDomElement *parent = &element;
QDomElement orElem;
if ( mList->list().size() > 1 )
{
orElem = doc.createElement( "ogc:Or" );
element.appendChild( orElem );
parent = &orElem;
}
foreach ( Node* n, mList->list() )
{
QDomElement eqElem = doc.createElement( "ogc:PropertyIsEqualTo" );
mNode->toOgcFilter( doc, eqElem );
n->toOgcFilter( doc, eqElem );
parent->appendChild( eqElem );
}
}
//
QVariant QgsExpression::NodeFunction::eval( QgsExpression* parent, QgsFeature* f )
{
const FunctionDef& fd = BuiltinFunctions()[mFnIndex];
// evaluate arguments
QVariantList argValues;
if ( mArgs )
{
foreach ( Node* n, mArgs->list() )
{
QVariant v = n->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
if ( isNull( v ) && fd.mFcn != fcnCoalesce )
return QVariant(); // all "normal" functions return NULL, when any parameter is NULL (so coalesce is abnormal)
argValues.append( v );
}
}
// run the function
QVariant res = fd.mFcn( argValues, f, parent );
ENSURE_NO_EVAL_ERROR;
// everything went fine
return res;
}
bool QgsExpression::NodeFunction::prepare( QgsExpression* parent, const QgsFieldMap& fields )
{
bool res = true;
if ( mArgs )
{
foreach ( Node* n, mArgs->list() )
{
res = res && n->prepare( parent, fields );
}
}
return res;
}
QString QgsExpression::NodeFunction::dump() const
{
const FunctionDef& fd = BuiltinFunctions()[mFnIndex];
if ( fd.mParams == 0 )
return fd.mName; // special column
else
return QString( "%1(%2)" ).arg( fd.mName ).arg( mArgs ? mArgs->dump() : QString() ); // function
}
void QgsExpression::NodeFunction::toOgcFilter( QDomDocument &doc, QDomElement &element ) const
{
const FunctionDef& fd = BuiltinFunctions()[mFnIndex];
if ( fd.mParams == 0 )
return; // TODO: special column
QDomElement funcElem = doc.createElement( "ogc:Function" );
funcElem.setAttribute( "name", fd.mName );
mArgs->toOgcFilter( doc, funcElem );
element.appendChild( funcElem );
}
QgsExpression::Node* QgsExpression::NodeFunction::createFromOgcFilter( QDomElement &element, QString &errorMessage )
{
if ( element.isNull() )
return NULL;
if ( element.localName() != "Function" )
{
errorMessage = QString( "ogc:Function expected, got %1" ).arg( element.tagName() );
return NULL;
}
for ( int i = 0; i < BuiltinFunctions().size(); i++ )
{
QgsExpression::FunctionDef funcDef = BuiltinFunctions()[i];
if ( element.attribute( "name" ) != funcDef.mName )
continue;
QgsExpression::NodeList *args = new QgsExpression::NodeList();
QDomElement operandElem = element.firstChildElement();
while ( !operandElem.isNull() )
{
QgsExpression::Node* op = QgsExpression::Node::createFromOgcFilter( operandElem, errorMessage );
if ( !op )
{
delete args;
return NULL;
}
args->append( op );
operandElem = operandElem.nextSiblingElement();
}
return new QgsExpression::NodeFunction( i, args );
}
return NULL;
}
//
QVariant QgsExpression::NodeLiteral::eval( QgsExpression* , QgsFeature* )
{
return mValue;
}
bool QgsExpression::NodeLiteral::prepare( QgsExpression* /*parent*/, const QgsFieldMap& /*fields*/ )
{
return true;
}
QString QgsExpression::NodeLiteral::dump() const
{
if ( mValue.isNull() )
return "NULL";
switch ( mValue.type() )
{
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 QObject::tr( "[unsupported type;%1; value:%2]" ).arg( mValue.typeName() ).arg( mValue.toString() );
}
}
void QgsExpression::NodeLiteral::toOgcFilter( QDomDocument &doc, QDomElement &element ) const
{
QString value;
if ( !mValue.isNull() )
{
switch ( mValue.type() )
{
case QVariant::Int:
value = QString::number( mValue.toInt() );
break;
case QVariant::Double:
value = QString::number( mValue.toDouble() );
break;
case QVariant::String:
value = mValue.toString();
break;
default:
break;
}
}
QDomElement litElem = doc.createElement( "ogc:Literal" );
litElem.appendChild( doc.createTextNode( value ) );
element.appendChild( litElem );
}
QgsExpression::Node* QgsExpression::NodeLiteral::createFromOgcFilter( QDomElement &element, QString &errorMessage )
{
if ( element.isNull() )
return NULL;
if ( element.localName() != "Literal" )
{
errorMessage = QString( "ogc:Literal expected, got %1" ).arg( element.tagName() );
return NULL;
}
QgsExpression::Node *root = 0;
// the literal content can have more children (e.g. CDATA section, text, ...)
QDomNode childNode = element.firstChild();
while ( !childNode.isNull() )
{
QgsExpression::Node* operand = 0;
if ( childNode.nodeType() == QDomNode::ElementNode )
{
// found a element node (e.g. PropertyName), convert it
QDomElement operandElem = childNode.toElement();
operand = QgsExpression::Node::createFromOgcFilter( operandElem, errorMessage );
if ( !operand )
{
if ( root )
delete root;
errorMessage = QString( "'%1' is an invalid or not supported content for ogc:Literal" ).arg( operandElem.tagName() );
return NULL;
}
}
else
{
// probably a text/CDATA node
QVariant value = childNode.nodeValue();
// try to convert the node content to number if possible,
// otherwise let's use it as string
bool ok;
double d = value.toDouble( &ok );
if ( ok )
value = d;
operand = new QgsExpression::NodeLiteral( value );
if ( !operand )
continue;
}
// use the concat operator to merge the ogc:Literal children
if ( !root )
{
root = operand;
}
else
{
root = new QgsExpression::NodeBinaryOperator( boConcat, root, operand );
}
childNode = childNode.nextSibling();
}
if ( root )
return root;
return NULL;
}
//
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 = QObject::tr( "Column '%1' not found" ).arg( mName );
mIndex = -1;
return false;
}
QString QgsExpression::NodeColumnRef::dump() const
{
return mName;
}
void QgsExpression::NodeColumnRef::toOgcFilter( QDomDocument &doc, QDomElement &element ) const
{
QDomElement propElem = doc.createElement( "ogc:PropertyName" );
propElem.appendChild( doc.createTextNode( mName ) );
element.appendChild( propElem );
}
QgsExpression::Node* QgsExpression::NodeColumnRef::createFromOgcFilter( QDomElement &element, QString &errorMessage )
{
if ( element.isNull() )
return NULL;
if ( element.localName() != "PropertyName" )
{
errorMessage = QString( "ogc:PropertyName expected, got %1" ).arg( element.tagName() );
return NULL;
}
return new QgsExpression::NodeColumnRef( element.firstChild().nodeValue() );
}
//
QVariant QgsExpression::NodeCondition::eval( QgsExpression* parent, QgsFeature* f )
{
foreach ( WhenThen* cond, mConditions )
{
QVariant vWhen = cond->mWhenExp->eval( parent, f );
TVL tvl = getTVLValue( vWhen, parent );
ENSURE_NO_EVAL_ERROR;
if ( tvl == True )
{
QVariant vRes = cond->mThenExp->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
return vRes;
}
}
if ( mElseExp )
{
QVariant vElse = mElseExp->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
return vElse;
}
// return NULL if no condition is matching
return QVariant();
}
bool QgsExpression::NodeCondition::prepare( QgsExpression* parent, const QgsFieldMap& fields )
{
bool res;
foreach ( WhenThen* cond, mConditions )
{
res = cond->mWhenExp->prepare( parent, fields )
& cond->mThenExp->prepare( parent, fields );
if ( !res ) return false;
}
if ( mElseExp )
return mElseExp->prepare( parent, fields );
return true;
}
QString QgsExpression::NodeCondition::dump() const
{
QString msg = "CONDITION:\n";
foreach ( WhenThen* cond, mConditions )
{
msg += QString( "- WHEN %1 THEN %2\n" ).arg( cond->mWhenExp->dump() ).arg( cond->mThenExp->dump() );
}
if ( mElseExp )
msg += QString( "- ELSE %1" ).arg( mElseExp->dump() );
return msg;
}
void QgsExpression::NodeCondition::toOgcFilter( QDomDocument &doc, QDomElement &element ) const
{
// TODO: if(cond) ... [else if (cond2) ...]* [else ...]
element.appendChild( doc.createComment( "CASE operator not implemented yet" ) );
}
QStringList QgsExpression::NodeCondition::referencedColumns() const
{
QStringList lst;
foreach ( WhenThen* cond, mConditions )
{
lst += cond->mWhenExp->referencedColumns() + cond->mThenExp->referencedColumns();
}
if ( mElseExp )
lst += mElseExp->referencedColumns();
return lst;
}
bool QgsExpression::NodeCondition::needsGeometry() const
{
foreach ( WhenThen* cond, mConditions )
{
if ( cond->mWhenExp->needsGeometry() ||
cond->mThenExp->needsGeometry() )
return true;
}
if ( mElseExp && mElseExp->needsGeometry() )
return true;
return false;
}