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