6 changes: 4 additions & 2 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ SET(QGIS_CORE_SRCS
qgsdiagram.cpp
qgsdiagramrendererv2.cpp
qgsdistancearea.cpp
qgsexpression.cpp
qgsfeature.cpp
qgsfield.cpp
qgsgeometry.cpp
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -286,6 +287,7 @@ SET(QGIS_CORE_HDRS
qgsdistancearea.h
qgscsexception.h
qgsexception.h
qgsexpression.h
qgsfeature.h
qgsfield.h
qgsgeometry.h
Expand Down
919 changes: 919 additions & 0 deletions src/core/qgsexpression.cpp

Large diffs are not rendered by default.

342 changes: 342 additions & 0 deletions src/core/qgsexpression.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
/***************************************************************************
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").
The expressions try to follow both syntax and semantics of SQL expressions.
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
Similarly to SQL, this class supports three-value logic: true/false/unknown.
Unknown value may be a result of operations with missing data (NULL). Please note
that NULL is different value than zero or an empty string. For example
3 > NULL returns unknown.
There is no special (three-value) 'boolean' type: true/false is represented as
1/0 integer, unknown value is represented the same way as NULL values: invalid QVariant.
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).
Type conversion: operators and functions that expect arguments to be of particular
type automatically convert the arguments to that type, e.g. sin('2.1') will convert
the argument to a double, length(123) will first convert the number to a string.
Explicit conversion can be achieved with toint, toreal, tostring functions.
If implicit or explicit conversion is invalid, the evaluation returns an error.
Comparison operators do numeric comparison in case both operators are numeric (int/double)
or they can be converted to numeric types.
Arithmetic operators do integer arithmetics if both operands are integer. That is
2+2 yields integer 4, but 2.0+2 returns real number 4.0. There are also two versions of
division and modulo operators: 1.0/2 returns 0.5 while 1/2 returns 0.
@note added in 2.0
*/
class CORE_EXPORT 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 );

//! return quoted column reference (in double quotes)
static QString quotedColumnRef( QString name ) { return QString( "\"%1\"" ).arg( name.replace( "\"", "\"\"" ) ); }

//////

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 changes: 171 additions & 0 deletions src/core/qgsexpressionlexer.ll
Original file line number Diff line number Diff line change
@@ -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 changes: 232 additions & 0 deletions src/core/qgsexpressionparser.yy
Original file line number Diff line number Diff line change
@@ -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;
}

35 changes: 19 additions & 16 deletions src/core/symbology-ng/qgsrulebasedrendererv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

#include "qgsrulebasedrendererv2.h"
#include "qgssymbollayerv2.h"
#include "qgssearchtreenode.h"
#include "qgsexpression.h"
#include "qgssymbollayerv2utils.h"
#include "qgsrendercontext.h"
#include "qgsvectorlayer.h"
Expand All @@ -31,32 +31,34 @@
QgsRuleBasedRendererV2::Rule::Rule( QgsSymbolV2* symbol, int scaleMinDenom, int scaleMaxDenom, QString filterExp, QString label, QString description )
: mSymbol( symbol ),
mScaleMinDenom( scaleMinDenom ), mScaleMaxDenom( scaleMaxDenom ),
mFilterExp( filterExp ), mLabel( label ), mDescription( description )
mFilterExp( filterExp ), mLabel( label ), mDescription( description ),
mFilter( NULL )
{
initFilter();
}

QgsRuleBasedRendererV2::Rule::Rule( const QgsRuleBasedRendererV2::Rule& other )
: mSymbol( NULL )
: mSymbol( NULL ), mFilter( NULL )
{
*this = other;
}

QgsRuleBasedRendererV2::Rule::~Rule()
{
delete mSymbol;
delete mFilter;
}

void QgsRuleBasedRendererV2::Rule::initFilter()
{
if ( !mFilterExp.isEmpty() )
{
mFilterParsed.setString( mFilterExp );
mFilterTree = mFilterParsed.tree(); // may be NULL if there's no input
delete mFilter;
mFilter = new QgsExpression( mFilterExp );
}
else
{
mFilterTree = NULL;
mFilter = NULL;
}
}

Expand All @@ -70,20 +72,19 @@ QString QgsRuleBasedRendererV2::Rule::dump() const

QStringList QgsRuleBasedRendererV2::Rule::needsFields() const
{
if ( ! mFilterTree )
if ( ! mFilter )
return QStringList();

return mFilterTree->referencedColumns();
return mFilter->referencedColumns();
}

bool QgsRuleBasedRendererV2::Rule::isFilterOK( const QgsFieldMap& fields, QgsFeature& f ) const
bool QgsRuleBasedRendererV2::Rule::isFilterOK( QgsFeature& f ) const
{
if ( ! mFilterTree )
if ( ! mFilter )
return true;

bool res = mFilterTree->checkAgainst( fields, f );
//print "is_ok", res, feature.id(), feature.attributeMap()
return res;
QVariant res = mFilter->evaluate( &f );
return res.toInt() != 0;
}

bool QgsRuleBasedRendererV2::Rule::isScaleOK( double scale ) const
Expand Down Expand Up @@ -152,7 +153,7 @@ void QgsRuleBasedRendererV2::renderFeature( QgsFeature& feature,
for ( QList<Rule*>::iterator it = mCurrentRules.begin(); it != mCurrentRules.end(); ++it )
{
Rule* rule = *it;
if ( rule->isFilterOK( mCurrentFields, feature ) )
if ( rule->isFilterOK( feature ) )
{
mCurrentSymbol = rule->symbol();
// will ask for mCurrentSymbol
Expand All @@ -175,11 +176,14 @@ void QgsRuleBasedRendererV2::startRender( QgsRenderContext& context, const QgsVe
mCurrentRules.append( &rule );
}

mCurrentFields = vlayer->pendingFields();
QgsFieldMap pendingFields = vlayer->pendingFields();

for ( QList<Rule*>::iterator it = mCurrentRules.begin(); it != mCurrentRules.end(); ++it )
{
Rule* rule = *it;
QgsExpression* exp = rule->filter();
if ( exp )
exp->prepare( pendingFields );
rule->symbol()->startRender( context );
}
}
Expand All @@ -193,7 +197,6 @@ void QgsRuleBasedRendererV2::stopRender( QgsRenderContext& context )
}

mCurrentRules.clear();
mCurrentFields.clear();
}

QList<QString> QgsRuleBasedRendererV2::usedAttributes()
Expand Down
10 changes: 5 additions & 5 deletions src/core/symbology-ng/qgsrulebasedrendererv2.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
#define QGSRULEBASEDRENDERERV2_H

#include "qgsfield.h"
#include "qgssearchstring.h"

#include "qgsrendererv2.h"

class QgsExpression;

class QgsCategorizedSymbolRendererV2;
class QgsGraduatedSymbolRendererV2;

Expand Down Expand Up @@ -50,13 +51,14 @@ class CORE_EXPORT QgsRuleBasedRendererV2 : public QgsFeatureRendererV2
~Rule();
QString dump() const;
QStringList needsFields() const;
bool isFilterOK( const QgsFieldMap& fields, QgsFeature& f ) const;
bool isFilterOK( QgsFeature& f ) const;
bool isScaleOK( double scale ) const;

QgsSymbolV2* symbol() { return mSymbol; }
bool dependsOnScale() const { return mScaleMinDenom != 0 || mScaleMaxDenom != 0; }
int scaleMinDenom() const { return mScaleMinDenom; }
int scaleMaxDenom() const { return mScaleMaxDenom; }
QgsExpression* filter() const { return mFilter; }
QString filterExpression() const { return mFilterExp; }
QString label() const { return mLabel; }
QString description() const { return mDescription; }
Expand All @@ -78,8 +80,7 @@ class CORE_EXPORT QgsRuleBasedRendererV2 : public QgsFeatureRendererV2
QString mFilterExp, mLabel, mDescription;

// temporary
QgsSearchString mFilterParsed;
QgsSearchTreeNode* mFilterTree;
QgsExpression* mFilter;
};

/////
Expand Down Expand Up @@ -151,7 +152,6 @@ class CORE_EXPORT QgsRuleBasedRendererV2 : public QgsFeatureRendererV2

// temporary
QList<Rule*> mCurrentRules;
QgsFieldMap mCurrentFields;
QgsSymbolV2* mCurrentSymbol;

};
Expand Down
61 changes: 30 additions & 31 deletions src/gui/qgssearchquerybuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
#include "qgsfeature.h"
#include "qgsfield.h"
#include "qgssearchquerybuilder.h"
#include "qgssearchstring.h"
#include "qgssearchtreenode.h"
#include "qgsexpression.h"
#include "qgsvectorlayer.h"
#include "qgslogger.h"

Expand Down Expand Up @@ -81,7 +80,7 @@ void QgsSearchQueryBuilder::populateFields()
QString fieldName = it->name();
mFieldMap[fieldName] = it.key();
if ( !reQuote.exactMatch( fieldName ) ) // quote if necessary
fieldName = QgsSearchTreeNode::quotedColumnRef( fieldName );
fieldName = QgsExpression::quotedColumnRef( fieldName );
QStandardItem *myItem = new QStandardItem( fieldName );
myItem->setEditable( false );
mModelFields->insertRow( mModelFields->rowCount(), myItem );
Expand Down Expand Up @@ -190,50 +189,51 @@ void QgsSearchQueryBuilder::on_btnTest_clicked()
// This method tests the number of records that would be returned
long QgsSearchQueryBuilder::countRecords( QString searchString )
{
QgsSearchString search;
if ( !search.setString( searchString ) )
QgsExpression search( searchString );
if ( search.hasParserError() )
{
QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorMsg() );
QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorString() );
return -1;
}

if ( !mLayer )
return -1;

QgsSearchTreeNode* searchTree = search.tree();
if ( !searchTree )
bool fetchGeom = search.needsGeometry();

int count = 0;
QgsFeature feat;
const QgsFieldMap& fields = mLayer->pendingFields();

if ( !search.prepare( fields ) )
{
// entered empty search string
return mLayer->featureCount();
QMessageBox::critical( this, tr( "Evaluation error" ), search.evalErrorString() );
return -1;
}

bool fetchGeom = searchTree->needsGeometry();

QApplication::setOverrideCursor( Qt::WaitCursor );

int count = 0;
QgsFeature feat;
const QgsFieldMap& fields = mLayer->pendingFields();
QgsAttributeList allAttributes = mLayer->pendingAllAttributesList();
mLayer->select( allAttributes, QgsRectangle(), fetchGeom );

while ( mLayer->nextFeature( feat ) )
{
if ( searchTree->checkAgainst( fields, feat ) )
QVariant value = search.evaluate( &feat );
if ( value.toInt() != 0 )
{
count++;
}

// check if there were errors during evaulating
if ( searchTree->hasError() )
if ( search.hasEvalError() )
break;
}

QApplication::restoreOverrideCursor();

if ( searchTree->hasError() )
if ( search.hasEvalError() )
{
QMessageBox::critical( this, tr( "Error during search" ), searchTree->errorMsg() );
QMessageBox::critical( this, tr( "Error during search" ), search.evalErrorString() );
return -1;
}

Expand Down Expand Up @@ -432,19 +432,17 @@ void QgsSearchQueryBuilder::loadQuery()
QString query = queryElem.text();

//todo: test if all the attributes are valid
QgsSearchString search;
if ( !search.setString( query ) )
QgsExpression search( query );
if ( search.hasParserError() )
{
QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorMsg() );
QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorString() );
return;
}

QgsSearchTreeNode* searchTree = search.tree();
if ( !searchTree )
{
QMessageBox::critical( this, tr( "Error creating search tree" ), search.parserErrorMsg() );
return;
}
QString newQueryText = query;

#if 0
// TODO: QgsExpression does not support overwriting of existing expressions

QStringList attributes = searchTree->referencedColumns();
QMap< QString, QString> attributesToReplace;
Expand Down Expand Up @@ -487,12 +485,13 @@ void QgsSearchQueryBuilder::loadQuery()
}
}

txtSQL->clear();
QString newQueryText = query;
if ( attributesToReplace.size() > 0 )
{
newQueryText = searchTree->makeSearchString();
newQueryText = query;
}
#endif

txtSQL->clear();
txtSQL->insertPlainText( newQueryText );
}

21 changes: 11 additions & 10 deletions src/gui/symbology-ng/qgsrulebasedrendererv2widget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
#include "qgssymbolv2.h"
#include "qgsvectorlayer.h"
#include "qgsapplication.h"
#include "qgssearchtreenode.h"
#include "qgsexpression.h"
#include "qgssymbolv2selectordialog.h"
#include "qgslogger.h"
#include "qstring.h"
Expand Down Expand Up @@ -444,32 +444,33 @@ void QgsRendererRulePropsDialog::buildExpression()

void QgsRendererRulePropsDialog::testFilter()
{
QgsSearchString filterParsed;
if ( ! filterParsed.setString( editFilter->text() ) )
QgsExpression filter( editFilter->text() );
if ( filter.hasParserError() )
{
QMessageBox::critical( this, tr( "Error" ), tr( "Filter expression parsing error:\n" ) + filterParsed.parserErrorMsg() );
QMessageBox::critical( this, tr( "Error" ), tr( "Filter expression parsing error:\n" ) + filter.parserErrorString() );
return;
}

QgsSearchTreeNode* tree = filterParsed.tree();
if ( ! tree )
const QgsFieldMap& fields = mLayer->pendingFields();

if ( !filter.prepare( fields ) )
{
QMessageBox::critical( this, tr( "Error" ), tr( "Filter is empty" ) );
QMessageBox::critical( this, tr( "Evaluation error" ), filter.evalErrorString() );
return;
}

QApplication::setOverrideCursor( Qt::WaitCursor );

const QgsFieldMap& fields = mLayer->pendingFields();
mLayer->select( fields.keys(), QgsRectangle(), false );

int count = 0;
QgsFeature f;
while ( mLayer->nextFeature( f ) )
{
if ( tree->checkAgainst( fields, f ) )
QVariant value = filter.evaluate( &f );
if ( value.toInt() != 0 )
count++;
if ( tree->hasError() )
if ( filter.hasEvalError() )
break;
}

Expand Down
22 changes: 2 additions & 20 deletions tests/src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,32 +60,14 @@ MACRO (ADD_QGIS_TEST testname testsrc)
ADD_EXECUTABLE(qgis_${testname} ${qgis_${testname}_SRCS})
ADD_DEPENDENCIES(qgis_${testname} qgis_${testname}moc)
TARGET_LINK_LIBRARIES(qgis_${testname} ${QT_LIBRARIES} qgis_core)
SET_TARGET_PROPERTIES(qgis_${testname}
PROPERTIES
# skip the full RPATH for the build tree
SKIP_BUILD_RPATH TRUE
# when building, use the install RPATH already
# (so it doesn't need to relink when installing)
BUILD_WITH_INSTALL_RPATH TRUE
# the RPATH to be used when installing
INSTALL_RPATH ${QGIS_LIB_DIR}
# add the automatically determined parts of the RPATH
# which point to directories outside the build tree to the install RPATH
INSTALL_RPATH_USE_LINK_PATH true)
IF (APPLE)
# For Mac OS X, the executable must be at the root of the bundle's executable folder
INSTALL(TARGETS qgis_${testname} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX})
ADD_TEST(qgis_${testname} ${CMAKE_INSTALL_PREFIX}/qgis_${testname})
ELSE (APPLE)
INSTALL(TARGETS qgis_${testname} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
ADD_TEST(qgis_${testname} ${CMAKE_INSTALL_PREFIX}/bin/qgis_${testname})
ENDIF (APPLE)
ADD_TEST(qgis_${testname} qgis_${testname})
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)
Expand Down
411 changes: 411 additions & 0 deletions tests/src/core/testqgsexpression.cpp

Large diffs are not rendered by default.