QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 "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 Q_GLOBAL_STATIC( HelpTextHash, sFunctionHelpTexts )
34 Q_GLOBAL_STATIC( QgsStringMap, sVariableHelpTexts )
35 Q_GLOBAL_STATIC( QgsStringMap, sGroups )
36 
37 HelpTextHash &functionHelpTexts()
38 {
39  return *sFunctionHelpTexts();
40 }
41 
42 bool QgsExpression::checkExpression( const QString &text, const QgsExpressionContext *context, QString &errorMessage )
43 {
44  QgsExpression exp( text );
45  exp.prepare( context );
46  errorMessage = exp.parserErrorString();
47  return !exp.hasParserError();
48 }
49 
50 void QgsExpression::setExpression( const QString &expression )
51 {
52  detach();
53  d->mRootNode = ::parseExpression( expression, d->mParserErrorString, d->mParserErrors );
54  d->mEvalErrorString = QString();
55  d->mExp = expression;
56  d->mIsPrepared = false;
57 }
58 
60 {
61  if ( !d->mExp.isNull() )
62  return d->mExp;
63  else
64  return dump();
65 }
66 
67 QString QgsExpression::quotedColumnRef( QString name )
68 {
69  return QStringLiteral( "\"%1\"" ).arg( name.replace( '\"', QLatin1String( "\"\"" ) ) );
70 }
71 
72 QString QgsExpression::quotedString( QString text )
73 {
74  text.replace( '\'', QLatin1String( "''" ) );
75  text.replace( '\\', QLatin1String( "\\\\" ) );
76  text.replace( '\n', QLatin1String( "\\n" ) );
77  text.replace( '\t', QLatin1String( "\\t" ) );
78  return QStringLiteral( "'%1'" ).arg( text );
79 }
80 
81 QString QgsExpression::quotedValue( const QVariant &value )
82 {
83  return quotedValue( value, value.type() );
84 }
85 
86 QString QgsExpression::quotedValue( const QVariant &value, QVariant::Type type )
87 {
88  if ( value.isNull() )
89  return QStringLiteral( "NULL" );
90 
91  switch ( type )
92  {
93  case QVariant::Int:
94  case QVariant::LongLong:
95  case QVariant::Double:
96  return value.toString();
97 
98  case QVariant::Bool:
99  return value.toBool() ? QStringLiteral( "TRUE" ) : QStringLiteral( "FALSE" );
100 
101  case QVariant::List:
102  {
103  QStringList quotedValues;
104  const QVariantList values = value.toList();
105  quotedValues.reserve( values.count() );
106  for ( const QVariant &v : values )
107  {
108  quotedValues += quotedValue( v );
109  }
110  return QStringLiteral( "array( %1 )" ).arg( quotedValues.join( QLatin1String( ", " ) ) );
111  }
112 
113  default:
114  case QVariant::String:
115  return quotedString( value.toString() );
116  }
117 
118 }
119 
120 bool QgsExpression::isFunctionName( const QString &name )
121 {
122  return functionIndex( name ) != -1;
123 }
124 
125 int QgsExpression::functionIndex( const QString &name )
126 {
127  int count = functionCount();
128  for ( int i = 0; i < count; i++ )
129  {
130  if ( QString::compare( name, QgsExpression::Functions()[i]->name(), Qt::CaseInsensitive ) == 0 )
131  return i;
132  const QStringList aliases = QgsExpression::Functions()[i]->aliases();
133  for ( const QString &alias : aliases )
134  {
135  if ( QString::compare( name, alias, Qt::CaseInsensitive ) == 0 )
136  return i;
137  }
138  }
139  return -1;
140 }
141 
143 {
144  return Functions().size();
145 }
146 
147 
148 QgsExpression::QgsExpression( const QString &expr )
149  : d( new QgsExpressionPrivate )
150 {
151  d->mRootNode = ::parseExpression( expr, d->mParserErrorString, d->mParserErrors );
152  d->mExp = expr;
153  Q_ASSERT( !d->mParserErrorString.isNull() || d->mRootNode );
154 }
155 
157  : d( other.d )
158 {
159  d->ref.ref();
160 }
161 
163 {
164  if ( this != &other )
165  {
166  if ( !d->ref.deref() )
167  {
168  delete d;
169  }
170 
171  d = other.d;
172  d->ref.ref();
173  }
174  return *this;
175 }
176 
177 QgsExpression::operator QString() const
178 {
179  return d->mExp;
180 }
181 
183  : d( new QgsExpressionPrivate )
184 {
185 }
186 
188 {
189  Q_ASSERT( d );
190  if ( !d->ref.deref() )
191  delete d;
192 }
193 
194 bool QgsExpression::operator==( const QgsExpression &other ) const
195 {
196  return ( d == other.d || d->mExp == other.d->mExp );
197 }
198 
200 {
201  return d->mRootNode;
202 }
203 
205 {
206  return d->mParserErrors.count() > 0;
207 }
208 
210 {
211  return d->mParserErrorString;
212 }
213 
214 QList<QgsExpression::ParserError> QgsExpression::parserErrors() const
215 {
216  return d->mParserErrors;
217 }
218 
219 QSet<QString> QgsExpression::referencedColumns() const
220 {
221  if ( !d->mRootNode )
222  return QSet<QString>();
223 
224  return d->mRootNode->referencedColumns();
225 }
226 
228 {
229  if ( !d->mRootNode )
230  return QSet<QString>();
231 
232  return d->mRootNode->referencedVariables();
233 }
234 
236 {
237  if ( !d->mRootNode )
238  return QSet<QString>();
239 
240  return d->mRootNode->referencedFunctions();
241 }
242 
243 QSet<int> QgsExpression::referencedAttributeIndexes( const QgsFields &fields ) const
244 {
245  if ( !d->mRootNode )
246  return QSet<int>();
247 
248  const QSet<QString> referencedFields = d->mRootNode->referencedColumns();
249  QSet<int> referencedIndexes;
250 
251  for ( const QString &fieldName : referencedFields )
252  {
253  if ( fieldName == QgsFeatureRequest::ALL_ATTRIBUTES )
254  {
255  referencedIndexes = qgis::listToSet( fields.allAttributesList() );
256  break;
257  }
258  const int idx = fields.lookupField( fieldName );
259  if ( idx >= 0 )
260  {
261  referencedIndexes << idx;
262  }
263  }
264 
265  return referencedIndexes;
266 }
267 
269 {
270  if ( !d->mRootNode )
271  return false;
272  return d->mRootNode->needsGeometry();
273 }
274 
275 void QgsExpression::initGeomCalculator( const QgsExpressionContext *context )
276 {
277  // Set the geometry calculator from the context if it has not been set by setGeomCalculator()
278  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  d->mDaEllipsoid = context->variable( QStringLiteral( "project_ellipsoid" ) ).toString();
283  d->mDaCrs = std::make_unique<QgsCoordinateReferenceSystem>( context->variable( QStringLiteral( "_layer_crs" ) ).value<QgsCoordinateReferenceSystem>() );
284  d->mDaTransformContext = std::make_unique<QgsCoordinateTransformContext>( context->variable( QStringLiteral( "_project_transform_context" ) ).value<QgsCoordinateTransformContext>() );
285  }
286 
287  // Set the distance units from the context if it has not been set by setDistanceUnits()
288  if ( context && distanceUnits() == QgsUnitTypes::DistanceUnknownUnit )
289  {
290  QString distanceUnitsStr = context->variable( QStringLiteral( "project_distance_units" ) ).toString();
291  if ( ! distanceUnitsStr.isEmpty() )
293  }
294 
295  // Set the area units from the context if it has not been set by setAreaUnits()
296  if ( context && areaUnits() == QgsUnitTypes::AreaUnknownUnit )
297  {
298  QString areaUnitsStr = context->variable( QStringLiteral( "project_area_units" ) ).toString();
299  if ( ! areaUnitsStr.isEmpty() )
300  setAreaUnits( QgsUnitTypes::stringToAreaUnit( areaUnitsStr ) );
301  }
302 }
303 
304 void QgsExpression::detach()
305 {
306  Q_ASSERT( d );
307 
308  if ( d->ref > 1 )
309  {
310  ( void )d->ref.deref();
311 
312  d = new QgsExpressionPrivate( *d );
313  }
314 }
315 
317 {
318  detach();
319  if ( calc )
320  d->mCalc = std::shared_ptr<QgsDistanceArea>( new QgsDistanceArea( *calc ) );
321  else
322  d->mCalc.reset();
323 }
324 
326 {
327  detach();
328  d->mEvalErrorString = QString();
329  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  d->mRootNode = ::parseExpression( d->mExp, d->mParserErrorString, d->mParserErrors );
335  }
336 
337  if ( !d->mRootNode )
338  {
339  d->mEvalErrorString = tr( "No root node! Parsing failed?" );
340  return false;
341  }
342 
343  initGeomCalculator( context );
344  d->mIsPrepared = true;
345  return d->mRootNode->prepare( this, context );
346 }
347 
349 {
350  d->mEvalErrorString = QString();
351  if ( !d->mRootNode )
352  {
353  d->mEvalErrorString = tr( "No root node! Parsing failed?" );
354  return QVariant();
355  }
356 
357  return d->mRootNode->eval( this, static_cast<const QgsExpressionContext *>( nullptr ) );
358 }
359 
361 {
362  d->mEvalErrorString = QString();
363  if ( !d->mRootNode )
364  {
365  d->mEvalErrorString = tr( "No root node! Parsing failed?" );
366  return QVariant();
367  }
368 
369  if ( ! d->mIsPrepared )
370  {
371  prepare( context );
372  }
373  return d->mRootNode->eval( this, context );
374 }
375 
377 {
378  return !d->mEvalErrorString.isNull();
379 }
380 
382 {
383  return d->mEvalErrorString;
384 }
385 
387 {
388  d->mEvalErrorString = str;
389 }
390 
391 QString QgsExpression::dump() const
392 {
393  if ( !d->mRootNode )
394  return QString();
395 
396  return d->mRootNode->dump();
397 }
398 
400 {
401  if ( !d->mCalc && d->mDaCrs && d->mDaCrs->isValid() && d->mDaTransformContext )
402  {
403  // calculator IS required, so initialize it now...
404  d->mCalc = std::shared_ptr<QgsDistanceArea>( new QgsDistanceArea() );
405  d->mCalc->setEllipsoid( d->mDaEllipsoid.isEmpty() ? geoNone() : d->mDaEllipsoid );
406  d->mCalc->setSourceCrs( *d->mDaCrs.get(), *d->mDaTransformContext.get() );
407  }
408 
409  return d->mCalc.get();
410 }
411 
413 {
414  return d->mDistanceUnit;
415 }
416 
418 {
419  d->mDistanceUnit = unit;
420 }
421 
423 {
424  return d->mAreaUnit;
425 }
426 
428 {
429  d->mAreaUnit = unit;
430 }
431 
432 QString QgsExpression::replaceExpressionText( const QString &action, const QgsExpressionContext *context, const QgsDistanceArea *distanceArea )
433 {
434  QString expr_action;
435 
436  int index = 0;
437  while ( index < action.size() )
438  {
439  static const QRegularExpression sRegEx{ QStringLiteral( "\\[%(.*?)%\\]" ), QRegularExpression::MultilineOption | QRegularExpression::DotMatchesEverythingOption };
440 
441  const QRegularExpressionMatch match = sRegEx.match( action, index );
442  if ( !match.hasMatch() )
443  break;
444 
445  const int pos = action.indexOf( sRegEx, index );
446  const int start = index;
447  index = pos + match.capturedLength( 0 );
448  const QString toReplace = match.captured( 1 ).trimmed();
449  QgsDebugMsgLevel( "Found expression: " + toReplace, 3 );
450 
451  QgsExpression exp( toReplace );
452  if ( exp.hasParserError() )
453  {
454  QgsDebugMsg( "Expression parser error: " + exp.parserErrorString() );
455 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 2)
456  expr_action += action.midRef( start, index - start );
457 #else
458  expr_action += QStringView {action}.mid( start, index - start );
459 #endif
460  continue;
461  }
462 
463  if ( distanceArea )
464  {
465  //if QgsDistanceArea specified for area/distance conversion, use it
466  exp.setGeomCalculator( distanceArea );
467  }
468 
469  QVariant result = exp.evaluate( context );
470 
471  if ( exp.hasEvalError() )
472  {
473  QgsDebugMsg( "Expression parser eval error: " + exp.evalErrorString() );
474 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 2)
475  expr_action += action.midRef( start, index - start );
476 #else
477  expr_action += QStringView {action}.mid( start, index - start );
478 #endif
479  continue;
480  }
481 
482  QgsDebugMsgLevel( "Expression result is: " + result.toString(), 3 );
483  expr_action += action.mid( start, pos - start ) + result.toString();
484  }
485 
486 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 2)
487  expr_action += action.midRef( index );
488 #else
489  expr_action += QStringView {action}.mid( index ).toString();
490 #endif
491 
492  return expr_action;
493 }
494 
495 QSet<QString> QgsExpression::referencedVariables( const QString &text )
496 {
497  QSet<QString> variables;
498  int index = 0;
499  while ( index < text.size() )
500  {
501  QRegExp rx = QRegExp( "\\[%([^\\]]+)%\\]" );
502 
503  int pos = rx.indexIn( text, index );
504  if ( pos < 0 )
505  break;
506 
507  index = pos + rx.matchedLength();
508  QString to_replace = rx.cap( 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." ) );
753  sVariableHelpTexts()->insert( QStringLiteral( "layout_pagewidth" ), QCoreApplication::translate( "variable_help", "Composition page width in mm." ) );
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  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  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  QDateTime dt = value.toDateTime();
1027  return startToken + tr( "datetime: %1" ).arg( dt.toString( QStringLiteral( "yyyy-MM-dd hh:mm:ss" ) ) ) + 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 || value.type() == QVariant::UInt || value.type() == QVariant::LongLong || value.type() == QVariant::ULongLong )
1089  {
1090  bool ok;
1091  QString res;
1092 
1093  if ( value.type() == QVariant::ULongLong )
1094  {
1095  res = QLocale().toString( value.toULongLong( &ok ) );
1096  }
1097  else
1098  {
1099  res = QLocale().toString( value.toLongLong( &ok ) );
1100  }
1101 
1102  if ( ok )
1103  {
1104  return res;
1105  }
1106  else
1107  {
1108  return value.toString();
1109  }
1110  }
1111  // Qt madness with QMetaType::Float :/
1112  else if ( value.type() == QVariant::Double || value.type() == static_cast<QVariant::Type>( QMetaType::Float ) )
1113  {
1114  bool ok;
1115  const QString res { QLocale().toString( value.toDouble( &ok ), 'f', 6 ) };
1116 
1117  if ( ok )
1118  {
1119  return res;
1120  }
1121  else
1122  {
1123  return value.toString();
1124  }
1125  }
1126  else
1127  {
1128  return value.toString();
1129  }
1130 }
1131 
1132 QString QgsExpression::createFieldEqualityExpression( const QString &fieldName, const QVariant &value )
1133 {
1134  QString expr;
1135 
1136  if ( value.isNull() )
1137  expr = QStringLiteral( "%1 IS NULL" ).arg( quotedColumnRef( fieldName ) );
1138  else
1139  expr = QStringLiteral( "%1 = %2" ).arg( quotedColumnRef( fieldName ), quotedValue( value ) );
1140 
1141  return expr;
1142 }
1143 
1144 bool QgsExpression::isFieldEqualityExpression( const QString &expression, QString &field, QVariant &value )
1145 {
1147 
1148  if ( !e.rootNode() )
1149  return false;
1150 
1151  if ( const QgsExpressionNodeBinaryOperator *binOp = dynamic_cast<const QgsExpressionNodeBinaryOperator *>( e.rootNode() ) )
1152  {
1153  if ( binOp->op() == QgsExpressionNodeBinaryOperator::boEQ )
1154  {
1155  const QgsExpressionNodeColumnRef *columnRef = dynamic_cast<const QgsExpressionNodeColumnRef *>( binOp->opLeft() );
1156  const QgsExpressionNodeLiteral *literal = dynamic_cast<const QgsExpressionNodeLiteral *>( binOp->opRight() );
1157  if ( columnRef && literal )
1158  {
1159  field = columnRef->name();
1160  value = literal->value();
1161  return true;
1162  }
1163  }
1164  }
1165  return false;
1166 }
1167 
1168 bool QgsExpression::attemptReduceToInClause( const QStringList &expressions, QString &result )
1169 {
1170  if ( expressions.empty() )
1171  return false;
1172 
1173  QString inField;
1174  bool first = true;
1175  QStringList values;
1176  for ( const QString &expression : expressions )
1177  {
1178  QString field;
1179  QVariant value;
1181  {
1182  if ( first )
1183  {
1184  inField = field;
1185  first = false;
1186  }
1187  else if ( field != inField )
1188  {
1189  return false;
1190  }
1191  values << QgsExpression::quotedValue( value );
1192  }
1193  else
1194  {
1195  // we also allow reducing similar 'field IN (...)' expressions!
1197 
1198  if ( !e.rootNode() )
1199  return false;
1200 
1201  if ( const QgsExpressionNodeInOperator *inOp = dynamic_cast<const QgsExpressionNodeInOperator *>( e.rootNode() ) )
1202  {
1203  if ( inOp->isNotIn() )
1204  return false;
1205 
1206  const QgsExpressionNodeColumnRef *columnRef = dynamic_cast<const QgsExpressionNodeColumnRef *>( inOp->node() );
1207  if ( !columnRef )
1208  return false;
1209 
1210  if ( first )
1211  {
1212  inField = columnRef->name();
1213  first = false;
1214  }
1215  else if ( columnRef->name() != inField )
1216  {
1217  return false;
1218  }
1219 
1220  if ( QgsExpressionNode::NodeList *nodeList = inOp->list() )
1221  {
1222  const QList<QgsExpressionNode *> nodes = nodeList->list();
1223  for ( const QgsExpressionNode *node : nodes )
1224  {
1225  const QgsExpressionNodeLiteral *literal = dynamic_cast<const QgsExpressionNodeLiteral *>( node );
1226  if ( !literal )
1227  return false;
1228 
1229  values << QgsExpression::quotedValue( literal->value() );
1230  }
1231  }
1232  }
1233  // Collect ORs
1234  else if ( const QgsExpressionNodeBinaryOperator *orOp = dynamic_cast<const QgsExpressionNodeBinaryOperator *>( e.rootNode() ) )
1235  {
1236 
1237  // OR Collector function: returns a possibly empty list of the left and right operands of an OR expression
1238  std::function<QStringList( QgsExpressionNode *, QgsExpressionNode * )> collectOrs = [ &collectOrs ]( QgsExpressionNode * opLeft, QgsExpressionNode * opRight ) -> QStringList
1239  {
1240  QStringList orParts;
1241  if ( const QgsExpressionNodeBinaryOperator *leftOrOp = dynamic_cast<const QgsExpressionNodeBinaryOperator *>( opLeft ) )
1242  {
1243  if ( leftOrOp->op( ) == QgsExpressionNodeBinaryOperator::BinaryOperator::boOr )
1244  {
1245  orParts.append( collectOrs( leftOrOp->opLeft(), leftOrOp->opRight() ) );
1246  }
1247  else
1248  {
1249  orParts.append( leftOrOp->dump() );
1250  }
1251  }
1252  else if ( const QgsExpressionNodeInOperator *leftInOp = dynamic_cast<const QgsExpressionNodeInOperator *>( opLeft ) )
1253  {
1254  orParts.append( leftInOp->dump() );
1255  }
1256  else
1257  {
1258  return {};
1259  }
1260 
1261  if ( const QgsExpressionNodeBinaryOperator *rightOrOp = dynamic_cast<const QgsExpressionNodeBinaryOperator *>( opRight ) )
1262  {
1263  if ( rightOrOp->op( ) == QgsExpressionNodeBinaryOperator::BinaryOperator::boOr )
1264  {
1265  orParts.append( collectOrs( rightOrOp->opLeft(), rightOrOp->opRight() ) );
1266  }
1267  else
1268  {
1269  orParts.append( rightOrOp->dump() );
1270  }
1271  }
1272  else if ( const QgsExpressionNodeInOperator *rightInOp = dynamic_cast<const QgsExpressionNodeInOperator *>( opRight ) )
1273  {
1274  orParts.append( rightInOp->dump() );
1275  }
1276  else
1277  {
1278  return {};
1279  }
1280 
1281  return orParts;
1282  };
1283 
1284  if ( orOp->op( ) == QgsExpressionNodeBinaryOperator::BinaryOperator::boOr )
1285  {
1286  // Try to collect all OR conditions
1287  const QStringList orParts = collectOrs( orOp->opLeft(), orOp->opRight() );
1288  if ( orParts.isEmpty() )
1289  {
1290  return false;
1291  }
1292  else
1293  {
1294  QString orPartsResult;
1295  if ( attemptReduceToInClause( orParts, orPartsResult ) )
1296  {
1297  // Need to check if the IN field is correct,
1298  QgsExpression inExp { orPartsResult };
1299  if ( ! inExp.rootNode() )
1300  {
1301  return false;
1302  }
1303 
1304  if ( const QgsExpressionNodeInOperator *inOpInner = dynamic_cast<const QgsExpressionNodeInOperator *>( inExp.rootNode() ) )
1305  {
1306  if ( inOpInner->node()->nodeType() != QgsExpressionNode::NodeType::ntColumnRef || inOpInner->node()->referencedColumns().size() < 1 )
1307  {
1308  return false;
1309  }
1310 
1311  const QString innerInfield { inOpInner->node()->referencedColumns().values().first() };
1312 
1313  if ( first )
1314  {
1315  inField = innerInfield;
1316  first = false;
1317  }
1318 
1319  if ( innerInfield != inField )
1320  {
1321  return false;
1322  }
1323  else
1324  {
1325  const auto constInnerValuesList { inOpInner->list()->list() };
1326  for ( const auto &innerInValueNode : std::as_const( constInnerValuesList ) )
1327  {
1328  values.append( innerInValueNode->dump() );
1329  }
1330  }
1331 
1332  }
1333  else
1334  {
1335  return false;
1336  }
1337  }
1338  else
1339  {
1340  return false;
1341  }
1342  }
1343  }
1344  else
1345  {
1346  return false;
1347  }
1348  }
1349  else
1350  {
1351  return false;
1352  }
1353  }
1354  }
1355  result = QStringLiteral( "%1 IN (%2)" ).arg( inField, values.join( ',' ) );
1356  return true;
1357 }
1358 
1360 {
1361  return d->mRootNode;
1362 }
1363 
1365 {
1366  return d->mRootNode && d->mRootNode->nodeType() == QgsExpressionNode::ntColumnRef;
1367 }
1368 
1369 QList<const QgsExpressionNode *> QgsExpression::nodes() const
1370 {
1371  if ( !d->mRootNode )
1372  return QList<const QgsExpressionNode *>();
1373 
1374  return d->mRootNode->nodes();
1375 }
1376 
1377 
1378 
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.
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
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:998
QMap< QString, QString > QgsStringMap
Definition: qgis.h:1041
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:1853