154 changes: 151 additions & 3 deletions src/core/qgsexpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,13 @@ static QVariant fcnSqrt( const QVariantList& values, QgsFeature* /*f*/, QgsExpre
double x = getDoubleValue( values.at( 0 ), parent );
return QVariant( sqrt( x ) );
}

static QVariant fcnAbs( const QVariantList& values, QgsFeature*, QgsExpression* parent )
{
double val = getDoubleValue( values.at( 0 ), parent );
return QVariant( fabs( val ) );
}

static QVariant fcnSin( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
Expand Down Expand Up @@ -410,6 +417,102 @@ static QVariant fcnLog( const QVariantList& values, QgsFeature* , QgsExpression*
return QVariant();
return QVariant( log( x ) / log( b ) );
}
static QVariant fcnRndF( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double min = getDoubleValue( values.at( 0 ), parent );
double max = getDoubleValue( values.at( 1 ), parent );
if ( max < min )
return QVariant();

// Return a random double in the range [min, max] (inclusive)
double f = ( double )rand() / RAND_MAX;
return QVariant( min + f * ( max - min ) ) ;
}
static QVariant fcnRnd( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
int min = getIntValue( values.at( 0 ), parent );
int max = getIntValue( values.at( 1 ), parent );
if ( max < min )
return QVariant();

// Return a random integer in the range [min, max] (inclusive)
return QVariant( min + ( rand() % ( int )( max - min + 1 ) ) );
}

static QVariant fcnLinearScale( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double val = getDoubleValue( values.at( 0 ), parent );
double domain_min = getDoubleValue( values.at( 1 ), parent );
double domain_max = getDoubleValue( values.at( 2 ), parent );
double range_min = getDoubleValue( values.at( 3 ), parent );
double range_max = getDoubleValue( values.at( 4 ), parent );

// outside of domain?
if ( val >= domain_max )
{
return range_max;
}
else if ( val <= domain_min )
{
return range_min;
}

// calculate linear scale
double m = ( range_max - range_min ) / ( domain_max - domain_min );
double c = range_min - ( domain_min * m );

// Return linearly scaled value
return QVariant( m * val + c );
}

static QVariant fcnMax( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
//initially set max as first value
double maxVal = getDoubleValue( values.at( 0 ), parent );

//check against all other values
for ( int i = 1; i < values.length(); ++i )
{
double testVal = getDoubleValue( values[i], parent );
if ( testVal > maxVal )
{
maxVal = testVal;
}
}

return QVariant( maxVal );
}

static QVariant fcnMin( const QVariantList& values, QgsFeature* , QgsExpression *parent )
{
//initially set min as first value
double minVal = getDoubleValue( values.at( 0 ), parent );

//check against all other values
for ( int i = 1; i < values.length(); ++i )
{
double testVal = getDoubleValue( values[i], parent );
if ( testVal < minVal )
{
minVal = testVal;
}
}

return QVariant( minVal );
}

static QVariant fcnFloor( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
return QVariant( floor( x ) );
}

static QVariant fcnCeil( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
return QVariant( ceil( x ) );
}

static QVariant fcnToInt( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
return QVariant( getIntValue( values.at( 0 ), parent ) );
Expand Down Expand Up @@ -459,6 +562,13 @@ static QVariant fcnTitle( const QVariantList& values, QgsFeature* , QgsExpressio
}
return QVariant( elems.join( " " ) );
}

static QVariant fcnTrim( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
return QVariant( str.trimmed() );
}

static QVariant fcnLength( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
Expand Down Expand Up @@ -500,6 +610,31 @@ static QVariant fcnRegexpMatch( const QVariantList& values, QgsFeature* , QgsExp
return QVariant( str.contains( re ) ? 1 : 0 );
}

static QVariant fcnRegexpSubstr( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
QString regexp = getStringValue( values.at( 1 ), parent );

QRegExp re( regexp );
if ( !re.isValid() )
{
parent->setEvalErrorString( QObject::tr( "Invalid regular expression '%1': %2" ).arg( regexp ).arg( re.errorString() ) );
return QVariant();
}

// extract substring
re.indexIn( str );
if ( re.captureCount() > 0 )
{
// return first capture
return QVariant( re.capturedTexts()[0] );
}
else
{
return QVariant( "" );
}
}

static QVariant fcnSubstr( const QVariantList& values, QgsFeature* , QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
Expand Down Expand Up @@ -1204,15 +1339,18 @@ const QStringList &QgsExpression::BuiltinFunctions()
if ( gmBuiltinFunctions.isEmpty() )
{
gmBuiltinFunctions
<< "sqrt" << "cos" << "sin" << "tan"
<< "abs" << "sqrt" << "cos" << "sin" << "tan"
<< "asin" << "acos" << "atan" << "atan2"
<< "exp" << "ln" << "log10" << "log"
<< "round" << "toint" << "toreal" << "tostring"
<< "round" << "rand" << "randf" << "max" << "min"
<< "scale_linear" << "floor" << "ceil"
<< "toint" << "toreal" << "tostring"
<< "todatetime" << "todate" << "totime" << "tointerval"
<< "coalesce" << "regexp_match" << "$now" << "age" << "year"
<< "month" << "week" << "day" << "hour"
<< "minute" << "second" << "lower" << "upper"
<< "title" << "length" << "replace" << "regexp_replace"
<< "title" << "length" << "replace" << "trim"
<< "regexp_replace" << "regexp_substr"
<< "substr" << "concat" << "strpos" << "left"
<< "right" << "rpad" << "lpad"
<< "format_number" << "format_date"
Expand All @@ -1234,6 +1372,7 @@ const QList<QgsExpression::Function*> &QgsExpression::Functions()
{
gmFunctions
<< new StaticFunction( "sqrt", 1, fcnSqrt, QObject::tr( "Math" ) )
<< new StaticFunction( "abs", 1, fcnAbs, QObject::tr( "Math" ) )
<< new StaticFunction( "cos", 1, fcnCos, QObject::tr( "Math" ) )
<< new StaticFunction( "sin", 1, fcnSin, QObject::tr( "Math" ) )
<< new StaticFunction( "tan", 1, fcnTan, QObject::tr( "Math" ) )
Expand All @@ -1246,6 +1385,13 @@ const QList<QgsExpression::Function*> &QgsExpression::Functions()
<< new StaticFunction( "log10", 1, fcnLog10, QObject::tr( "Math" ) )
<< new StaticFunction( "log", 2, fcnLog, QObject::tr( "Math" ) )
<< new StaticFunction( "round", -1, fcnRound, QObject::tr( "Math" ) )
<< new StaticFunction( "rand", 2, fcnRnd, QObject::tr( "Math" ) )
<< new StaticFunction( "randf", 2, fcnRndF, QObject::tr( "Math" ) )
<< new StaticFunction( "max", -1, fcnMax, QObject::tr( "Math" ) )
<< new StaticFunction( "min", -1, fcnMin, QObject::tr( "Math" ) )
<< new StaticFunction( "scale_linear", 5, fcnLinearScale, QObject::tr( "Math" ) )
<< new StaticFunction( "floor", 1, fcnFloor, QObject::tr( "Math" ) )
<< new StaticFunction( "ceil", 1, fcnCeil, QObject::tr( "Math" ) )
<< new StaticFunction( "$pi", 0, fcnPi, QObject::tr( "Math" ) )
<< new StaticFunction( "toint", 1, fcnToInt, QObject::tr( "Conversions" ) )
<< new StaticFunction( "toreal", 1, fcnToReal, QObject::tr( "Conversions" ) )
Expand All @@ -1268,9 +1414,11 @@ const QList<QgsExpression::Function*> &QgsExpression::Functions()
<< new StaticFunction( "lower", 1, fcnLower, QObject::tr( "String" ) )
<< new StaticFunction( "upper", 1, fcnUpper, QObject::tr( "String" ) )
<< new StaticFunction( "title", 1, fcnTitle, QObject::tr( "String" ) )
<< new StaticFunction( "trim", 1, fcnTrim, QObject::tr( "String" ) )
<< new StaticFunction( "length", 1, fcnLength, QObject::tr( "String" ) )
<< new StaticFunction( "replace", 3, fcnReplace, QObject::tr( "String" ) )
<< new StaticFunction( "regexp_replace", 3, fcnRegexpReplace, QObject::tr( "String" ) )
<< new StaticFunction( "regexp_substr", 2, fcnRegexpSubstr, QObject::tr( "String" ) )
<< new StaticFunction( "substr", 3, fcnSubstr, QObject::tr( "String" ) )
<< new StaticFunction( "concat", -1, fcnConcat, QObject::tr( "String" ) )
<< new StaticFunction( "strpos", 2, fcnStrpos, QObject::tr( "String" ) )
Expand Down
55 changes: 55 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ class TestQgsExpression: public QObject

// math functions
QTest::newRow( "sqrt" ) << "sqrt(16)" << false << QVariant( 4. );
QTest::newRow( "abs(0.1)" ) << "abs(0.1)" << false << QVariant( 0.1 );
QTest::newRow( "abs(0)" ) << "abs(0)" << false << QVariant( 0. );
QTest::newRow( "abs(-0.1)" ) << "abs(-0.1)" << false << QVariant( 0.1 );
QTest::newRow( "invalid sqrt value" ) << "sqrt('a')" << true << QVariant();
QTest::newRow( "sin 0" ) << "sin(0)" << false << QVariant( 0. );
QTest::newRow( "cos 0" ) << "cos(0)" << false << QVariant( 1. );
Expand All @@ -253,6 +256,20 @@ class TestQgsExpression: public QObject
QTest::newRow( "round(1234.554,2) - round down" ) << "round(1234.554,2)" << false << QVariant( 1234.55 );
QTest::newRow( "round(1234.6) - round up to int" ) << "round(1234.6)" << false << QVariant( 1235 );
QTest::newRow( "round(1234.6) - round down to int" ) << "round(1234.4)" << false << QVariant( 1234 );
QTest::newRow( "max(1)" ) << "max(1)" << false << QVariant( 1. );
QTest::newRow( "max(1,3.5,-2.1)" ) << "max(1,3.5,-2.1)" << false << QVariant( 3.5 );
QTest::newRow( "min(-1.5)" ) << "min(-1.5)" << false << QVariant( -1.5 );
QTest::newRow( "min(-16.6,3.5,-2.1)" ) << "min(-16.6,3.5,-2.1)" << false << QVariant( -16.6 );
QTest::newRow( "floor(4.9)" ) << "floor(4.9)" << false << QVariant( 4. );
QTest::newRow( "floor(-4.9)" ) << "floor(-4.9)" << false << QVariant( -5. );
QTest::newRow( "ceil(4.9)" ) << "ceil(4.9)" << false << QVariant( 5. );
QTest::newRow( "ceil(-4.9)" ) << "ceil(-4.9)" << false << QVariant( -4. );
QTest::newRow( "scale_linear(0.5,0,1,0,1)" ) << "scale_linear(0.5,0,1,0,1)" << false << QVariant( 0.5 );
QTest::newRow( "scale_linear(0,0,10,100,200)" ) << "scale_linear(0,0,10,100,200)" << false << QVariant( 100. );
QTest::newRow( "scale_linear(5,0,10,100,200)" ) << "scale_linear(5,0,10,100,200)" << false << QVariant( 150. );
QTest::newRow( "scale_linear(10,0,10,100,200)" ) << "scale_linear(10,0,10,100,200)" << false << QVariant( 200. );
QTest::newRow( "scale_linear(-1,0,10,100,200)" ) << "scale_linear(-1,0,10,100,200)" << false << QVariant( 100. );
QTest::newRow( "scale_linear(11,0,10,100,200)" ) << "scale_linear(11,0,10,100,200)" << false << QVariant( 200. );

// cast functions
QTest::newRow( "double to int" ) << "toint(3.2)" << false << QVariant( 3 );
Expand All @@ -274,6 +291,8 @@ class TestQgsExpression: public QObject
QTest::newRow( "regexp_replace invalid" ) << "regexp_replace('HeLLo','[[[', '-')" << true << QVariant();
QTest::newRow( "substr" ) << "substr('HeLLo', 3,2)" << false << QVariant( "LL" );
QTest::newRow( "substr outside" ) << "substr('HeLLo', -5,2)" << false << QVariant( "" );
QTest::newRow( "regexp_substr" ) << "regexp_substr('abc123','(\\\\d+)')" << false << QVariant( "123" );
QTest::newRow( "regexp_substr invalid" ) << "regexp_substr('abc123','([[[')" << true << QVariant();
QTest::newRow( "strpos" ) << "strpos('Hello World','World')" << false << QVariant( 6 );
QTest::newRow( "strpos outside" ) << "strpos('Hello World','blah')" << false << QVariant( -1 );
QTest::newRow( "left" ) << "left('Hello World',5)" << false << QVariant( "Hello" );
Expand All @@ -283,6 +302,8 @@ class TestQgsExpression: public QObject
QTest::newRow( "lpad" ) << "lpad('Hello', 10, 'x')" << false << QVariant( "Helloxxxxx" );
QTest::newRow( "lpad truncate" ) << "rpad('Hello', 4, 'x')" << false << QVariant( "Hell" );
QTest::newRow( "title" ) << "title(' HeLlO WORLD ')" << false << QVariant( " Hello World " );
QTest::newRow( "trim" ) << "trim(' Test String ')" << false << QVariant( "Test String" );
QTest::newRow( "trim empty string" ) << "trim('')" << false << QVariant( "" );
QTest::newRow( "format" ) << "format('%1 %2 %3 %1', 'One', 'Two', 'Three')" << false << QVariant( "One Two Three One" );

// implicit conversions
Expand Down Expand Up @@ -454,6 +475,40 @@ class TestQgsExpression: public QObject
QCOMPARE( v.toInt(), 200 );
}

void eval_rand()
{
QgsExpression exp1( "rand(1,10)" );
QVariant v1 = exp1.evaluate();
QCOMPARE( v1.toInt() <= 10, true );
QCOMPARE( v1.toInt() >= 1, true );

QgsExpression exp2( "rand(-5,-5)" );
QVariant v2 = exp2.evaluate();
QCOMPARE( v2.toInt(), -5 );

// Invalid expression since max<min
QgsExpression exp3( "rand(10,1)" );
QVariant v3 = exp3.evaluate();
QCOMPARE( v3.type(), QVariant::Invalid );
}

void eval_randf()
{
QgsExpression exp1( "randf(1.5,9.5)" );
QVariant v1 = exp1.evaluate();
QCOMPARE( v1.toDouble() <= 9.5, true );
QCOMPARE( v1.toDouble() >= 1.5, true );

QgsExpression exp2( "randf(-0.0005,-0.0005)" );
QVariant v2 = exp2.evaluate();
QCOMPARE( v2.toDouble(), -0.0005 );

// Invalid expression since max<min
QgsExpression exp3( "randf(9.3333,1.784)" );
QVariant v3 = exp3.evaluate();
QCOMPARE( v3.type(), QVariant::Invalid );
}

void referenced_columns()
{
QSet<QString> expectedCols;
Expand Down