LCOV - code coverage report
Current view: top level - core/expression - qgsexpression.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 7 720 1.0 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :                               qgsexpression.cpp
       3                 :            :                              -------------------
       4                 :            :     begin                : August 2011
       5                 :            :     copyright            : (C) 2011 Martin Dobias
       6                 :            :     email                : wonder.sk at gmail dot com
       7                 :            :  ***************************************************************************
       8                 :            :  *                                                                         *
       9                 :            :  *   This program is free software; you can redistribute it and/or modify  *
      10                 :            :  *   it under the terms of the GNU General Public License as published by  *
      11                 :            :  *   the Free Software Foundation; either version 2 of the License, or     *
      12                 :            :  *   (at your option) any later version.                                   *
      13                 :            :  *                                                                         *
      14                 :            :  ***************************************************************************/
      15                 :            : 
      16                 :            : #include "qgsexpression.h"
      17                 :            : #include "qgsexpressionfunction.h"
      18                 :            : #include "qgsexpressionnodeimpl.h"
      19                 :            : #include "qgsfeaturerequest.h"
      20                 :            : #include "qgscolorramp.h"
      21                 :            : #include "qgslogger.h"
      22                 :            : #include "qgsexpressioncontext.h"
      23                 :            : #include "qgsgeometry.h"
      24                 :            : #include "qgsproject.h"
      25                 :            : #include "qgsexpressioncontextutils.h"
      26                 :            : #include "qgsexpression_p.h"
      27                 :            : 
      28                 :            : #include <QRegularExpression>
      29                 :            : 
      30                 :            : // from parser
      31                 :            : extern QgsExpressionNode *parseExpression( const QString &str, QString &parserErrorMsg, QList<QgsExpression::ParserError> &parserErrors );
      32                 :            : 
      33                 :          0 : Q_GLOBAL_STATIC( HelpTextHash, sFunctionHelpTexts )
      34                 :          0 : Q_GLOBAL_STATIC( QgsStringMap, sVariableHelpTexts )
      35                 :          0 : Q_GLOBAL_STATIC( QgsStringMap, sGroups )
      36                 :            : 
      37                 :          0 : HelpTextHash &functionHelpTexts()
      38                 :            : {
      39                 :          0 :   return *sFunctionHelpTexts();
      40                 :            : }
      41                 :            : 
      42                 :          0 : bool QgsExpression::checkExpression( const QString &text, const QgsExpressionContext *context, QString &errorMessage )
      43                 :            : {
      44                 :          0 :   QgsExpression exp( text );
      45                 :          0 :   exp.prepare( context );
      46                 :          0 :   errorMessage = exp.parserErrorString();
      47                 :          0 :   return !exp.hasParserError();
      48                 :          0 : }
      49                 :            : 
      50                 :          0 : void QgsExpression::setExpression( const QString &expression )
      51                 :            : {
      52                 :          0 :   detach();
      53                 :          0 :   d->mRootNode = ::parseExpression( expression, d->mParserErrorString, d->mParserErrors );
      54                 :          0 :   d->mEvalErrorString = QString();
      55                 :          0 :   d->mExp = expression;
      56                 :          0 :   d->mIsPrepared = false;
      57                 :          0 : }
      58                 :            : 
      59                 :          0 : QString QgsExpression::expression() const
      60                 :            : {
      61                 :          0 :   if ( !d->mExp.isNull() )
      62                 :          0 :     return d->mExp;
      63                 :            :   else
      64                 :          0 :     return dump();
      65                 :          0 : }
      66                 :            : 
      67                 :          0 : QString QgsExpression::quotedColumnRef( QString name )
      68                 :            : {
      69                 :          0 :   return QStringLiteral( "\"%1\"" ).arg( name.replace( '\"', QLatin1String( "\"\"" ) ) );
      70                 :          0 : }
      71                 :            : 
      72                 :          0 : QString QgsExpression::quotedString( QString text )
      73                 :            : {
      74                 :          0 :   text.replace( '\'', QLatin1String( "''" ) );
      75                 :          0 :   text.replace( '\\', QLatin1String( "\\\\" ) );
      76                 :          0 :   text.replace( '\n', QLatin1String( "\\n" ) );
      77                 :          0 :   text.replace( '\t', QLatin1String( "\\t" ) );
      78                 :          0 :   return QStringLiteral( "'%1'" ).arg( text );
      79                 :          0 : }
      80                 :            : 
      81                 :          0 : QString QgsExpression::quotedValue( const QVariant &value )
      82                 :            : {
      83                 :          0 :   return quotedValue( value, value.type() );
      84                 :            : }
      85                 :            : 
      86                 :          0 : QString QgsExpression::quotedValue( const QVariant &value, QVariant::Type type )
      87                 :            : {
      88                 :          0 :   if ( value.isNull() )
      89                 :          0 :     return QStringLiteral( "NULL" );
      90                 :            : 
      91                 :          0 :   switch ( type )
      92                 :            :   {
      93                 :            :     case QVariant::Int:
      94                 :            :     case QVariant::LongLong:
      95                 :            :     case QVariant::Double:
      96                 :          0 :       return value.toString();
      97                 :            : 
      98                 :            :     case QVariant::Bool:
      99                 :          0 :       return value.toBool() ? QStringLiteral( "TRUE" ) : QStringLiteral( "FALSE" );
     100                 :            : 
     101                 :            :     case QVariant::List:
     102                 :            :     {
     103                 :          0 :       QStringList quotedValues;
     104                 :          0 :       const QVariantList values = value.toList();
     105                 :          0 :       quotedValues.reserve( values.count() );
     106                 :          0 :       for ( const QVariant &v : values )
     107                 :            :       {
     108                 :          0 :         quotedValues += quotedValue( v );
     109                 :            :       }
     110                 :          0 :       return QStringLiteral( "array( %1 )" ).arg( quotedValues.join( QLatin1String( ", " ) ) );
     111                 :          0 :     }
     112                 :            : 
     113                 :            :     default:
     114                 :            :     case QVariant::String:
     115                 :          0 :       return quotedString( value.toString() );
     116                 :            :   }
     117                 :            : 
     118                 :          0 : }
     119                 :            : 
     120                 :          0 : bool QgsExpression::isFunctionName( const QString &name )
     121                 :            : {
     122                 :          0 :   return functionIndex( name ) != -1;
     123                 :            : }
     124                 :            : 
     125                 :          0 : int QgsExpression::functionIndex( const QString &name )
     126                 :            : {
     127                 :          0 :   int count = functionCount();
     128                 :          0 :   for ( int i = 0; i < count; i++ )
     129                 :            :   {
     130                 :          0 :     if ( QString::compare( name, QgsExpression::Functions()[i]->name(), Qt::CaseInsensitive ) == 0 )
     131                 :          0 :       return i;
     132                 :          0 :     const QStringList aliases = QgsExpression::Functions()[i]->aliases();
     133                 :          0 :     for ( const QString &alias : aliases )
     134                 :            :     {
     135                 :          0 :       if ( QString::compare( name, alias, Qt::CaseInsensitive ) == 0 )
     136                 :          0 :         return i;
     137                 :            :     }
     138                 :          0 :   }
     139                 :          0 :   return -1;
     140                 :          0 : }
     141                 :            : 
     142                 :          0 : int QgsExpression::functionCount()
     143                 :            : {
     144                 :          0 :   return Functions().size();
     145                 :            : }
     146                 :            : 
     147                 :            : 
     148                 :          0 : QgsExpression::QgsExpression( const QString &expr )
     149                 :          0 :   : d( new QgsExpressionPrivate )
     150                 :            : {
     151                 :          0 :   d->mRootNode = ::parseExpression( expr, d->mParserErrorString, d->mParserErrors );
     152                 :          0 :   d->mExp = expr;
     153                 :            :   Q_ASSERT( !d->mParserErrorString.isNull() || d->mRootNode );
     154                 :          0 : }
     155                 :            : 
     156                 :          0 : QgsExpression::QgsExpression( const QgsExpression &other )
     157                 :          0 :   : d( other.d )
     158                 :            : {
     159                 :          0 :   d->ref.ref();
     160                 :          0 : }
     161                 :            : 
     162                 :          0 : QgsExpression &QgsExpression::operator=( const QgsExpression &other )
     163                 :            : {
     164                 :          0 :   if ( this != &other )
     165                 :            :   {
     166                 :          0 :     if ( !d->ref.deref() )
     167                 :            :     {
     168                 :          0 :       delete d;
     169                 :          0 :     }
     170                 :            : 
     171                 :          0 :     d = other.d;
     172                 :          0 :     d->ref.ref();
     173                 :          0 :   }
     174                 :          0 :   return *this;
     175                 :            : }
     176                 :            : 
     177                 :          0 : QgsExpression::operator QString() const
     178                 :            : {
     179                 :          0 :   return d->mExp;
     180                 :            : }
     181                 :            : 
     182                 :         78 : QgsExpression::QgsExpression()
     183                 :         78 :   : d( new QgsExpressionPrivate )
     184                 :            : {
     185                 :         78 : }
     186                 :            : 
     187                 :         62 : QgsExpression::~QgsExpression()
     188                 :            : {
     189                 :            :   Q_ASSERT( d );
     190                 :         62 :   if ( !d->ref.deref() )
     191                 :         62 :     delete d;
     192                 :         62 : }
     193                 :            : 
     194                 :          0 : bool QgsExpression::operator==( const QgsExpression &other ) const
     195                 :            : {
     196                 :          0 :   return ( d == other.d || d->mExp == other.d->mExp );
     197                 :            : }
     198                 :            : 
     199                 :          0 : bool QgsExpression::isValid() const
     200                 :            : {
     201                 :          0 :   return d->mRootNode;
     202                 :            : }
     203                 :            : 
     204                 :          0 : bool QgsExpression::hasParserError() const
     205                 :            : {
     206                 :          0 :   return d->mParserErrors.count() > 0;
     207                 :            : }
     208                 :            : 
     209                 :          0 : QString QgsExpression::parserErrorString() const
     210                 :            : {
     211                 :          0 :   return d->mParserErrorString;
     212                 :            : }
     213                 :            : 
     214                 :          0 : QList<QgsExpression::ParserError> QgsExpression::parserErrors() const
     215                 :            : {
     216                 :          0 :   return d->mParserErrors;
     217                 :            : }
     218                 :            : 
     219                 :          0 : QSet<QString> QgsExpression::referencedColumns() const
     220                 :            : {
     221                 :          0 :   if ( !d->mRootNode )
     222                 :          0 :     return QSet<QString>();
     223                 :            : 
     224                 :          0 :   return d->mRootNode->referencedColumns();
     225                 :          0 : }
     226                 :            : 
     227                 :          0 : QSet<QString> QgsExpression::referencedVariables() const
     228                 :            : {
     229                 :          0 :   if ( !d->mRootNode )
     230                 :          0 :     return QSet<QString>();
     231                 :            : 
     232                 :          0 :   return d->mRootNode->referencedVariables();
     233                 :          0 : }
     234                 :            : 
     235                 :          0 : QSet<QString> QgsExpression::referencedFunctions() const
     236                 :            : {
     237                 :          0 :   if ( !d->mRootNode )
     238                 :          0 :     return QSet<QString>();
     239                 :            : 
     240                 :          0 :   return d->mRootNode->referencedFunctions();
     241                 :          0 : }
     242                 :            : 
     243                 :          0 : QSet<int> QgsExpression::referencedAttributeIndexes( const QgsFields &fields ) const
     244                 :            : {
     245                 :          0 :   if ( !d->mRootNode )
     246                 :          0 :     return QSet<int>();
     247                 :            : 
     248                 :          0 :   const QSet<QString> referencedFields = d->mRootNode->referencedColumns();
     249                 :          0 :   QSet<int> referencedIndexes;
     250                 :            : 
     251                 :          0 :   for ( const QString &fieldName : referencedFields )
     252                 :            :   {
     253                 :          0 :     if ( fieldName == QgsFeatureRequest::ALL_ATTRIBUTES )
     254                 :            :     {
     255                 :          0 :       referencedIndexes = qgis::listToSet( fields.allAttributesList() );
     256                 :          0 :       break;
     257                 :            :     }
     258                 :          0 :     const int idx = fields.lookupField( fieldName );
     259                 :          0 :     if ( idx >= 0 )
     260                 :            :     {
     261                 :          0 :       referencedIndexes << idx;
     262                 :          0 :     }
     263                 :            :   }
     264                 :            : 
     265                 :          0 :   return referencedIndexes;
     266                 :          0 : }
     267                 :            : 
     268                 :          0 : bool QgsExpression::needsGeometry() const
     269                 :            : {
     270                 :          0 :   if ( !d->mRootNode )
     271                 :          0 :     return false;
     272                 :          0 :   return d->mRootNode->needsGeometry();
     273                 :          0 : }
     274                 :            : 
     275                 :          0 : void QgsExpression::initGeomCalculator( const QgsExpressionContext *context )
     276                 :            : {
     277                 :            :   // Set the geometry calculator from the context if it has not been set by setGeomCalculator()
     278                 :          0 :   if ( context && ! d->mCalc )
     279                 :            :   {
     280                 :            :     // actually don't do it right away, cos it's expensive to create and only a very small number of expression
     281                 :            :     // functions actually require it. Let's lazily construct it when needed
     282                 :          0 :     d->mDaEllipsoid = context->variable( QStringLiteral( "project_ellipsoid" ) ).toString();
     283                 :          0 :     d->mDaCrs = std::make_unique<QgsCoordinateReferenceSystem>( context->variable( QStringLiteral( "_layer_crs" ) ).value<QgsCoordinateReferenceSystem>() );
     284                 :          0 :     d->mDaTransformContext = std::make_unique<QgsCoordinateTransformContext>( context->variable( QStringLiteral( "_project_transform_context" ) ).value<QgsCoordinateTransformContext>() );
     285                 :          0 :   }
     286                 :            : 
     287                 :            :   // Set the distance units from the context if it has not been set by setDistanceUnits()
     288                 :          0 :   if ( context && distanceUnits() == QgsUnitTypes::DistanceUnknownUnit )
     289                 :            :   {
     290                 :          0 :     QString distanceUnitsStr = context->variable( QStringLiteral( "project_distance_units" ) ).toString();
     291                 :          0 :     if ( ! distanceUnitsStr.isEmpty() )
     292                 :          0 :       setDistanceUnits( QgsUnitTypes::stringToDistanceUnit( distanceUnitsStr ) );
     293                 :          0 :   }
     294                 :            : 
     295                 :            :   // Set the area units from the context if it has not been set by setAreaUnits()
     296                 :          0 :   if ( context && areaUnits() == QgsUnitTypes::AreaUnknownUnit )
     297                 :            :   {
     298                 :          0 :     QString areaUnitsStr = context->variable( QStringLiteral( "project_area_units" ) ).toString();
     299                 :          0 :     if ( ! areaUnitsStr.isEmpty() )
     300                 :          0 :       setAreaUnits( QgsUnitTypes::stringToAreaUnit( areaUnitsStr ) );
     301                 :          0 :   }
     302                 :          0 : }
     303                 :            : 
     304                 :          0 : void QgsExpression::detach()
     305                 :            : {
     306                 :            :   Q_ASSERT( d );
     307                 :            : 
     308                 :          0 :   if ( d->ref > 1 )
     309                 :            :   {
     310                 :          0 :     ( void )d->ref.deref();
     311                 :            : 
     312                 :          0 :     d = new QgsExpressionPrivate( *d );
     313                 :          0 :   }
     314                 :          0 : }
     315                 :            : 
     316                 :          0 : void QgsExpression::setGeomCalculator( const QgsDistanceArea *calc )
     317                 :            : {
     318                 :          0 :   detach();
     319                 :          0 :   if ( calc )
     320                 :          0 :     d->mCalc = std::shared_ptr<QgsDistanceArea>( new QgsDistanceArea( *calc ) );
     321                 :            :   else
     322                 :          0 :     d->mCalc.reset();
     323                 :          0 : }
     324                 :            : 
     325                 :          0 : bool QgsExpression::prepare( const QgsExpressionContext *context )
     326                 :            : {
     327                 :          0 :   detach();
     328                 :          0 :   d->mEvalErrorString = QString();
     329                 :          0 :   if ( !d->mRootNode )
     330                 :            :   {
     331                 :            :     //re-parse expression. Creation of QgsExpressionContexts may have added extra
     332                 :            :     //known functions since this expression was created, so we have another try
     333                 :            :     //at re-parsing it now that the context must have been created
     334                 :          0 :     d->mRootNode = ::parseExpression( d->mExp, d->mParserErrorString, d->mParserErrors );
     335                 :          0 :   }
     336                 :            : 
     337                 :          0 :   if ( !d->mRootNode )
     338                 :            :   {
     339                 :          0 :     d->mEvalErrorString = tr( "No root node! Parsing failed?" );
     340                 :          0 :     return false;
     341                 :            :   }
     342                 :            : 
     343                 :          0 :   initGeomCalculator( context );
     344                 :          0 :   d->mIsPrepared = true;
     345                 :          0 :   return d->mRootNode->prepare( this, context );
     346                 :          0 : }
     347                 :            : 
     348                 :          0 : QVariant QgsExpression::evaluate()
     349                 :            : {
     350                 :          0 :   d->mEvalErrorString = QString();
     351                 :          0 :   if ( !d->mRootNode )
     352                 :            :   {
     353                 :          0 :     d->mEvalErrorString = tr( "No root node! Parsing failed?" );
     354                 :          0 :     return QVariant();
     355                 :            :   }
     356                 :            : 
     357                 :          0 :   return d->mRootNode->eval( this, static_cast<const QgsExpressionContext *>( nullptr ) );
     358                 :          0 : }
     359                 :            : 
     360                 :          0 : QVariant QgsExpression::evaluate( const QgsExpressionContext *context )
     361                 :            : {
     362                 :          0 :   d->mEvalErrorString = QString();
     363                 :          0 :   if ( !d->mRootNode )
     364                 :            :   {
     365                 :          0 :     d->mEvalErrorString = tr( "No root node! Parsing failed?" );
     366                 :          0 :     return QVariant();
     367                 :            :   }
     368                 :            : 
     369                 :          0 :   if ( ! d->mIsPrepared )
     370                 :            :   {
     371                 :          0 :     prepare( context );
     372                 :          0 :   }
     373                 :          0 :   return d->mRootNode->eval( this, context );
     374                 :          0 : }
     375                 :            : 
     376                 :          0 : bool QgsExpression::hasEvalError() const
     377                 :            : {
     378                 :          0 :   return !d->mEvalErrorString.isNull();
     379                 :            : }
     380                 :            : 
     381                 :          0 : QString QgsExpression::evalErrorString() const
     382                 :            : {
     383                 :          0 :   return d->mEvalErrorString;
     384                 :            : }
     385                 :            : 
     386                 :          0 : void QgsExpression::setEvalErrorString( const QString &str )
     387                 :            : {
     388                 :          0 :   d->mEvalErrorString = str;
     389                 :          0 : }
     390                 :            : 
     391                 :          0 : QString QgsExpression::dump() const
     392                 :            : {
     393                 :          0 :   if ( !d->mRootNode )
     394                 :          0 :     return QString();
     395                 :            : 
     396                 :          0 :   return d->mRootNode->dump();
     397                 :          0 : }
     398                 :            : 
     399                 :          0 : QgsDistanceArea *QgsExpression::geomCalculator()
     400                 :            : {
     401                 :          0 :   if ( !d->mCalc && d->mDaCrs && d->mDaCrs->isValid() && d->mDaTransformContext )
     402                 :            :   {
     403                 :            :     // calculator IS required, so initialize it now...
     404                 :          0 :     d->mCalc = std::shared_ptr<QgsDistanceArea>( new QgsDistanceArea() );
     405                 :          0 :     d->mCalc->setEllipsoid( d->mDaEllipsoid.isEmpty() ? geoNone() : d->mDaEllipsoid );
     406                 :          0 :     d->mCalc->setSourceCrs( *d->mDaCrs.get(), *d->mDaTransformContext.get() );
     407                 :          0 :   }
     408                 :            : 
     409                 :          0 :   return d->mCalc.get();
     410                 :          0 : }
     411                 :            : 
     412                 :          0 : QgsUnitTypes::DistanceUnit QgsExpression::distanceUnits() const
     413                 :            : {
     414                 :          0 :   return d->mDistanceUnit;
     415                 :            : }
     416                 :            : 
     417                 :          0 : void QgsExpression::setDistanceUnits( QgsUnitTypes::DistanceUnit unit )
     418                 :            : {
     419                 :          0 :   d->mDistanceUnit = unit;
     420                 :          0 : }
     421                 :            : 
     422                 :          0 : QgsUnitTypes::AreaUnit QgsExpression::areaUnits() const
     423                 :            : {
     424                 :          0 :   return d->mAreaUnit;
     425                 :            : }
     426                 :            : 
     427                 :          0 : void QgsExpression::setAreaUnits( QgsUnitTypes::AreaUnit unit )
     428                 :            : {
     429                 :          0 :   d->mAreaUnit = unit;
     430                 :          0 : }
     431                 :            : 
     432                 :          0 : QString QgsExpression::replaceExpressionText( const QString &action, const QgsExpressionContext *context, const QgsDistanceArea *distanceArea )
     433                 :            : {
     434                 :          0 :   QString expr_action;
     435                 :            : 
     436                 :          0 :   int index = 0;
     437                 :          0 :   while ( index < action.size() )
     438                 :            :   {
     439                 :          0 :     static const QRegularExpression sRegEx{ QStringLiteral( "\\[%(.*?)%\\]" ),  QRegularExpression::MultilineOption | QRegularExpression::DotMatchesEverythingOption };
     440                 :            : 
     441                 :          0 :     const QRegularExpressionMatch match = sRegEx.match( action, index );
     442                 :          0 :     if ( !match.hasMatch() )
     443                 :          0 :       break;
     444                 :            : 
     445                 :          0 :     const int pos = action.indexOf( sRegEx, index );
     446                 :          0 :     const int start = index;
     447                 :          0 :     index = pos + match.capturedLength( 0 );
     448                 :          0 :     const QString toReplace = match.captured( 1 ).trimmed();
     449                 :          0 :     QgsDebugMsgLevel( "Found expression: " + toReplace, 3 );
     450                 :            : 
     451                 :          0 :     QgsExpression exp( toReplace );
     452                 :          0 :     if ( exp.hasParserError() )
     453                 :            :     {
     454                 :          0 :       QgsDebugMsg( "Expression parser error: " + exp.parserErrorString() );
     455                 :          0 :       expr_action += action.midRef( start, index - start );
     456                 :          0 :       continue;
     457                 :            :     }
     458                 :            : 
     459                 :          0 :     if ( distanceArea )
     460                 :            :     {
     461                 :            :       //if QgsDistanceArea specified for area/distance conversion, use it
     462                 :          0 :       exp.setGeomCalculator( distanceArea );
     463                 :          0 :     }
     464                 :            : 
     465                 :          0 :     QVariant result = exp.evaluate( context );
     466                 :            : 
     467                 :          0 :     if ( exp.hasEvalError() )
     468                 :            :     {
     469                 :          0 :       QgsDebugMsg( "Expression parser eval error: " + exp.evalErrorString() );
     470                 :          0 :       expr_action += action.midRef( start, index - start );
     471                 :          0 :       continue;
     472                 :            :     }
     473                 :            : 
     474                 :          0 :     QgsDebugMsgLevel( "Expression result is: " + result.toString(), 3 );
     475                 :          0 :     expr_action += action.mid( start, pos - start ) + result.toString();
     476                 :          0 :   }
     477                 :            : 
     478                 :          0 :   expr_action += action.midRef( index );
     479                 :            : 
     480                 :          0 :   return expr_action;
     481                 :          0 : }
     482                 :            : 
     483                 :          0 : QSet<QString> QgsExpression::referencedVariables( const QString &text )
     484                 :            : {
     485                 :          0 :   QSet<QString> variables;
     486                 :          0 :   int index = 0;
     487                 :          0 :   while ( index < text.size() )
     488                 :            :   {
     489                 :          0 :     QRegExp rx = QRegExp( "\\[%([^\\]]+)%\\]" );
     490                 :            : 
     491                 :          0 :     int pos = rx.indexIn( text, index );
     492                 :          0 :     if ( pos < 0 )
     493                 :          0 :       break;
     494                 :            : 
     495                 :          0 :     index = pos + rx.matchedLength();
     496                 :          0 :     QString to_replace = rx.cap( 1 ).trimmed();
     497                 :            : 
     498                 :          0 :     QgsExpression exp( to_replace );
     499                 :          0 :     variables.unite( exp.referencedVariables() );
     500                 :          0 :   }
     501                 :            : 
     502                 :          0 :   return variables;
     503                 :          0 : }
     504                 :            : 
     505                 :          0 : double QgsExpression::evaluateToDouble( const QString &text, const double fallbackValue )
     506                 :            : {
     507                 :            :   bool ok;
     508                 :            :   //first test if text is directly convertible to double
     509                 :            :   // use system locale: e.g. in German locale, user is presented with numbers "1,23" instead of "1.23" in C locale
     510                 :            :   // so we also want to allow user to rewrite it to "5,23" and it is still accepted
     511                 :          0 :   double convertedValue = QLocale().toDouble( text, &ok );
     512                 :          0 :   if ( ok )
     513                 :            :   {
     514                 :          0 :     return convertedValue;
     515                 :            :   }
     516                 :            : 
     517                 :            :   //otherwise try to evaluate as expression
     518                 :          0 :   QgsExpression expr( text );
     519                 :            : 
     520                 :          0 :   QgsExpressionContext context;
     521                 :          0 :   context << QgsExpressionContextUtils::globalScope()
     522                 :          0 :           << QgsExpressionContextUtils::projectScope( QgsProject::instance() );
     523                 :            : 
     524                 :          0 :   QVariant result = expr.evaluate( &context );
     525                 :          0 :   convertedValue = result.toDouble( &ok );
     526                 :          0 :   if ( expr.hasEvalError() || !ok )
     527                 :            :   {
     528                 :          0 :     return fallbackValue;
     529                 :            :   }
     530                 :          0 :   return convertedValue;
     531                 :          0 : }
     532                 :            : 
     533                 :          0 : QString QgsExpression::helpText( QString name )
     534                 :            : {
     535                 :          0 :   QgsExpression::initFunctionHelp();
     536                 :            : 
     537                 :          0 :   if ( !sFunctionHelpTexts()->contains( name ) )
     538                 :          0 :     return tr( "function help for %1 missing" ).arg( name );
     539                 :            : 
     540                 :          0 :   const Help &f = ( *sFunctionHelpTexts() )[ name ];
     541                 :            : 
     542                 :          0 :   name = f.mName;
     543                 :          0 :   if ( f.mType == tr( "group" ) )
     544                 :            :   {
     545                 :          0 :     name = group( name );
     546                 :          0 :     name = name.toLower();
     547                 :          0 :   }
     548                 :            : 
     549                 :          0 :   name = name.toHtmlEscaped();
     550                 :            : 
     551                 :          0 :   QString helpContents( QStringLiteral( "<h3>%1</h3>\n<div class=\"description\"><p>%2</p></div>" )
     552                 :          0 :                         .arg( tr( "%1 %2" ).arg( f.mType, name ),
     553                 :          0 :                               f.mDescription ) );
     554                 :            : 
     555                 :          0 :   for ( const HelpVariant &v : std::as_const( f.mVariants ) )
     556                 :            :   {
     557                 :          0 :     if ( f.mVariants.size() > 1 )
     558                 :            :     {
     559                 :          0 :       helpContents += QStringLiteral( "<h3>%1</h3>\n<div class=\"description\">%2</p></div>" ).arg( v.mName, v.mDescription );
     560                 :          0 :     }
     561                 :            : 
     562                 :          0 :     if ( f.mType != tr( "group" ) && f.mType != tr( "expression" ) )
     563                 :          0 :       helpContents += QStringLiteral( "<h4>%1</h4>\n<div class=\"syntax\">\n" ).arg( tr( "Syntax" ) );
     564                 :            : 
     565                 :          0 :     if ( f.mType == tr( "operator" ) )
     566                 :            :     {
     567                 :          0 :       if ( v.mArguments.size() == 1 )
     568                 :            :       {
     569                 :          0 :         helpContents += QStringLiteral( "<code><span class=\"functionname\">%1</span> <span class=\"argument\">%2</span></code>" )
     570                 :          0 :                         .arg( name, v.mArguments[0].mArg );
     571                 :          0 :       }
     572                 :          0 :       else if ( v.mArguments.size() == 2 )
     573                 :            :       {
     574                 :          0 :         helpContents += QStringLiteral( "<code><span class=\"argument\">%1</span> <span class=\"functionname\">%2</span> <span class=\"argument\">%3</span></code>" )
     575                 :          0 :                         .arg( v.mArguments[0].mArg, name, v.mArguments[1].mArg );
     576                 :          0 :       }
     577                 :          0 :     }
     578                 :          0 :     else if ( f.mType != tr( "group" ) && f.mType != tr( "expression" ) )
     579                 :            :     {
     580                 :          0 :       helpContents += QStringLiteral( "<code><span class=\"functionname\">%1</span>" ).arg( name );
     581                 :            : 
     582                 :          0 :       bool hasOptionalArgs = false;
     583                 :            : 
     584                 :          0 :       if ( f.mType == tr( "function" ) && ( f.mName[0] != '$' || !v.mArguments.isEmpty() || v.mVariableLenArguments ) )
     585                 :            :       {
     586                 :          0 :         helpContents += '(';
     587                 :            : 
     588                 :          0 :         QString delim;
     589                 :          0 :         for ( const HelpArg &a : std::as_const( v.mArguments ) )
     590                 :            :         {
     591                 :          0 :           if ( !a.mDescOnly )
     592                 :            :           {
     593                 :          0 :             if ( a.mOptional )
     594                 :            :             {
     595                 :          0 :               hasOptionalArgs = true;
     596                 :          0 :               helpContents += QLatin1Char( '[' );
     597                 :          0 :             }
     598                 :            : 
     599                 :          0 :             helpContents += delim;
     600                 :          0 :             helpContents += QStringLiteral( "<span class=\"argument\">%2%3</span>" ).arg(
     601                 :          0 :                               a.mArg,
     602                 :          0 :                               a.mDefaultVal.isEmpty() ? QString() : '=' + a.mDefaultVal
     603                 :            :                             );
     604                 :            : 
     605                 :          0 :             if ( a.mOptional )
     606                 :          0 :               helpContents += QLatin1Char( ']' );
     607                 :          0 :           }
     608                 :          0 :           delim = QStringLiteral( "," );
     609                 :            :         }
     610                 :            : 
     611                 :          0 :         if ( v.mVariableLenArguments )
     612                 :            :         {
     613                 :          0 :           helpContents += QChar( 0x2026 );
     614                 :          0 :         }
     615                 :            : 
     616                 :          0 :         helpContents += ')';
     617                 :          0 :       }
     618                 :            : 
     619                 :          0 :       helpContents += QLatin1String( "</code>" );
     620                 :            : 
     621                 :          0 :       if ( hasOptionalArgs )
     622                 :            :       {
     623                 :          0 :         helpContents += QLatin1String( "<br/><br/>" ) + tr( "[ ] marks optional components" );
     624                 :          0 :       }
     625                 :          0 :     }
     626                 :            : 
     627                 :          0 :     if ( !v.mArguments.isEmpty() )
     628                 :            :     {
     629                 :          0 :       helpContents += QStringLiteral( "<h4>%1</h4>\n<div class=\"arguments\">\n<table>" ).arg( tr( "Arguments" ) );
     630                 :            : 
     631                 :          0 :       for ( const HelpArg &a : std::as_const( v.mArguments ) )
     632                 :            :       {
     633                 :          0 :         if ( a.mSyntaxOnly )
     634                 :          0 :           continue;
     635                 :            : 
     636                 :          0 :         helpContents += QStringLiteral( "<tr><td class=\"argument\">%1</td><td>%2</td></tr>" ).arg( a.mArg, a.mDescription );
     637                 :            :       }
     638                 :            : 
     639                 :          0 :       helpContents += QLatin1String( "</table>\n</div>\n" );
     640                 :          0 :     }
     641                 :            : 
     642                 :          0 :     if ( !v.mExamples.isEmpty() )
     643                 :            :     {
     644                 :          0 :       helpContents += QStringLiteral( "<h4>%1</h4>\n<div class=\"examples\">\n<ul>\n" ).arg( tr( "Examples" ) );
     645                 :            : 
     646                 :          0 :       for ( const HelpExample &e : std::as_const( v.mExamples ) )
     647                 :            :       {
     648                 :          0 :         helpContents += "<li><code>" + e.mExpression + "</code> &rarr; <code>" + e.mReturns + "</code>";
     649                 :            : 
     650                 :          0 :         if ( !e.mNote.isEmpty() )
     651                 :          0 :           helpContents += QStringLiteral( " (%1)" ).arg( e.mNote );
     652                 :            : 
     653                 :          0 :         helpContents += QLatin1String( "</li>\n" );
     654                 :            :       }
     655                 :            : 
     656                 :          0 :       helpContents += QLatin1String( "</ul>\n</div>\n" );
     657                 :          0 :     }
     658                 :            : 
     659                 :          0 :     if ( !v.mNotes.isEmpty() )
     660                 :            :     {
     661                 :          0 :       helpContents += QStringLiteral( "<h4>%1</h4>\n<div class=\"notes\"><p>%2</p></div>\n" ).arg( tr( "Notes" ), v.mNotes );
     662                 :          0 :     }
     663                 :            :   }
     664                 :            : 
     665                 :          0 :   return helpContents;
     666                 :          0 : }
     667                 :            : 
     668                 :          0 : QStringList QgsExpression::tags( const QString &name )
     669                 :            : {
     670                 :          0 :   QStringList tags = QStringList();
     671                 :            : 
     672                 :          0 :   QgsExpression::initFunctionHelp();
     673                 :            : 
     674                 :          0 :   if ( sFunctionHelpTexts()->contains( name ) )
     675                 :            :   {
     676                 :          0 :     const Help &f = ( *sFunctionHelpTexts() )[ name ];
     677                 :            : 
     678                 :          0 :     for ( const HelpVariant &v : std::as_const( f.mVariants ) )
     679                 :            :     {
     680                 :          0 :       tags << v.mTags;
     681                 :            :     }
     682                 :          0 :   }
     683                 :            : 
     684                 :          0 :   return tags;
     685                 :          0 : }
     686                 :            : 
     687                 :          0 : void QgsExpression::initVariableHelp()
     688                 :            : {
     689                 :          0 :   if ( !sVariableHelpTexts()->isEmpty() )
     690                 :          0 :     return;
     691                 :            : 
     692                 :            :   //global variables
     693                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "qgis_version" ), QCoreApplication::translate( "variable_help", "Current QGIS version string." ) );
     694                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "qgis_version_no" ), QCoreApplication::translate( "variable_help", "Current QGIS version number." ) );
     695                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "qgis_release_name" ), QCoreApplication::translate( "variable_help", "Current QGIS release name." ) );
     696                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "qgis_short_version" ), QCoreApplication::translate( "variable_help", "Short QGIS version string." ) );
     697                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "qgis_os_name" ), QCoreApplication::translate( "variable_help", "Operating system name, e.g., 'windows', 'linux' or 'osx'." ) );
     698                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "qgis_platform" ), QCoreApplication::translate( "variable_help", "QGIS platform, e.g., 'desktop' or 'server'." ) );
     699                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "qgis_locale" ), QCoreApplication::translate( "variable_help", "Two letter identifier for current QGIS locale." ) );
     700                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "user_account_name" ), QCoreApplication::translate( "variable_help", "Current user's operating system account name." ) );
     701                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "user_full_name" ), QCoreApplication::translate( "variable_help", "Current user's operating system user name (if available)." ) );
     702                 :            : 
     703                 :            :   //project variables
     704                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_title" ), QCoreApplication::translate( "variable_help", "Title of current project." ) );
     705                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_path" ), QCoreApplication::translate( "variable_help", "Full path (including file name) of current project." ) );
     706                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_folder" ), QCoreApplication::translate( "variable_help", "Folder for current project." ) );
     707                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_filename" ), QCoreApplication::translate( "variable_help", "Filename of current project." ) );
     708                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_basename" ), QCoreApplication::translate( "variable_help", "Base name of current project's filename (without path and extension)." ) );
     709                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_home" ), QCoreApplication::translate( "variable_help", "Home path of current project." ) );
     710                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_crs" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of project (e.g., 'EPSG:4326')." ) );
     711                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_crs_definition" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of project (full definition)." ) );
     712                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_units" ), QCoreApplication::translate( "variable_help", "Unit of the project's CRS." ) );
     713                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_crs_description" ), QCoreApplication::translate( "variable_help", "Name of the coordinate reference system of the project." ) );
     714                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_crs_acronym" ), QCoreApplication::translate( "variable_help", "Acronym of the coordinate reference system of the project." ) );
     715                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_crs_ellipsoid" ), QCoreApplication::translate( "variable_help", "Acronym of the ellipsoid of the coordinate reference system of the project." ) );
     716                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_crs_proj4" ), QCoreApplication::translate( "variable_help", "Proj4 definition of the coordinate reference system of the project." ) );
     717                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_crs_wkt" ), QCoreApplication::translate( "variable_help", "WKT definition of the coordinate reference system of the project." ) );
     718                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_author" ), QCoreApplication::translate( "variable_help", "Project author, taken from project metadata." ) );
     719                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_abstract" ), QCoreApplication::translate( "variable_help", "Project abstract, taken from project metadata." ) );
     720                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_creation_date" ), QCoreApplication::translate( "variable_help", "Project creation date, taken from project metadata." ) );
     721                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_identifier" ), QCoreApplication::translate( "variable_help", "Project identifier, taken from project metadata." ) );
     722                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_last_saved" ), QCoreApplication::translate( "variable_help", "Date/time when project was last saved." ) );
     723                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_keywords" ), QCoreApplication::translate( "variable_help", "Project keywords, taken from project metadata." ) );
     724                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_area_units" ), QCoreApplication::translate( "variable_help", "Area unit for current project, used when calculating areas of geometries." ) );
     725                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_distance_units" ), QCoreApplication::translate( "variable_help", "Distance unit for current project, used when calculating lengths of geometries." ) );
     726                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "project_ellipsoid" ), QCoreApplication::translate( "variable_help", "Name of ellipsoid of current project, used when calculating geodetic areas and lengths of geometries." ) );
     727                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "layer_ids" ), QCoreApplication::translate( "variable_help", "List of all map layer IDs from the current project." ) );
     728                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "layers" ), QCoreApplication::translate( "variable_help", "List of all map layers from the current project." ) );
     729                 :            : 
     730                 :            :   //layer variables
     731                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "layer_name" ), QCoreApplication::translate( "variable_help", "Name of current layer." ) );
     732                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "layer_id" ), QCoreApplication::translate( "variable_help", "ID of current layer." ) );
     733                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "layer_crs" ), QCoreApplication::translate( "variable_help", "CRS Authority ID of current layer." ) );
     734                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "layer" ), QCoreApplication::translate( "variable_help", "The current layer." ) );
     735                 :            : 
     736                 :            :   //composition variables
     737                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "layout_name" ), QCoreApplication::translate( "variable_help", "Name of composition." ) );
     738                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "layout_numpages" ), QCoreApplication::translate( "variable_help", "Number of pages in composition." ) );
     739                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "layout_page" ), QCoreApplication::translate( "variable_help", "Current page number in composition." ) );
     740                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "layout_pageheight" ), QCoreApplication::translate( "variable_help", "Composition page height in mm." ) );
     741                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "layout_pagewidth" ), QCoreApplication::translate( "variable_help", "Composition page width in mm." ) );
     742                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "layout_pageoffsets" ), QCoreApplication::translate( "variable_help", "Array of Y coordinate of the top of each page." ) );
     743                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "layout_dpi" ), QCoreApplication::translate( "variable_help", "Composition resolution (DPI)." ) );
     744                 :            : 
     745                 :            :   //atlas variables
     746                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "atlas_layerid" ), QCoreApplication::translate( "variable_help", "Current atlas coverage layer ID." ) );
     747                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "atlas_layername" ), QCoreApplication::translate( "variable_help", "Current atlas coverage layer name." ) );
     748                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "atlas_totalfeatures" ), QCoreApplication::translate( "variable_help", "Total number of features in atlas." ) );
     749                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "atlas_featurenumber" ), QCoreApplication::translate( "variable_help", "Current atlas feature number." ) );
     750                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "atlas_filename" ), QCoreApplication::translate( "variable_help", "Current atlas file name." ) );
     751                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "atlas_pagename" ), QCoreApplication::translate( "variable_help", "Current atlas page name." ) );
     752                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "atlas_feature" ), QCoreApplication::translate( "variable_help", "Current atlas feature (as feature object)." ) );
     753                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "atlas_featureid" ), QCoreApplication::translate( "variable_help", "Current atlas feature ID." ) );
     754                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "atlas_geometry" ), QCoreApplication::translate( "variable_help", "Current atlas feature geometry." ) );
     755                 :            : 
     756                 :            :   //layout item variables
     757                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "item_id" ), QCoreApplication::translate( "variable_help", "Layout item user-assigned ID (not necessarily unique)." ) );
     758                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "item_uuid" ), QCoreApplication::translate( "variable_help", "layout item unique ID." ) );
     759                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "item_left" ), QCoreApplication::translate( "variable_help", "Left position of layout item (in mm)." ) );
     760                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "item_top" ), QCoreApplication::translate( "variable_help", "Top position of layout item (in mm)." ) );
     761                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "item_width" ), QCoreApplication::translate( "variable_help", "Width of layout item (in mm)." ) );
     762                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "item_height" ), QCoreApplication::translate( "variable_help", "Height of layout item (in mm)." ) );
     763                 :            : 
     764                 :            :   //map settings item variables
     765                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_id" ), QCoreApplication::translate( "variable_help", "ID of current map destination. This will be 'canvas' for canvas renders, and the item ID for layout map renders." ) );
     766                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_rotation" ), QCoreApplication::translate( "variable_help", "Current rotation of map." ) );
     767                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_scale" ), QCoreApplication::translate( "variable_help", "Current scale of map." ) );
     768                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_extent" ), QCoreApplication::translate( "variable_help", "Geometry representing the current extent of the map." ) );
     769                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_extent_center" ), QCoreApplication::translate( "variable_help", "Center of map." ) );
     770                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_extent_width" ), QCoreApplication::translate( "variable_help", "Width of map." ) );
     771                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_extent_height" ), QCoreApplication::translate( "variable_help", "Height of map." ) );
     772                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_crs" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of map (e.g., 'EPSG:4326')." ) );
     773                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_crs_description" ), QCoreApplication::translate( "variable_help", "Name of the coordinate reference system of the map." ) );
     774                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_units" ), QCoreApplication::translate( "variable_help", "Units for map measurements." ) );
     775                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_crs_definition" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of the map (full definition)." ) );
     776                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_crs_acronym" ), QCoreApplication::translate( "variable_help", "Acronym of the coordinate reference system of the map." ) );
     777                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_crs_ellipsoid" ), QCoreApplication::translate( "variable_help", "Acronym of the ellipsoid of the coordinate reference system of the map." ) );
     778                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_crs_proj4" ), QCoreApplication::translate( "variable_help", "Proj4 definition of the coordinate reference system of the map." ) );
     779                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_crs_wkt" ), QCoreApplication::translate( "variable_help", "WKT definition of the coordinate reference system of the map." ) );
     780                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_layer_ids" ), QCoreApplication::translate( "variable_help", "List of map layer IDs visible in the map." ) );
     781                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_layers" ), QCoreApplication::translate( "variable_help", "List of map layers visible in the map." ) );
     782                 :            : 
     783                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_start_time" ), QCoreApplication::translate( "variable_help", "Start of the map's temporal time range (as a datetime value)" ) );
     784                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_end_time" ), QCoreApplication::translate( "variable_help", "End of the map's temporal time range (as a datetime value)" ) );
     785                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "map_interval" ), QCoreApplication::translate( "variable_help", "Duration of the map's temporal time range (as an interval value)" ) );
     786                 :            : 
     787                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "frame_rate" ), QCoreApplication::translate( "variable_help", "Number of frames per second during animation playback" ) );
     788                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "frame_number" ), QCoreApplication::translate( "variable_help", "Current frame number during animation playback" ) );
     789                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "frame_duration" ), QCoreApplication::translate( "variable_help", "Temporal duration of each animation frame (as an interval value)" ) );
     790                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "animation_start_time" ), QCoreApplication::translate( "variable_help", "Start of the animation's overall temporal time range (as a datetime value)" ) );
     791                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "animation_end_time" ), QCoreApplication::translate( "variable_help", "End of the animation's overall temporal time range (as a datetime value)" ) );
     792                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "animation_interval" ), QCoreApplication::translate( "variable_help", "Duration of the animation's overall temporal time range (as an interval value)" ) );
     793                 :            : 
     794                 :            :   // vector tile layer variables
     795                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "zoom_level" ), QCoreApplication::translate( "variable_help", "Zoom level of the tile that is being rendered (derived from the current map scale). Normally in interval [0, 20]." ) );
     796                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "vector_tile_zoom" ), QCoreApplication::translate( "variable_help", "Exact zoom level of the tile that is being rendered (derived from the current map scale). Normally in interval [0, 20]. Unlike @zoom_level, this variable is a floating point value which can be used to interpolated values between two integer zoom levels." ) );
     797                 :            : 
     798                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "row_number" ), QCoreApplication::translate( "variable_help", "Stores the number of the current row." ) );
     799                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "grid_number" ), QCoreApplication::translate( "variable_help", "Current grid annotation value." ) );
     800                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "grid_axis" ), QCoreApplication::translate( "variable_help", "Current grid annotation axis (e.g., 'x' for longitude, 'y' for latitude)." ) );
     801                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "column_number" ), QCoreApplication::translate( "variable_help", "Stores the number of the current column." ) );
     802                 :            : 
     803                 :            :   // map canvas item variables
     804                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "canvas_cursor_point" ), QCoreApplication::translate( "variable_help", "Last cursor position on the canvas in the project's geographical coordinates." ) );
     805                 :            : 
     806                 :            :   // legend canvas item variables
     807                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "legend_title" ), QCoreApplication::translate( "variable_help", "Title of the legend." ) );
     808                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "legend_column_count" ), QCoreApplication::translate( "variable_help", "Number of column in the legend." ) );
     809                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "legend_split_layers" ), QCoreApplication::translate( "variable_help", "Boolean indicating if layers can be split in the legend." ) );
     810                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "legend_wrap_string" ), QCoreApplication::translate( "variable_help", "Characters used to wrap the legend text." ) );
     811                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "legend_filter_by_map" ), QCoreApplication::translate( "variable_help", "Boolean indicating if the content of the legend is filtered by the map." ) );
     812                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "legend_filter_out_atlas" ), QCoreApplication::translate( "variable_help", "Boolean indicating if the Atlas is filtered out of the legend." ) );
     813                 :            : 
     814                 :            :   // scalebar rendering
     815                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "scale_value" ), QCoreApplication::translate( "variable_help", "Current scale bar distance value." ) );
     816                 :            : 
     817                 :            :   // map tool capture variables
     818                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "snapping_results" ), QCoreApplication::translate( "variable_help",
     819                 :            :                                 "<p>An array with an item for each snapped point.</p>"
     820                 :            :                                 "<p>Each item is a map with the following keys:</p>"
     821                 :            :                                 "<dl>"
     822                 :            :                                 "<dt>valid</dt><dd>Boolean that indicates if the snapping result is valid</dd>"
     823                 :            :                                 "<dt>layer</dt><dd>The layer on which the snapped feature is</dd>"
     824                 :            :                                 "<dt>feature_id</dt><dd>The feature id of the snapped feature</dd>"
     825                 :            :                                 "<dt>vertex_index</dt><dd>The index of the snapped vertex</dd>"
     826                 :            :                                 "<dt>distance</dt><dd>The distance between the mouse cursor and the snapped point at the time of snapping</dd>"
     827                 :            :                                 "</dl>" ) );
     828                 :            : 
     829                 :            : 
     830                 :            :   //symbol variables
     831                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "geometry_part_count" ), QCoreApplication::translate( "variable_help", "Number of parts in rendered feature's geometry." ) );
     832                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "geometry_part_num" ), QCoreApplication::translate( "variable_help", "Current geometry part number for feature being rendered." ) );
     833                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "geometry_point_count" ), QCoreApplication::translate( "variable_help", "Number of points in the rendered geometry's part. It is only meaningful for line geometries and for symbol layers that set this variable." ) );
     834                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "geometry_point_num" ), QCoreApplication::translate( "variable_help", "Current point number in the rendered geometry's part. It is only meaningful for line geometries and for symbol layers that set this variable." ) );
     835                 :            : 
     836                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "symbol_color" ), QCoreApplication::translate( "symbol_color", "Color of symbol used to render the feature." ) );
     837                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "symbol_angle" ), QCoreApplication::translate( "symbol_angle", "Angle of symbol used to render the feature (valid for marker symbols only)." ) );
     838                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "symbol_layer_count" ), QCoreApplication::translate( "symbol_layer_count", "Total number of symbol layers in the symbol." ) );
     839                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "symbol_layer_index" ), QCoreApplication::translate( "symbol_layer_index", "Current symbol layer index." ) );
     840                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "symbol_marker_row" ), QCoreApplication::translate( "symbol_marker_row", "Row number for marker (valid for point pattern fills only)." ) );
     841                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "symbol_marker_column" ), QCoreApplication::translate( "symbol_marker_column", "Column number for marker (valid for point pattern fills only)." ) );
     842                 :            : 
     843                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "symbol_label" ), QCoreApplication::translate( "symbol_label", "Label for the symbol (either a user defined label or the default autogenerated label)." ) );
     844                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "symbol_id" ), QCoreApplication::translate( "symbol_id", "Internal ID of the symbol." ) );
     845                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "symbol_count" ), QCoreApplication::translate( "symbol_count", "Total number of features represented by the symbol." ) );
     846                 :            : 
     847                 :            :   //cluster variables
     848                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "cluster_color" ), QCoreApplication::translate( "cluster_color", "Color of symbols within a cluster, or NULL if symbols have mixed colors." ) );
     849                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "cluster_size" ), QCoreApplication::translate( "cluster_size", "Number of symbols contained within a cluster." ) );
     850                 :            : 
     851                 :            :   //processing variables
     852                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "algorithm_id" ), QCoreApplication::translate( "algorithm_id", "Unique ID for algorithm." ) );
     853                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "model_path" ), QCoreApplication::translate( "variable_help", "Full path (including file name) of current model (or project path if model is embedded in a project)." ) );
     854                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "model_folder" ), QCoreApplication::translate( "variable_help", "Folder containing current model (or project folder if model is embedded in a project)." ) );
     855                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "model_name" ), QCoreApplication::translate( "variable_help", "Name of current model." ) );
     856                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "model_group" ), QCoreApplication::translate( "variable_help", "Group for current model." ) );
     857                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "fullextent_minx" ), QCoreApplication::translate( "fullextent_minx", "Minimum x-value from full canvas extent (including all layers)." ) );
     858                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "fullextent_miny" ), QCoreApplication::translate( "fullextent_miny", "Minimum y-value from full canvas extent (including all layers)." ) );
     859                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "fullextent_maxx" ), QCoreApplication::translate( "fullextent_maxx", "Maximum x-value from full canvas extent (including all layers)." ) );
     860                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "fullextent_maxy" ), QCoreApplication::translate( "fullextent_maxy", "Maximum y-value from full canvas extent (including all layers)." ) );
     861                 :            : 
     862                 :            :   //provider notification
     863                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "notification_message" ), QCoreApplication::translate( "notification_message", "Content of the notification message sent by the provider (available only for actions triggered by provider notifications)." ) );
     864                 :            : 
     865                 :            :   //form context variable
     866                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "current_geometry" ), QCoreApplication::translate( "current_geometry", "Represents the geometry of the feature currently being edited in the form or the table row. Can be used in a form/row context to filter the related features." ) );
     867                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "current_feature" ), QCoreApplication::translate( "current_feature", "Represents the feature currently being edited in the form or the table row. Can be used in a form/row context to filter the related features." ) );
     868                 :            : 
     869                 :            :   //parent form context variable
     870                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "current_parent_geometry" ), QCoreApplication::translate( "current_parent_geometry",
     871                 :            :                                 "Only usable in an embedded form context, "
     872                 :            :                                 "represents the geometry of the feature currently being edited in the parent form.\n"
     873                 :            :                                 "Can be used in a form/row context to filter the related features using a value "
     874                 :            :                                 "from the feature currently edited in the parent form, to make sure that the filter "
     875                 :            :                                 "still works with standalone forms it is recommended to wrap this variable in a "
     876                 :            :                                 "'coalesce()'." ) );
     877                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "current_parent_feature" ), QCoreApplication::translate( "current_parent_feature",
     878                 :            :                                 "Only usable in an embedded form context, "
     879                 :            :                                 "represents the feature currently being edited in the parent form.\n"
     880                 :            :                                 "Can be used in a form/row context to filter the related features using a value "
     881                 :            :                                 "from the feature currently edited in the parent form, to make sure that the filter "
     882                 :            :                                 "still works with standalone forms it is recommended to wrap this variable in a "
     883                 :            :                                 "'coalesce()'." ) );
     884                 :            : 
     885                 :            :   //form variable
     886                 :          0 :   sVariableHelpTexts()->insert( QStringLiteral( "form_mode" ), QCoreApplication::translate( "form_mode", "What the form is used for, like AddFeatureMode, SingleEditMode, MultiEditMode, SearchMode, AggregateSearchMode or IdentifyMode as string." ) );
     887                 :          0 : }
     888                 :            : 
     889                 :          0 : QString QgsExpression::variableHelpText( const QString &variableName )
     890                 :            : {
     891                 :          0 :   QgsExpression::initVariableHelp();
     892                 :          0 :   return sVariableHelpTexts()->value( variableName, QString() );
     893                 :          0 : }
     894                 :            : 
     895                 :          0 : QString QgsExpression::formatVariableHelp( const QString &description, bool showValue, const QVariant &value )
     896                 :            : {
     897                 :          0 :   QString text = !description.isEmpty() ? QStringLiteral( "<p>%1</p>" ).arg( description ) : QString();
     898                 :          0 :   if ( showValue )
     899                 :            :   {
     900                 :          0 :     QString valueString;
     901                 :          0 :     if ( !value.isValid() )
     902                 :            :     {
     903                 :          0 :       valueString = QCoreApplication::translate( "variable_help", "not set" );
     904                 :          0 :     }
     905                 :            :     else
     906                 :            :     {
     907                 :          0 :       valueString = QStringLiteral( "<b>%1</b>" ).arg( formatPreviewString( value ) );
     908                 :            :     }
     909                 :          0 :     text.append( QCoreApplication::translate( "variable_help", "<p>Current value: %1</p>" ).arg( valueString ) );
     910                 :          0 :   }
     911                 :          0 :   return text;
     912                 :          0 : }
     913                 :            : 
     914                 :          0 : QString QgsExpression::group( const QString &name )
     915                 :            : {
     916                 :          0 :   if ( sGroups()->isEmpty() )
     917                 :            :   {
     918                 :          0 :     sGroups()->insert( QStringLiteral( "Aggregates" ), tr( "Aggregates" ) );
     919                 :          0 :     sGroups()->insert( QStringLiteral( "Arrays" ), tr( "Arrays" ) );
     920                 :          0 :     sGroups()->insert( QStringLiteral( "Color" ), tr( "Color" ) );
     921                 :          0 :     sGroups()->insert( QStringLiteral( "Conditionals" ), tr( "Conditionals" ) );
     922                 :          0 :     sGroups()->insert( QStringLiteral( "Conversions" ), tr( "Conversions" ) );
     923                 :          0 :     sGroups()->insert( QStringLiteral( "Date and Time" ), tr( "Date and Time" ) );
     924                 :          0 :     sGroups()->insert( QStringLiteral( "Fields and Values" ), tr( "Fields and Values" ) );
     925                 :          0 :     sGroups()->insert( QStringLiteral( "Files and Paths" ), tr( "Files and Paths" ) );
     926                 :          0 :     sGroups()->insert( QStringLiteral( "Fuzzy Matching" ), tr( "Fuzzy Matching" ) );
     927                 :          0 :     sGroups()->insert( QStringLiteral( "General" ), tr( "General" ) );
     928                 :          0 :     sGroups()->insert( QStringLiteral( "GeometryGroup" ), tr( "Geometry" ) );
     929                 :          0 :     sGroups()->insert( QStringLiteral( "Map Layers" ), tr( "Map Layers" ) );
     930                 :          0 :     sGroups()->insert( QStringLiteral( "Maps" ), tr( "Maps" ) );
     931                 :          0 :     sGroups()->insert( QStringLiteral( "Math" ), tr( "Math" ) );
     932                 :          0 :     sGroups()->insert( QStringLiteral( "Operators" ), tr( "Operators" ) );
     933                 :          0 :     sGroups()->insert( QStringLiteral( "Rasters" ), tr( "Rasters" ) );
     934                 :          0 :     sGroups()->insert( QStringLiteral( "Record and Attributes" ), tr( "Record and Attributes" ) );
     935                 :          0 :     sGroups()->insert( QStringLiteral( "String" ), tr( "String" ) );
     936                 :          0 :     sGroups()->insert( QStringLiteral( "Variables" ), tr( "Variables" ) );
     937                 :          0 :     sGroups()->insert( QStringLiteral( "Recent (%1)" ), tr( "Recent (%1)" ) );
     938                 :          0 :     sGroups()->insert( QStringLiteral( "UserGroup" ), tr( "User expressions" ) );
     939                 :          0 :   }
     940                 :            : 
     941                 :            :   //return the translated name for this group. If group does not
     942                 :            :   //have a translated name in the gGroups hash, return the name
     943                 :            :   //unchanged
     944                 :          0 :   return sGroups()->value( name, name );
     945                 :          0 : }
     946                 :            : 
     947                 :          0 : QString QgsExpression::formatPreviewString( const QVariant &value, const bool htmlOutput, int maximumPreviewLength )
     948                 :            : {
     949                 :          0 :   const QString startToken = htmlOutput ? QStringLiteral( "<i>&lt;" ) : QStringLiteral( "<" );
     950                 :          0 :   const QString endToken = htmlOutput ? QStringLiteral( "&gt;</i>" ) : QStringLiteral( ">" );
     951                 :            : 
     952                 :          0 :   if ( value.canConvert<QgsGeometry>() )
     953                 :            :   {
     954                 :            :     //result is a geometry
     955                 :          0 :     QgsGeometry geom = value.value<QgsGeometry>();
     956                 :          0 :     if ( geom.isNull() )
     957                 :          0 :       return startToken + tr( "empty geometry" ) + endToken;
     958                 :            :     else
     959                 :          0 :       return startToken + tr( "geometry: %1" ).arg( QgsWkbTypes::displayString( geom.constGet()->wkbType() ) )
     960                 :          0 :              + endToken;
     961                 :          0 :   }
     962                 :          0 :   else if ( value.value< QgsWeakMapLayerPointer >().data() )
     963                 :            :   {
     964                 :          0 :     return startToken + tr( "map layer" ) + endToken;
     965                 :            :   }
     966                 :          0 :   else if ( !value.isValid() )
     967                 :            :   {
     968                 :          0 :     return htmlOutput ? tr( "<i>NULL</i>" ) : QString();
     969                 :            :   }
     970                 :          0 :   else if ( value.canConvert< QgsFeature >() )
     971                 :            :   {
     972                 :            :     //result is a feature
     973                 :          0 :     QgsFeature feat = value.value<QgsFeature>();
     974                 :          0 :     return startToken + tr( "feature: %1" ).arg( feat.id() ) + endToken;
     975                 :          0 :   }
     976                 :          0 :   else if ( value.canConvert< QgsInterval >() )
     977                 :            :   {
     978                 :          0 :     QgsInterval interval = value.value<QgsInterval>();
     979                 :          0 :     if ( interval.days() > 1 )
     980                 :            :     {
     981                 :          0 :       return startToken + tr( "interval: %1 days" ).arg( interval.days() ) + endToken;
     982                 :            :     }
     983                 :          0 :     else if ( interval.hours() > 1 )
     984                 :            :     {
     985                 :          0 :       return startToken + tr( "interval: %1 hours" ).arg( interval.hours() ) + endToken;
     986                 :            :     }
     987                 :          0 :     else if ( interval.minutes() > 1 )
     988                 :            :     {
     989                 :          0 :       return startToken + tr( "interval: %1 minutes" ).arg( interval.minutes() ) + endToken;
     990                 :            :     }
     991                 :            :     else
     992                 :            :     {
     993                 :          0 :       return startToken + tr( "interval: %1 seconds" ).arg( interval.seconds() ) + endToken;
     994                 :            :     }
     995                 :            :   }
     996                 :          0 :   else if ( value.canConvert< QgsGradientColorRamp >() )
     997                 :            :   {
     998                 :          0 :     return startToken + tr( "gradient ramp" ) + endToken;
     999                 :            :   }
    1000                 :          0 :   else if ( value.type() == QVariant::Date )
    1001                 :            :   {
    1002                 :          0 :     QDate dt = value.toDate();
    1003                 :          0 :     return startToken + tr( "date: %1" ).arg( dt.toString( QStringLiteral( "yyyy-MM-dd" ) ) ) + endToken;
    1004                 :            :   }
    1005                 :          0 :   else if ( value.type() == QVariant::Time )
    1006                 :            :   {
    1007                 :          0 :     QTime tm = value.toTime();
    1008                 :          0 :     return startToken + tr( "time: %1" ).arg( tm.toString( QStringLiteral( "hh:mm:ss" ) ) ) + endToken;
    1009                 :            :   }
    1010                 :          0 :   else if ( value.type() == QVariant::DateTime )
    1011                 :            :   {
    1012                 :          0 :     QDateTime dt = value.toDateTime();
    1013                 :          0 :     return startToken + tr( "datetime: %1" ).arg( dt.toString( QStringLiteral( "yyyy-MM-dd hh:mm:ss" ) ) ) + endToken;
    1014                 :          0 :   }
    1015                 :          0 :   else if ( value.type() == QVariant::String )
    1016                 :            :   {
    1017                 :          0 :     const QString previewString = value.toString();
    1018                 :          0 :     if ( previewString.length() > maximumPreviewLength + 3 )
    1019                 :            :     {
    1020                 :          0 :       return tr( "'%1…'" ).arg( previewString.left( maximumPreviewLength ) );
    1021                 :            :     }
    1022                 :            :     else
    1023                 :            :     {
    1024                 :          0 :       return '\'' + previewString + '\'';
    1025                 :            :     }
    1026                 :          0 :   }
    1027                 :          0 :   else if ( value.type() == QVariant::Map )
    1028                 :            :   {
    1029                 :          0 :     QString mapStr = QStringLiteral( "{" );
    1030                 :          0 :     const QVariantMap map = value.toMap();
    1031                 :          0 :     QString separator;
    1032                 :          0 :     for ( QVariantMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it )
    1033                 :            :     {
    1034                 :          0 :       mapStr.append( separator );
    1035                 :          0 :       if ( separator.isEmpty() )
    1036                 :          0 :         separator = QStringLiteral( "," );
    1037                 :            : 
    1038                 :          0 :       mapStr.append( QStringLiteral( " '%1': %2" ).arg( it.key(), formatPreviewString( it.value(), htmlOutput ) ) );
    1039                 :          0 :       if ( mapStr.length() > maximumPreviewLength - 3 )
    1040                 :            :       {
    1041                 :          0 :         mapStr = tr( "%1…" ).arg( mapStr.left( maximumPreviewLength - 2 ) );
    1042                 :          0 :         break;
    1043                 :            :       }
    1044                 :          0 :     }
    1045                 :          0 :     if ( !map.empty() )
    1046                 :          0 :       mapStr += QLatin1Char( ' ' );
    1047                 :          0 :     mapStr += QLatin1Char( '}' );
    1048                 :          0 :     return mapStr;
    1049                 :          0 :   }
    1050                 :          0 :   else if ( value.type() == QVariant::List || value.type() == QVariant::StringList )
    1051                 :            :   {
    1052                 :          0 :     QString listStr = QStringLiteral( "[" );
    1053                 :          0 :     const QVariantList list = value.toList();
    1054                 :          0 :     QString separator;
    1055                 :          0 :     for ( const QVariant &arrayValue : list )
    1056                 :            :     {
    1057                 :          0 :       listStr.append( separator );
    1058                 :          0 :       if ( separator.isEmpty() )
    1059                 :          0 :         separator = QStringLiteral( "," );
    1060                 :            : 
    1061                 :          0 :       listStr.append( " " );
    1062                 :          0 :       listStr.append( formatPreviewString( arrayValue, htmlOutput ) );
    1063                 :          0 :       if ( listStr.length() > maximumPreviewLength - 3 )
    1064                 :            :       {
    1065                 :          0 :         listStr = QString( tr( "%1…" ) ).arg( listStr.left( maximumPreviewLength - 2 ) );
    1066                 :          0 :         break;
    1067                 :            :       }
    1068                 :            :     }
    1069                 :          0 :     if ( !list.empty() )
    1070                 :          0 :       listStr += QLatin1Char( ' ' );
    1071                 :          0 :     listStr += QLatin1Char( ']' );
    1072                 :          0 :     return listStr;
    1073                 :          0 :   }
    1074                 :            :   else
    1075                 :            :   {
    1076                 :          0 :     return value.toString();
    1077                 :            :   }
    1078                 :          0 : }
    1079                 :            : 
    1080                 :          0 : QString QgsExpression::createFieldEqualityExpression( const QString &fieldName, const QVariant &value )
    1081                 :            : {
    1082                 :          0 :   QString expr;
    1083                 :            : 
    1084                 :          0 :   if ( value.isNull() )
    1085                 :          0 :     expr = QStringLiteral( "%1 IS NULL" ).arg( quotedColumnRef( fieldName ) );
    1086                 :            :   else
    1087                 :          0 :     expr = QStringLiteral( "%1 = %2" ).arg( quotedColumnRef( fieldName ), quotedValue( value ) );
    1088                 :            : 
    1089                 :          0 :   return expr;
    1090                 :          0 : }
    1091                 :            : 
    1092                 :          0 : bool QgsExpression::isFieldEqualityExpression( const QString &expression, QString &field, QVariant &value )
    1093                 :            : {
    1094                 :          0 :   QgsExpression e( expression );
    1095                 :            : 
    1096                 :          0 :   if ( !e.rootNode() )
    1097                 :          0 :     return false;
    1098                 :            : 
    1099                 :          0 :   if ( const QgsExpressionNodeBinaryOperator *binOp = dynamic_cast<const QgsExpressionNodeBinaryOperator *>( e.rootNode() ) )
    1100                 :            :   {
    1101                 :          0 :     if ( binOp->op() == QgsExpressionNodeBinaryOperator::boEQ )
    1102                 :            :     {
    1103                 :          0 :       const QgsExpressionNodeColumnRef *columnRef = dynamic_cast<const QgsExpressionNodeColumnRef *>( binOp->opLeft() );
    1104                 :          0 :       const QgsExpressionNodeLiteral *literal = dynamic_cast<const QgsExpressionNodeLiteral *>( binOp->opRight() );
    1105                 :          0 :       if ( columnRef && literal )
    1106                 :            :       {
    1107                 :          0 :         field = columnRef->name();
    1108                 :          0 :         value = literal->value();
    1109                 :          0 :         return true;
    1110                 :            :       }
    1111                 :          0 :     }
    1112                 :          0 :   }
    1113                 :          0 :   return false;
    1114                 :          0 : }
    1115                 :            : 
    1116                 :          0 : bool QgsExpression::attemptReduceToInClause( const QStringList &expressions, QString &result )
    1117                 :            : {
    1118                 :          0 :   if ( expressions.empty() )
    1119                 :          0 :     return false;
    1120                 :            : 
    1121                 :          0 :   QString inField;
    1122                 :          0 :   bool first = true;
    1123                 :          0 :   QStringList values;
    1124                 :          0 :   for ( const QString &expression : expressions )
    1125                 :            :   {
    1126                 :          0 :     QString field;
    1127                 :          0 :     QVariant value;
    1128                 :          0 :     if ( QgsExpression::isFieldEqualityExpression( expression, field, value ) )
    1129                 :            :     {
    1130                 :          0 :       if ( first )
    1131                 :            :       {
    1132                 :          0 :         inField = field;
    1133                 :          0 :         first = false;
    1134                 :          0 :       }
    1135                 :          0 :       else if ( field != inField )
    1136                 :            :       {
    1137                 :          0 :         return false;
    1138                 :            :       }
    1139                 :          0 :       values << QgsExpression::quotedValue( value );
    1140                 :          0 :     }
    1141                 :            :     else
    1142                 :            :     {
    1143                 :            :       // we also allow reducing similar 'field IN (...)' expressions!
    1144                 :          0 :       QgsExpression e( expression );
    1145                 :            : 
    1146                 :          0 :       if ( !e.rootNode() )
    1147                 :          0 :         return false;
    1148                 :            : 
    1149                 :          0 :       if ( const QgsExpressionNodeInOperator *inOp = dynamic_cast<const QgsExpressionNodeInOperator *>( e.rootNode() ) )
    1150                 :            :       {
    1151                 :          0 :         if ( inOp->isNotIn() )
    1152                 :          0 :           return false;
    1153                 :            : 
    1154                 :          0 :         const QgsExpressionNodeColumnRef *columnRef = dynamic_cast<const QgsExpressionNodeColumnRef *>( inOp->node() );
    1155                 :          0 :         if ( !columnRef )
    1156                 :          0 :           return false;
    1157                 :            : 
    1158                 :          0 :         if ( first )
    1159                 :            :         {
    1160                 :          0 :           inField = columnRef->name();
    1161                 :          0 :           first = false;
    1162                 :          0 :         }
    1163                 :          0 :         else if ( columnRef->name() != inField )
    1164                 :            :         {
    1165                 :          0 :           return false;
    1166                 :            :         }
    1167                 :            : 
    1168                 :          0 :         if ( QgsExpressionNode::NodeList *nodeList = inOp->list() )
    1169                 :            :         {
    1170                 :          0 :           const QList<QgsExpressionNode *> nodes = nodeList->list();
    1171                 :          0 :           for ( const QgsExpressionNode *node : nodes )
    1172                 :            :           {
    1173                 :          0 :             const QgsExpressionNodeLiteral *literal = dynamic_cast<const QgsExpressionNodeLiteral *>( node );
    1174                 :          0 :             if ( !literal )
    1175                 :          0 :               return false;
    1176                 :            : 
    1177                 :          0 :             values << QgsExpression::quotedValue( literal->value() );
    1178                 :            :           }
    1179                 :          0 :         }
    1180                 :          0 :       }
    1181                 :            :       else
    1182                 :            :       {
    1183                 :          0 :         return false;
    1184                 :            :       }
    1185                 :          0 :     }
    1186                 :          0 :   }
    1187                 :          0 :   result = QStringLiteral( "%1 IN (%2)" ).arg( inField, values.join( ',' ) );
    1188                 :          0 :   return true;
    1189                 :          0 : }
    1190                 :            : 
    1191                 :          0 : const QgsExpressionNode *QgsExpression::rootNode() const
    1192                 :            : {
    1193                 :          0 :   return d->mRootNode;
    1194                 :            : }
    1195                 :            : 
    1196                 :          0 : bool QgsExpression::isField() const
    1197                 :            : {
    1198                 :          0 :   return d->mRootNode && d->mRootNode->nodeType() == QgsExpressionNode::ntColumnRef;
    1199                 :            : }
    1200                 :            : 
    1201                 :          0 : QList<const QgsExpressionNode *> QgsExpression::nodes() const
    1202                 :            : {
    1203                 :          0 :   if ( !d->mRootNode )
    1204                 :          0 :     return QList<const QgsExpressionNode *>();
    1205                 :            : 
    1206                 :          0 :   return d->mRootNode->nodes();
    1207                 :          0 : }
    1208                 :            : 
    1209                 :            : 
    1210                 :            : 

Generated by: LCOV version 1.14