mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
622 lines
15 KiB
C++
622 lines
15 KiB
C++
/***************************************************************************
|
|
qgssearchtreenode.cpp
|
|
Implementation for evaluating parsed tree
|
|
--------------------
|
|
begin : 2005-07-26
|
|
copyright : (C) 2005 by Martin Dobias
|
|
email : won.der at centrum.sk
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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. *
|
|
* *
|
|
***************************************************************************/
|
|
/* $Id$ */
|
|
|
|
#include "qgslogger.h"
|
|
#include "qgsdistancearea.h"
|
|
#include "qgsfield.h"
|
|
#include "qgsgeometry.h"
|
|
#include "qgssearchtreenode.h"
|
|
#include <QRegExp>
|
|
#include <QObject>
|
|
#include <QSet>
|
|
#include <QSettings>
|
|
#include <iostream>
|
|
|
|
#ifndef Q_OS_MACX
|
|
#include <cmath>
|
|
#else
|
|
#include <math.h>
|
|
#endif
|
|
|
|
|
|
|
|
#define EVAL_STR(x) (x.length() ? x : "(empty)")
|
|
|
|
QgsSearchTreeNode::QgsSearchTreeNode( double number )
|
|
{
|
|
mType = tNumber;
|
|
mNumber = number;
|
|
mLeft = NULL;
|
|
mRight = NULL;
|
|
|
|
init();
|
|
}
|
|
|
|
|
|
QgsSearchTreeNode::QgsSearchTreeNode( Operator op, QgsSearchTreeNode* left,
|
|
QgsSearchTreeNode* right )
|
|
{
|
|
mType = tOperator;
|
|
mOp = op;
|
|
mLeft = left;
|
|
mRight = right;
|
|
|
|
init();
|
|
}
|
|
|
|
|
|
QgsSearchTreeNode::QgsSearchTreeNode( QString text, bool isColumnRef )
|
|
{
|
|
mLeft = NULL;
|
|
mRight = NULL;
|
|
|
|
if ( isColumnRef )
|
|
{
|
|
mType = tColumnRef;
|
|
mText = text;
|
|
}
|
|
else
|
|
{
|
|
mType = tString;
|
|
mText = text;
|
|
stripText();
|
|
}
|
|
|
|
init();
|
|
}
|
|
|
|
|
|
QgsSearchTreeNode::QgsSearchTreeNode( const QgsSearchTreeNode& node )
|
|
{
|
|
mType = node.mType;
|
|
mOp = node.mOp;
|
|
mNumber = node.mNumber;
|
|
mText = node.mText;
|
|
|
|
// recursively copy children
|
|
if ( node.mLeft )
|
|
mLeft = new QgsSearchTreeNode( *node.mLeft );
|
|
else
|
|
mLeft = NULL;
|
|
|
|
if ( node.mRight )
|
|
mRight = new QgsSearchTreeNode( *node.mRight );
|
|
else
|
|
mRight = NULL;
|
|
|
|
init();
|
|
}
|
|
|
|
|
|
QgsSearchTreeNode::~QgsSearchTreeNode()
|
|
{
|
|
// delete children
|
|
|
|
if ( mLeft )
|
|
delete mLeft;
|
|
|
|
if ( mRight )
|
|
delete mRight;
|
|
|
|
delete mCalc;
|
|
}
|
|
|
|
|
|
void QgsSearchTreeNode::init()
|
|
{
|
|
if ( mType == tOperator && ( mOp == opLENGTH || mOp == opAREA ) )
|
|
{
|
|
//initialize QgsDistanceArea
|
|
mCalc = new QgsDistanceArea;
|
|
mCalc->setProjectionsEnabled( false );
|
|
QSettings settings;
|
|
QString ellipsoid = settings.value( "/qgis/measure/ellipsoid", "WGS84" ).toString();
|
|
mCalc->setEllipsoid( ellipsoid );
|
|
}
|
|
else
|
|
{
|
|
mCalc = NULL;
|
|
}
|
|
}
|
|
|
|
void QgsSearchTreeNode::stripText()
|
|
{
|
|
// strip single quotes on start,end
|
|
mText = mText.mid( 1, mText.length() - 2 );
|
|
|
|
// make single "single quotes" from double "single quotes"
|
|
mText.replace( QRegExp( "''" ), "'" );
|
|
|
|
// strip \n \' etc.
|
|
int index = 0;
|
|
while (( index = mText.indexOf( '\\', index ) ) != -1 )
|
|
{
|
|
mText.remove( index, 1 ); // delete backslash
|
|
QChar chr;
|
|
switch ( mText[index].toLatin1() ) // evaluate backslashed character
|
|
{
|
|
case 'n': chr = '\n'; break;
|
|
case 't': chr = '\t'; break;
|
|
case '\\': chr = '\\'; break;
|
|
case '\'': chr = '\''; break;
|
|
default: chr = '?'; break;
|
|
}
|
|
mText[index++] = chr; // set new character and push index +1
|
|
}
|
|
|
|
}
|
|
|
|
QString QgsSearchTreeNode::makeSearchString()
|
|
{
|
|
QString str;
|
|
if ( mType == tOperator )
|
|
{
|
|
str += "(";
|
|
if ( mOp != opNOT )
|
|
{
|
|
if ( mLeft )
|
|
{
|
|
str += mLeft->makeSearchString();
|
|
}
|
|
switch ( mOp )
|
|
{
|
|
case opAND: str += " AND "; break;
|
|
case opOR: str += " OR "; break;
|
|
|
|
case opPLUS: str += "+"; break;
|
|
case opMINUS: str += "-"; break;
|
|
case opMUL: str += "*"; break;
|
|
case opDIV: str += "/"; break;
|
|
|
|
case opEQ: str += " = "; break;
|
|
case opNE: str += " != "; break;
|
|
case opGT: str += " > "; break;
|
|
case opLT: str += " < "; break;
|
|
case opGE: str += " >= "; break;
|
|
case opLE: str += " <= "; break;
|
|
|
|
case opRegexp: str += " ~ "; break;
|
|
case opLike: str += " LIKE "; break;
|
|
|
|
default: str += " ? ";
|
|
}
|
|
|
|
if ( mRight )
|
|
{
|
|
str += mRight->makeSearchString();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
str += "NOT ";
|
|
str += mLeft->makeSearchString();
|
|
}
|
|
str += ")";
|
|
}
|
|
else if ( mType == tNumber )
|
|
{
|
|
str += QString::number( mNumber );
|
|
}
|
|
else if ( mType == tString || mType == tColumnRef )
|
|
{
|
|
str += mText;
|
|
}
|
|
else // unknown type
|
|
{
|
|
str += "unknown_node_type:";
|
|
str += QString::number( mType );
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
QStringList QgsSearchTreeNode::referencedColumns()
|
|
{
|
|
if ( mType == tOperator )
|
|
{
|
|
QStringList lst;
|
|
if ( mLeft )
|
|
lst += mLeft->referencedColumns();
|
|
if ( mRight )
|
|
lst += mRight->referencedColumns();
|
|
return lst.toSet().toList(); // make union and convert back to list
|
|
}
|
|
else if ( mType == tColumnRef )
|
|
{
|
|
return QStringList( mText );
|
|
}
|
|
else
|
|
{
|
|
// string or number - do nothing
|
|
return QStringList();
|
|
}
|
|
|
|
}
|
|
|
|
bool QgsSearchTreeNode::needsGeometry()
|
|
{
|
|
if ( mType == tOperator )
|
|
{
|
|
if ( mOp == opLENGTH || mOp == opAREA )
|
|
return true;
|
|
|
|
if ( mLeft && mLeft->needsGeometry() )
|
|
return true;
|
|
if ( mRight && mRight->needsGeometry() )
|
|
return true;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool QgsSearchTreeNode::checkAgainst( const QgsFieldMap& fields, const QgsAttributeMap& attributes, QgsGeometry* geom )
|
|
{
|
|
QgsDebugMsgLevel( "checkAgainst: " + makeSearchString(), 2 );
|
|
|
|
mError = "";
|
|
|
|
// this error should be caught when checking syntax, but for sure...
|
|
if ( mType != tOperator )
|
|
{
|
|
mError = "Expected operator, got scalar value!";
|
|
return false;
|
|
}
|
|
|
|
QgsSearchTreeValue value1, value2;
|
|
int res;
|
|
|
|
switch ( mOp )
|
|
{
|
|
case opNOT:
|
|
return !mLeft->checkAgainst( fields, attributes, geom );
|
|
|
|
case opAND:
|
|
if ( !mLeft->checkAgainst( fields, attributes, geom ) )
|
|
return false;
|
|
return mRight->checkAgainst( fields, attributes, geom );
|
|
|
|
case opOR:
|
|
if ( mLeft->checkAgainst( fields, attributes, geom ) )
|
|
return true;
|
|
return mRight->checkAgainst( fields, attributes, geom );
|
|
|
|
case opISNULL:
|
|
case opISNOTNULL:
|
|
if ( !getValue( value1, mLeft, fields, attributes, geom ) )
|
|
return false;
|
|
|
|
if ( mOp == opISNULL )
|
|
{
|
|
return value1.isNull();
|
|
}
|
|
else if ( mOp == opISNOTNULL )
|
|
{
|
|
return !value1.isNull();
|
|
}
|
|
|
|
case opEQ:
|
|
case opNE:
|
|
case opGT:
|
|
case opLT:
|
|
case opGE:
|
|
case opLE:
|
|
|
|
if ( !getValue( value1, mLeft, fields, attributes, geom ) || !getValue( value2, mRight, fields, attributes, geom ) )
|
|
return false;
|
|
|
|
if ( value1.isNull() || value2.isNull() )
|
|
{
|
|
// NULL values never match
|
|
return false;
|
|
}
|
|
|
|
res = QgsSearchTreeValue::compare( value1, value2 );
|
|
|
|
switch ( mOp )
|
|
{
|
|
case opEQ: return ( res == 0 );
|
|
case opNE: return ( res != 0 );
|
|
case opGT: return ( res > 0 );
|
|
case opLT: return ( res < 0 );
|
|
case opGE: return ( res >= 0 );
|
|
case opLE: return ( res <= 0 );
|
|
default:
|
|
mError = "Unexpected state when evaluating operator!";
|
|
return false;
|
|
}
|
|
|
|
case opRegexp:
|
|
case opLike:
|
|
{
|
|
if ( !getValue( value1, mLeft, fields, attributes, geom ) ||
|
|
!getValue( value2, mRight, fields, attributes, geom ) )
|
|
return false;
|
|
|
|
// value1 is string to be matched
|
|
// value2 is regular expression
|
|
|
|
// XXX does it make sense to use regexp on numbers?
|
|
// in what format should they be?
|
|
if ( value1.isNumeric() || value2.isNumeric() )
|
|
{
|
|
mError = QObject::tr( "Regular expressions on numeric values don't make sense. Use comparison instead." );
|
|
return false;
|
|
}
|
|
|
|
// TODO: reuse QRegExp
|
|
|
|
QString str = value2.string();
|
|
if ( mOp == opLike ) // change from LIKE syntax to regexp
|
|
{
|
|
// XXX escape % and _ ???
|
|
str.replace( "%", ".*" );
|
|
str.replace( "_", "." );
|
|
return QRegExp( str ).exactMatch( value1.string() );
|
|
}
|
|
else
|
|
{
|
|
return ( QRegExp( str ).indexIn( value1.string() ) != -1 );
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
mError = "Unknown operator: ";
|
|
mError += QString::number( mOp );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool QgsSearchTreeNode::getValue( QgsSearchTreeValue& value, QgsSearchTreeNode* node, const QgsFieldMap& fields, const QgsAttributeMap& attributes, QgsGeometry* geom )
|
|
{
|
|
value = node->valueAgainst( fields, attributes, geom );
|
|
if ( value.isError() )
|
|
{
|
|
switch (( int )value.number() )
|
|
{
|
|
case 1:
|
|
mError = QObject::tr( "Referenced column wasn't found: %1" ).arg( value.string() );
|
|
break;
|
|
case 2:
|
|
mError = QObject::tr( "Division by zero." );
|
|
break;
|
|
|
|
// these should never happen (no need to translate)
|
|
case 3:
|
|
mError = "Unknown operator: ";
|
|
mError += value.string();
|
|
break;
|
|
case 4:
|
|
mError = "Unknown token: ";
|
|
mError += value.string();
|
|
break;
|
|
default:
|
|
mError = "Unknown error!";
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
QgsSearchTreeValue QgsSearchTreeNode::valueAgainst( const QgsFieldMap& fields, const QgsAttributeMap& attributes, QgsGeometry* geom )
|
|
{
|
|
QgsDebugMsgLevel( "valueAgainst: " + makeSearchString(), 2 );
|
|
|
|
switch ( mType )
|
|
{
|
|
|
|
case tNumber:
|
|
QgsDebugMsgLevel( "number: " + QString::number( mNumber ), 2 );
|
|
return QgsSearchTreeValue( mNumber );
|
|
|
|
case tString:
|
|
QgsDebugMsgLevel( "text: " + EVAL_STR( mText ), 2 );
|
|
return QgsSearchTreeValue( mText );
|
|
|
|
case tColumnRef:
|
|
{
|
|
QgsDebugMsgLevel( "column (" + mText.toLower() + "): ", 2 );
|
|
// find field index for the column
|
|
QgsFieldMap::const_iterator it;
|
|
for ( it = fields.begin(); it != fields.end(); it++ )
|
|
{
|
|
if ( it->name().toLower() == mText.toLower() ) // TODO: optimize
|
|
break;
|
|
}
|
|
|
|
if ( it == fields.end() )
|
|
{
|
|
// report missing column if not found
|
|
QgsDebugMsgLevel( "ERROR!", 2 );
|
|
return QgsSearchTreeValue( 1, mText );
|
|
}
|
|
|
|
// get the value
|
|
QVariant val = attributes[it.key()];
|
|
if ( val.isNull() )
|
|
{
|
|
QgsDebugMsgLevel( " NULL", 2 );
|
|
return QgsSearchTreeValue();
|
|
}
|
|
else if ( val.type() == QVariant::Bool || val.type() == QVariant::Int || val.type() == QVariant::Double )
|
|
{
|
|
QgsDebugMsgLevel( " number: " + QString::number( val.toDouble() ), 2 );
|
|
return QgsSearchTreeValue( val.toDouble() );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsgLevel( " text: " + EVAL_STR( val.toString() ), 2 );
|
|
return QgsSearchTreeValue( val.toString() );
|
|
}
|
|
|
|
}
|
|
|
|
// arithmetic operators
|
|
case tOperator:
|
|
{
|
|
QgsSearchTreeValue value1, value2;
|
|
if ( mLeft )
|
|
{
|
|
if ( !getValue( value1, mLeft, fields, attributes, geom ) ) return value1;
|
|
}
|
|
if ( mRight )
|
|
{
|
|
if ( !getValue( value2, mRight, fields, attributes, geom ) ) return value2;
|
|
}
|
|
|
|
if ( mOp == opLENGTH || mOp == opAREA )
|
|
{
|
|
if ( !geom )
|
|
{
|
|
return QgsSearchTreeValue( 2, "Geometry is 0" );
|
|
}
|
|
|
|
//check that we don't use area for lines or length for polygons
|
|
if ( mOp == opLENGTH && geom->type() != QGis::Line )
|
|
{
|
|
return QgsSearchTreeValue( 0 );
|
|
}
|
|
if ( mOp == opAREA && geom->type() != QGis::Polygon )
|
|
{
|
|
return QgsSearchTreeValue( 0 );
|
|
}
|
|
return QgsSearchTreeValue( mCalc->measure( geom ) );
|
|
}
|
|
|
|
//string operations with one argument
|
|
if ( !mRight && !value1.isNumeric() )
|
|
{
|
|
if ( mOp == opTOINT )
|
|
{
|
|
return QgsSearchTreeValue( value1.string().toInt() );
|
|
}
|
|
else if ( mOp == opTOREAL )
|
|
{
|
|
return QgsSearchTreeValue( value1.string().toDouble() );
|
|
}
|
|
}
|
|
|
|
//don't convert to numbers in case of string concatenation
|
|
if ( mLeft && mRight && !value1.isNumeric() && !value2.isNumeric() )
|
|
{
|
|
if ( mOp == opPLUS )
|
|
{
|
|
return QgsSearchTreeValue( value1.string() + value2.string() );
|
|
}
|
|
}
|
|
|
|
// for other operators, convert strings to numbers if needed
|
|
double val1, val2;
|
|
if ( value1.isNumeric() )
|
|
val1 = value1.number();
|
|
else
|
|
val1 = value1.string().toDouble();
|
|
if ( value2.isNumeric() )
|
|
val2 = value2.number();
|
|
else
|
|
val2 = value2.string().toDouble();
|
|
|
|
switch ( mOp )
|
|
{
|
|
case opPLUS:
|
|
return QgsSearchTreeValue( val1 + val2 );
|
|
case opMINUS:
|
|
return QgsSearchTreeValue( val1 - val2 );
|
|
case opMUL:
|
|
return QgsSearchTreeValue( val1 * val2 );
|
|
case opDIV:
|
|
if ( val2 == 0 )
|
|
return QgsSearchTreeValue( 2, "" ); // division by zero
|
|
else
|
|
return QgsSearchTreeValue( val1 / val2 );
|
|
default:
|
|
return QgsSearchTreeValue( 3, QString::number( mOp ) ); // unknown operator
|
|
case opPOW:
|
|
if (( val1 == 0 && val2 < 0 ) || ( val2 < 0 && ( val2 - floor( val2 ) ) > 0 ) )
|
|
{
|
|
return QgsSearchTreeValue( 4, "Error in power function" );
|
|
}
|
|
return QgsSearchTreeValue( pow( val1, val2 ) );
|
|
case opSQRT:
|
|
return QgsSearchTreeValue( sqrt( val1 ) );
|
|
case opSIN:
|
|
return QgsSearchTreeValue( sin( val1 ) );
|
|
case opCOS:
|
|
return QgsSearchTreeValue( cos( val1 ) );
|
|
case opTAN:
|
|
return QgsSearchTreeValue( tan( val1 ) );
|
|
case opASIN:
|
|
return QgsSearchTreeValue( asin( val1 ) );
|
|
case opACOS:
|
|
return QgsSearchTreeValue( acos( val1 ) );
|
|
case opATAN:
|
|
return QgsSearchTreeValue( atan( val1 ) );
|
|
case opTOINT:
|
|
return QgsSearchTreeValue( int( val1 ) );
|
|
case opTOREAL:
|
|
return QgsSearchTreeValue( val1 );
|
|
case opTOSTRING:
|
|
return QgsSearchTreeValue( QString::number( val1 ) );
|
|
}
|
|
}
|
|
|
|
default:
|
|
return QgsSearchTreeValue( 4, QString::number( mType ) ); // unknown token
|
|
}
|
|
}
|
|
|
|
|
|
int QgsSearchTreeValue::compare( QgsSearchTreeValue& value1, QgsSearchTreeValue& value2, Qt::CaseSensitivity cs )
|
|
{
|
|
if ( value1.isNumeric() || value2.isNumeric() )
|
|
{
|
|
// numeric comparison
|
|
|
|
// convert to numbers if needed
|
|
double val1, val2;
|
|
if ( value1.isNumeric() )
|
|
val1 = value1.number();
|
|
else
|
|
val1 = value1.string().toDouble();
|
|
if ( value2.isNumeric() )
|
|
val2 = value2.number();
|
|
else
|
|
val2 = value2.string().toDouble();
|
|
|
|
QgsDebugMsgLevel( "NUM_COMP: " + QString::number( val1 ) + " ~ " + QString::number( val2 ), 2 );
|
|
|
|
if ( val1 < val2 )
|
|
return -1;
|
|
else if ( val1 > val2 )
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
// string comparison
|
|
return value1.string().compare( value2.string(), cs );
|
|
}
|
|
}
|