QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 = qgis::make_unique<QgsCoordinateReferenceSystem>( context->variable( QStringLiteral( "_layer_crs" ) ).value<QgsCoordinateReferenceSystem>() );
284  d->mDaTransformContext = qgis::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 
386 void QgsExpression::setEvalErrorString( const QString &str )
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  expr_action += action.midRef( start, index - start );
456  continue;
457  }
458 
459  if ( distanceArea )
460  {
461  //if QgsDistanceArea specified for area/distance conversion, use it
462  exp.setGeomCalculator( distanceArea );
463  }
464 
465  QVariant result = exp.evaluate( context );
466 
467  if ( exp.hasEvalError() )
468  {
469  QgsDebugMsg( "Expression parser eval error: " + exp.evalErrorString() );
470  expr_action += action.midRef( start, index - start );
471  continue;
472  }
473 
474  QgsDebugMsgLevel( "Expression result is: " + result.toString(), 3 );
475  expr_action += action.mid( start, pos - start ) + result.toString();
476  }
477 
478  expr_action += action.midRef( index );
479 
480  return expr_action;
481 }
482 
483 QSet<QString> QgsExpression::referencedVariables( const QString &text )
484 {
485  QSet<QString> variables;
486  int index = 0;
487  while ( index < text.size() )
488  {
489  QRegExp rx = QRegExp( "\\[%([^\\]]+)%\\]" );
490 
491  int pos = rx.indexIn( text, index );
492  if ( pos < 0 )
493  break;
494 
495  index = pos + rx.matchedLength();
496  QString to_replace = rx.cap( 1 ).trimmed();
497 
498  QgsExpression exp( to_replace );
499  variables.unite( exp.referencedVariables() );
500  }
501 
502  return variables;
503 }
504 
505 double QgsExpression::evaluateToDouble( const QString &text, const double fallbackValue )
506 {
507  bool ok;
508  //first test if text is directly convertible to double
509  // use system locale: e.g. in German locale, user is presented with numbers "1,23" instead of "1.23" in C locale
510  // so we also want to allow user to rewrite it to "5,23" and it is still accepted
511  double convertedValue = QLocale().toDouble( text, &ok );
512  if ( ok )
513  {
514  return convertedValue;
515  }
516 
517  //otherwise try to evaluate as expression
518  QgsExpression expr( text );
519 
520  QgsExpressionContext context;
523 
524  QVariant result = expr.evaluate( &context );
525  convertedValue = result.toDouble( &ok );
526  if ( expr.hasEvalError() || !ok )
527  {
528  return fallbackValue;
529  }
530  return convertedValue;
531 }
532 
533 QString QgsExpression::helpText( QString name )
534 {
535  QgsExpression::initFunctionHelp();
536 
537  if ( !sFunctionHelpTexts()->contains( name ) )
538  return tr( "function help for %1 missing" ).arg( name );
539 
540  const Help &f = ( *sFunctionHelpTexts() )[ name ];
541 
542  name = f.mName;
543  if ( f.mType == tr( "group" ) )
544  {
545  name = group( name );
546  name = name.toLower();
547  }
548 
549  name = name.toHtmlEscaped();
550 
551  QString helpContents( QStringLiteral( "<h3>%1</h3>\n<div class=\"description\"><p>%2</p></div>" )
552  .arg( tr( "%1 %2" ).arg( f.mType, name ),
553  f.mDescription ) );
554 
555  for ( const HelpVariant &v : qgis::as_const( f.mVariants ) )
556  {
557  if ( f.mVariants.size() > 1 )
558  {
559  helpContents += QStringLiteral( "<h3>%1</h3>\n<div class=\"description\">%2</p></div>" ).arg( v.mName, v.mDescription );
560  }
561 
562  if ( f.mType != tr( "group" ) && f.mType != tr( "expression" ) )
563  helpContents += QStringLiteral( "<h4>%1</h4>\n<div class=\"syntax\">\n" ).arg( tr( "Syntax" ) );
564 
565  if ( f.mType == tr( "operator" ) )
566  {
567  if ( v.mArguments.size() == 1 )
568  {
569  helpContents += QStringLiteral( "<code><span class=\"functionname\">%1</span> <span class=\"argument\">%2</span></code>" )
570  .arg( name, v.mArguments[0].mArg );
571  }
572  else if ( v.mArguments.size() == 2 )
573  {
574  helpContents += QStringLiteral( "<code><span class=\"argument\">%1</span> <span class=\"functionname\">%2</span> <span class=\"argument\">%3</span></code>" )
575  .arg( v.mArguments[0].mArg, name, v.mArguments[1].mArg );
576  }
577  }
578  else if ( f.mType != tr( "group" ) && f.mType != tr( "expression" ) )
579  {
580  helpContents += QStringLiteral( "<code><span class=\"functionname\">%1</span>" ).arg( name );
581 
582  bool hasOptionalArgs = false;
583 
584  if ( f.mType == tr( "function" ) && ( f.mName[0] != '$' || !v.mArguments.isEmpty() || v.mVariableLenArguments ) )
585  {
586  helpContents += '(';
587 
588  QString delim;
589  for ( const HelpArg &a : qgis::as_const( v.mArguments ) )
590  {
591  if ( !a.mDescOnly )
592  {
593  if ( a.mOptional )
594  {
595  hasOptionalArgs = true;
596  helpContents += QLatin1Char( '[' );
597  }
598 
599  helpContents += delim;
600  helpContents += QStringLiteral( "<span class=\"argument\">%2%3</span>" ).arg(
601  a.mArg,
602  a.mDefaultVal.isEmpty() ? QString() : '=' + a.mDefaultVal
603  );
604 
605  if ( a.mOptional )
606  helpContents += QLatin1Char( ']' );
607  }
608  delim = QStringLiteral( "," );
609  }
610 
611  if ( v.mVariableLenArguments )
612  {
613  helpContents += QChar( 0x2026 );
614  }
615 
616  helpContents += ')';
617  }
618 
619  helpContents += QLatin1String( "</code>" );
620 
621  if ( hasOptionalArgs )
622  {
623  helpContents += QLatin1String( "<br/><br/>" ) + tr( "[ ] marks optional components" );
624  }
625  }
626 
627  if ( !v.mArguments.isEmpty() )
628  {
629  helpContents += QStringLiteral( "<h4>%1</h4>\n<div class=\"arguments\">\n<table>" ).arg( tr( "Arguments" ) );
630 
631  for ( const HelpArg &a : qgis::as_const( v.mArguments ) )
632  {
633  if ( a.mSyntaxOnly )
634  continue;
635 
636  helpContents += QStringLiteral( "<tr><td class=\"argument\">%1</td><td>%2</td></tr>" ).arg( a.mArg, a.mDescription );
637  }
638 
639  helpContents += QLatin1String( "</table>\n</div>\n" );
640  }
641 
642  if ( !v.mExamples.isEmpty() )
643  {
644  helpContents += QStringLiteral( "<h4>%1</h4>\n<div class=\"examples\">\n<ul>\n" ).arg( tr( "Examples" ) );
645 
646  for ( const HelpExample &e : qgis::as_const( v.mExamples ) )
647  {
648  helpContents += "<li><code>" + e.mExpression + "</code> &rarr; <code>" + e.mReturns + "</code>";
649 
650  if ( !e.mNote.isEmpty() )
651  helpContents += QStringLiteral( " (%1)" ).arg( e.mNote );
652 
653  helpContents += QLatin1String( "</li>\n" );
654  }
655 
656  helpContents += QLatin1String( "</ul>\n</div>\n" );
657  }
658 
659  if ( !v.mNotes.isEmpty() )
660  {
661  helpContents += QStringLiteral( "<h4>%1</h4>\n<div class=\"notes\"><p>%2</p></div>\n" ).arg( tr( "Notes" ), v.mNotes );
662  }
663  }
664 
665  return helpContents;
666 }
667 
668 QStringList QgsExpression::tags( const QString &name )
669 {
670  QStringList tags = QStringList();
671 
672  QgsExpression::initFunctionHelp();
673 
674  if ( sFunctionHelpTexts()->contains( name ) )
675  {
676  const Help &f = ( *sFunctionHelpTexts() )[ name ];
677 
678  for ( const HelpVariant &v : qgis::as_const( f.mVariants ) )
679  {
680  tags << v.mTags;
681  }
682  }
683 
684  return tags;
685 }
686 
687 void QgsExpression::initVariableHelp()
688 {
689  if ( !sVariableHelpTexts()->isEmpty() )
690  return;
691 
692  //global variables
693  sVariableHelpTexts()->insert( QStringLiteral( "qgis_version" ), QCoreApplication::translate( "variable_help", "Current QGIS version string." ) );
694  sVariableHelpTexts()->insert( QStringLiteral( "qgis_version_no" ), QCoreApplication::translate( "variable_help", "Current QGIS version number." ) );
695  sVariableHelpTexts()->insert( QStringLiteral( "qgis_release_name" ), QCoreApplication::translate( "variable_help", "Current QGIS release name." ) );
696  sVariableHelpTexts()->insert( QStringLiteral( "qgis_short_version" ), QCoreApplication::translate( "variable_help", "Short QGIS version string." ) );
697  sVariableHelpTexts()->insert( QStringLiteral( "qgis_os_name" ), QCoreApplication::translate( "variable_help", "Operating system name, e.g., 'windows', 'linux' or 'osx'." ) );
698  sVariableHelpTexts()->insert( QStringLiteral( "qgis_platform" ), QCoreApplication::translate( "variable_help", "QGIS platform, e.g., 'desktop' or 'server'." ) );
699  sVariableHelpTexts()->insert( QStringLiteral( "qgis_locale" ), QCoreApplication::translate( "variable_help", "Two letter identifier for current QGIS locale." ) );
700  sVariableHelpTexts()->insert( QStringLiteral( "user_account_name" ), QCoreApplication::translate( "variable_help", "Current user's operating system account name." ) );
701  sVariableHelpTexts()->insert( QStringLiteral( "user_full_name" ), QCoreApplication::translate( "variable_help", "Current user's operating system user name (if available)." ) );
702 
703  //project variables
704  sVariableHelpTexts()->insert( QStringLiteral( "project_title" ), QCoreApplication::translate( "variable_help", "Title of current project." ) );
705  sVariableHelpTexts()->insert( QStringLiteral( "project_path" ), QCoreApplication::translate( "variable_help", "Full path (including file name) of current project." ) );
706  sVariableHelpTexts()->insert( QStringLiteral( "project_folder" ), QCoreApplication::translate( "variable_help", "Folder for current project." ) );
707  sVariableHelpTexts()->insert( QStringLiteral( "project_filename" ), QCoreApplication::translate( "variable_help", "Filename of current project." ) );
708  sVariableHelpTexts()->insert( QStringLiteral( "project_basename" ), QCoreApplication::translate( "variable_help", "Base name of current project's filename (without path and extension)." ) );
709  sVariableHelpTexts()->insert( QStringLiteral( "project_home" ), QCoreApplication::translate( "variable_help", "Home path of current project." ) );
710  sVariableHelpTexts()->insert( QStringLiteral( "project_crs" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of project (e.g., 'EPSG:4326')." ) );
711  sVariableHelpTexts()->insert( QStringLiteral( "project_crs_definition" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of project (full definition)." ) );
712  sVariableHelpTexts()->insert( QStringLiteral( "project_units" ), QCoreApplication::translate( "variable_help", "Unit of the project's CRS." ) );
713  sVariableHelpTexts()->insert( QStringLiteral( "project_crs_description" ), QCoreApplication::translate( "variable_help", "Name of the coordinate reference system of the project." ) );
714  sVariableHelpTexts()->insert( QStringLiteral( "project_crs_acronym" ), QCoreApplication::translate( "variable_help", "Acronym of the coordinate reference system of the project." ) );
715  sVariableHelpTexts()->insert( QStringLiteral( "project_crs_ellipsoid" ), QCoreApplication::translate( "variable_help", "Acronym of the ellipsoid of the coordinate reference system of the project." ) );
716  sVariableHelpTexts()->insert( QStringLiteral( "project_crs_proj4" ), QCoreApplication::translate( "variable_help", "Proj4 definition of the coordinate reference system of the project." ) );
717  sVariableHelpTexts()->insert( QStringLiteral( "project_crs_wkt" ), QCoreApplication::translate( "variable_help", "WKT definition of the coordinate reference system of the project." ) );
718  sVariableHelpTexts()->insert( QStringLiteral( "project_author" ), QCoreApplication::translate( "variable_help", "Project author, taken from project metadata." ) );
719  sVariableHelpTexts()->insert( QStringLiteral( "project_abstract" ), QCoreApplication::translate( "variable_help", "Project abstract, taken from project metadata." ) );
720  sVariableHelpTexts()->insert( QStringLiteral( "project_creation_date" ), QCoreApplication::translate( "variable_help", "Project creation date, taken from project metadata." ) );
721  sVariableHelpTexts()->insert( QStringLiteral( "project_identifier" ), QCoreApplication::translate( "variable_help", "Project identifier, taken from project metadata." ) );
722  sVariableHelpTexts()->insert( QStringLiteral( "project_last_saved" ), QCoreApplication::translate( "variable_help", "Date/time when project was last saved." ) );
723  sVariableHelpTexts()->insert( QStringLiteral( "project_keywords" ), QCoreApplication::translate( "variable_help", "Project keywords, taken from project metadata." ) );
724  sVariableHelpTexts()->insert( QStringLiteral( "project_area_units" ), QCoreApplication::translate( "variable_help", "Area unit for current project, used when calculating areas of geometries." ) );
725  sVariableHelpTexts()->insert( QStringLiteral( "project_distance_units" ), QCoreApplication::translate( "variable_help", "Distance unit for current project, used when calculating lengths of geometries." ) );
726  sVariableHelpTexts()->insert( QStringLiteral( "project_ellipsoid" ), QCoreApplication::translate( "variable_help", "Name of ellipsoid of current project, used when calculating geodetic areas and lengths of geometries." ) );
727  sVariableHelpTexts()->insert( QStringLiteral( "layer_ids" ), QCoreApplication::translate( "variable_help", "List of all map layer IDs from the current project." ) );
728  sVariableHelpTexts()->insert( QStringLiteral( "layers" ), QCoreApplication::translate( "variable_help", "List of all map layers from the current project." ) );
729 
730  //layer variables
731  sVariableHelpTexts()->insert( QStringLiteral( "layer_name" ), QCoreApplication::translate( "variable_help", "Name of current layer." ) );
732  sVariableHelpTexts()->insert( QStringLiteral( "layer_id" ), QCoreApplication::translate( "variable_help", "ID of current layer." ) );
733  sVariableHelpTexts()->insert( QStringLiteral( "layer_crs" ), QCoreApplication::translate( "variable_help", "CRS Authority ID of current layer." ) );
734  sVariableHelpTexts()->insert( QStringLiteral( "layer" ), QCoreApplication::translate( "variable_help", "The current layer." ) );
735 
736  //composition variables
737  sVariableHelpTexts()->insert( QStringLiteral( "layout_name" ), QCoreApplication::translate( "variable_help", "Name of composition." ) );
738  sVariableHelpTexts()->insert( QStringLiteral( "layout_numpages" ), QCoreApplication::translate( "variable_help", "Number of pages in composition." ) );
739  sVariableHelpTexts()->insert( QStringLiteral( "layout_page" ), QCoreApplication::translate( "variable_help", "Current page number in composition." ) );
740  sVariableHelpTexts()->insert( QStringLiteral( "layout_pageheight" ), QCoreApplication::translate( "variable_help", "Composition page height in mm." ) );
741  sVariableHelpTexts()->insert( QStringLiteral( "layout_pagewidth" ), QCoreApplication::translate( "variable_help", "Composition page width in mm." ) );
742  sVariableHelpTexts()->insert( QStringLiteral( "layout_pageoffsets" ), QCoreApplication::translate( "variable_help", "Array of Y coordinate of the top of each page." ) );
743  sVariableHelpTexts()->insert( QStringLiteral( "layout_dpi" ), QCoreApplication::translate( "variable_help", "Composition resolution (DPI)." ) );
744 
745  //atlas variables
746  sVariableHelpTexts()->insert( QStringLiteral( "atlas_layerid" ), QCoreApplication::translate( "variable_help", "Current atlas coverage layer ID." ) );
747  sVariableHelpTexts()->insert( QStringLiteral( "atlas_layername" ), QCoreApplication::translate( "variable_help", "Current atlas coverage layer name." ) );
748  sVariableHelpTexts()->insert( QStringLiteral( "atlas_totalfeatures" ), QCoreApplication::translate( "variable_help", "Total number of features in atlas." ) );
749  sVariableHelpTexts()->insert( QStringLiteral( "atlas_featurenumber" ), QCoreApplication::translate( "variable_help", "Current atlas feature number." ) );
750  sVariableHelpTexts()->insert( QStringLiteral( "atlas_filename" ), QCoreApplication::translate( "variable_help", "Current atlas file name." ) );
751  sVariableHelpTexts()->insert( QStringLiteral( "atlas_pagename" ), QCoreApplication::translate( "variable_help", "Current atlas page name." ) );
752  sVariableHelpTexts()->insert( QStringLiteral( "atlas_feature" ), QCoreApplication::translate( "variable_help", "Current atlas feature (as feature object)." ) );
753  sVariableHelpTexts()->insert( QStringLiteral( "atlas_featureid" ), QCoreApplication::translate( "variable_help", "Current atlas feature ID." ) );
754  sVariableHelpTexts()->insert( QStringLiteral( "atlas_geometry" ), QCoreApplication::translate( "variable_help", "Current atlas feature geometry." ) );
755 
756  //layout item variables
757  sVariableHelpTexts()->insert( QStringLiteral( "item_id" ), QCoreApplication::translate( "variable_help", "Layout item user-assigned ID (not necessarily unique)." ) );
758  sVariableHelpTexts()->insert( QStringLiteral( "item_uuid" ), QCoreApplication::translate( "variable_help", "layout item unique ID." ) );
759  sVariableHelpTexts()->insert( QStringLiteral( "item_left" ), QCoreApplication::translate( "variable_help", "Left position of layout item (in mm)." ) );
760  sVariableHelpTexts()->insert( QStringLiteral( "item_top" ), QCoreApplication::translate( "variable_help", "Top position of layout item (in mm)." ) );
761  sVariableHelpTexts()->insert( QStringLiteral( "item_width" ), QCoreApplication::translate( "variable_help", "Width of layout item (in mm)." ) );
762  sVariableHelpTexts()->insert( QStringLiteral( "item_height" ), QCoreApplication::translate( "variable_help", "Height of layout item (in mm)." ) );
763 
764  //map settings item variables
765  sVariableHelpTexts()->insert( QStringLiteral( "map_id" ), QCoreApplication::translate( "variable_help", "ID of current map destination. This will be 'canvas' for canvas renders, and the item ID for layout map renders." ) );
766  sVariableHelpTexts()->insert( QStringLiteral( "map_rotation" ), QCoreApplication::translate( "variable_help", "Current rotation of map." ) );
767  sVariableHelpTexts()->insert( QStringLiteral( "map_scale" ), QCoreApplication::translate( "variable_help", "Current scale of map." ) );
768  sVariableHelpTexts()->insert( QStringLiteral( "map_extent" ), QCoreApplication::translate( "variable_help", "Geometry representing the current extent of the map." ) );
769  sVariableHelpTexts()->insert( QStringLiteral( "map_extent_center" ), QCoreApplication::translate( "variable_help", "Center of map." ) );
770  sVariableHelpTexts()->insert( QStringLiteral( "map_extent_width" ), QCoreApplication::translate( "variable_help", "Width of map." ) );
771  sVariableHelpTexts()->insert( QStringLiteral( "map_extent_height" ), QCoreApplication::translate( "variable_help", "Height of map." ) );
772  sVariableHelpTexts()->insert( QStringLiteral( "map_crs" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of map (e.g., 'EPSG:4326')." ) );
773  sVariableHelpTexts()->insert( QStringLiteral( "map_crs_description" ), QCoreApplication::translate( "variable_help", "Name of the coordinate reference system of the map." ) );
774  sVariableHelpTexts()->insert( QStringLiteral( "map_units" ), QCoreApplication::translate( "variable_help", "Units for map measurements." ) );
775  sVariableHelpTexts()->insert( QStringLiteral( "map_crs_definition" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of the map (full definition)." ) );
776  sVariableHelpTexts()->insert( QStringLiteral( "map_crs_acronym" ), QCoreApplication::translate( "variable_help", "Acronym of the coordinate reference system of the map." ) );
777  sVariableHelpTexts()->insert( QStringLiteral( "map_crs_ellipsoid" ), QCoreApplication::translate( "variable_help", "Acronym of the ellipsoid of the coordinate reference system of the map." ) );
778  sVariableHelpTexts()->insert( QStringLiteral( "map_crs_proj4" ), QCoreApplication::translate( "variable_help", "Proj4 definition of the coordinate reference system of the map." ) );
779  sVariableHelpTexts()->insert( QStringLiteral( "map_crs_wkt" ), QCoreApplication::translate( "variable_help", "WKT definition of the coordinate reference system of the map." ) );
780  sVariableHelpTexts()->insert( QStringLiteral( "map_layer_ids" ), QCoreApplication::translate( "variable_help", "List of map layer IDs visible in the map." ) );
781  sVariableHelpTexts()->insert( QStringLiteral( "map_layers" ), QCoreApplication::translate( "variable_help", "List of map layers visible in the map." ) );
782 
783  sVariableHelpTexts()->insert( QStringLiteral( "map_start_time" ), QCoreApplication::translate( "variable_help", "Start of the map's temporal time range (as a datetime value)" ) );
784  sVariableHelpTexts()->insert( QStringLiteral( "map_end_time" ), QCoreApplication::translate( "variable_help", "End of the map's temporal time range (as a datetime value)" ) );
785  sVariableHelpTexts()->insert( QStringLiteral( "map_interval" ), QCoreApplication::translate( "variable_help", "Duration of the map's temporal time range (as an interval value)" ) );
786 
787  sVariableHelpTexts()->insert( QStringLiteral( "frame_rate" ), QCoreApplication::translate( "variable_help", "Number of frames per second during animation playback" ) );
788  sVariableHelpTexts()->insert( QStringLiteral( "frame_number" ), QCoreApplication::translate( "variable_help", "Current frame number during animation playback" ) );
789  sVariableHelpTexts()->insert( QStringLiteral( "frame_duration" ), QCoreApplication::translate( "variable_help", "Temporal duration of each animation frame (as an interval value)" ) );
790  sVariableHelpTexts()->insert( QStringLiteral( "animation_start_time" ), QCoreApplication::translate( "variable_help", "Start of the animation's overall temporal time range (as a datetime value)" ) );
791  sVariableHelpTexts()->insert( QStringLiteral( "animation_end_time" ), QCoreApplication::translate( "variable_help", "End of the animation's overall temporal time range (as a datetime value)" ) );
792  sVariableHelpTexts()->insert( QStringLiteral( "animation_interval" ), QCoreApplication::translate( "variable_help", "Duration of the animation's overall temporal time range (as an interval value)" ) );
793 
794  // vector tile layer variables
795  sVariableHelpTexts()->insert( QStringLiteral( "zoom_level" ), QCoreApplication::translate( "variable_help", "Zoom level of the tile that is being rendered (derived from the current map scale). Normally in interval [0, 20]." ) );
796  sVariableHelpTexts()->insert( QStringLiteral( "vector_tile_zoom" ), QCoreApplication::translate( "variable_help", "Exact zoom level of the tile that is being rendered (derived from the current map scale). Normally in interval [0, 20]. Unlike @zoom_level, this variable is a floating point value which can be used to interpolated values between two integer zoom levels." ) );
797 
798  sVariableHelpTexts()->insert( QStringLiteral( "row_number" ), QCoreApplication::translate( "variable_help", "Stores the number of the current row." ) );
799  sVariableHelpTexts()->insert( QStringLiteral( "grid_number" ), QCoreApplication::translate( "variable_help", "Current grid annotation value." ) );
800  sVariableHelpTexts()->insert( QStringLiteral( "grid_axis" ), QCoreApplication::translate( "variable_help", "Current grid annotation axis (e.g., 'x' for longitude, 'y' for latitude)." ) );
801  sVariableHelpTexts()->insert( QStringLiteral( "column_number" ), QCoreApplication::translate( "variable_help", "Stores the number of the current column." ) );
802 
803  // map canvas item variables
804  sVariableHelpTexts()->insert( QStringLiteral( "canvas_cursor_point" ), QCoreApplication::translate( "variable_help", "Last cursor position on the canvas in the project's geographical coordinates." ) );
805 
806  // legend canvas item variables
807  sVariableHelpTexts()->insert( QStringLiteral( "legend_title" ), QCoreApplication::translate( "variable_help", "Title of the legend." ) );
808  sVariableHelpTexts()->insert( QStringLiteral( "legend_column_count" ), QCoreApplication::translate( "variable_help", "Number of column in the legend." ) );
809  sVariableHelpTexts()->insert( QStringLiteral( "legend_split_layers" ), QCoreApplication::translate( "variable_help", "Boolean indicating if layers can be split in the legend." ) );
810  sVariableHelpTexts()->insert( QStringLiteral( "legend_wrap_string" ), QCoreApplication::translate( "variable_help", "Characters used to wrap the legend text." ) );
811  sVariableHelpTexts()->insert( QStringLiteral( "legend_filter_by_map" ), QCoreApplication::translate( "variable_help", "Boolean indicating if the content of the legend is filtered by the map." ) );
812  sVariableHelpTexts()->insert( QStringLiteral( "legend_filter_out_atlas" ), QCoreApplication::translate( "variable_help", "Boolean indicating if the Atlas is filtered out of the legend." ) );
813 
814  // scalebar rendering
815  sVariableHelpTexts()->insert( QStringLiteral( "scale_value" ), QCoreApplication::translate( "variable_help", "Current scale bar distance value." ) );
816 
817  // map tool capture variables
818  sVariableHelpTexts()->insert( QStringLiteral( "snapping_results" ), QCoreApplication::translate( "variable_help",
819  "<p>An array with an item for each snapped point.</p>"
820  "<p>Each item is a map with the following keys:</p>"
821  "<dl>"
822  "<dt>valid</dt><dd>Boolean that indicates if the snapping result is valid</dd>"
823  "<dt>layer</dt><dd>The layer on which the snapped feature is</dd>"
824  "<dt>feature_id</dt><dd>The feature id of the snapped feature</dd>"
825  "<dt>vertex_index</dt><dd>The index of the snapped vertex</dd>"
826  "<dt>distance</dt><dd>The distance between the mouse cursor and the snapped point at the time of snapping</dd>"
827  "</dl>" ) );
828 
829 
830  //symbol variables
831  sVariableHelpTexts()->insert( QStringLiteral( "geometry_part_count" ), QCoreApplication::translate( "variable_help", "Number of parts in rendered feature's geometry." ) );
832  sVariableHelpTexts()->insert( QStringLiteral( "geometry_part_num" ), QCoreApplication::translate( "variable_help", "Current geometry part number for feature being rendered." ) );
833  sVariableHelpTexts()->insert( QStringLiteral( "geometry_point_count" ), QCoreApplication::translate( "variable_help", "Number of points in the rendered geometry's part. It is only meaningful for line geometries and for symbol layers that set this variable." ) );
834  sVariableHelpTexts()->insert( QStringLiteral( "geometry_point_num" ), QCoreApplication::translate( "variable_help", "Current point number in the rendered geometry's part. It is only meaningful for line geometries and for symbol layers that set this variable." ) );
835 
836  sVariableHelpTexts()->insert( QStringLiteral( "symbol_color" ), QCoreApplication::translate( "symbol_color", "Color of symbol used to render the feature." ) );
837  sVariableHelpTexts()->insert( QStringLiteral( "symbol_angle" ), QCoreApplication::translate( "symbol_angle", "Angle of symbol used to render the feature (valid for marker symbols only)." ) );
838  sVariableHelpTexts()->insert( QStringLiteral( "symbol_layer_count" ), QCoreApplication::translate( "symbol_layer_count", "Total number of symbol layers in the symbol." ) );
839  sVariableHelpTexts()->insert( QStringLiteral( "symbol_layer_index" ), QCoreApplication::translate( "symbol_layer_index", "Current symbol layer index." ) );
840  sVariableHelpTexts()->insert( QStringLiteral( "symbol_marker_row" ), QCoreApplication::translate( "symbol_marker_row", "Row number for marker (valid for point pattern fills only)." ) );
841  sVariableHelpTexts()->insert( QStringLiteral( "symbol_marker_column" ), QCoreApplication::translate( "symbol_marker_column", "Column number for marker (valid for point pattern fills only)." ) );
842 
843  sVariableHelpTexts()->insert( QStringLiteral( "symbol_label" ), QCoreApplication::translate( "symbol_label", "Label for the symbol (either a user defined label or the default autogenerated label)." ) );
844  sVariableHelpTexts()->insert( QStringLiteral( "symbol_id" ), QCoreApplication::translate( "symbol_id", "Internal ID of the symbol." ) );
845  sVariableHelpTexts()->insert( QStringLiteral( "symbol_count" ), QCoreApplication::translate( "symbol_count", "Total number of features represented by the symbol." ) );
846 
847  //cluster variables
848  sVariableHelpTexts()->insert( QStringLiteral( "cluster_color" ), QCoreApplication::translate( "cluster_color", "Color of symbols within a cluster, or NULL if symbols have mixed colors." ) );
849  sVariableHelpTexts()->insert( QStringLiteral( "cluster_size" ), QCoreApplication::translate( "cluster_size", "Number of symbols contained within a cluster." ) );
850 
851  //processing variables
852  sVariableHelpTexts()->insert( QStringLiteral( "algorithm_id" ), QCoreApplication::translate( "algorithm_id", "Unique ID for algorithm." ) );
853  sVariableHelpTexts()->insert( QStringLiteral( "model_path" ), QCoreApplication::translate( "variable_help", "Full path (including file name) of current model (or project path if model is embedded in a project)." ) );
854  sVariableHelpTexts()->insert( QStringLiteral( "model_folder" ), QCoreApplication::translate( "variable_help", "Folder containing current model (or project folder if model is embedded in a project)." ) );
855  sVariableHelpTexts()->insert( QStringLiteral( "model_name" ), QCoreApplication::translate( "variable_help", "Name of current model." ) );
856  sVariableHelpTexts()->insert( QStringLiteral( "model_group" ), QCoreApplication::translate( "variable_help", "Group for current model." ) );
857  sVariableHelpTexts()->insert( QStringLiteral( "fullextent_minx" ), QCoreApplication::translate( "fullextent_minx", "Minimum x-value from full canvas extent (including all layers)." ) );
858  sVariableHelpTexts()->insert( QStringLiteral( "fullextent_miny" ), QCoreApplication::translate( "fullextent_miny", "Minimum y-value from full canvas extent (including all layers)." ) );
859  sVariableHelpTexts()->insert( QStringLiteral( "fullextent_maxx" ), QCoreApplication::translate( "fullextent_maxx", "Maximum x-value from full canvas extent (including all layers)." ) );
860  sVariableHelpTexts()->insert( QStringLiteral( "fullextent_maxy" ), QCoreApplication::translate( "fullextent_maxy", "Maximum y-value from full canvas extent (including all layers)." ) );
861 
862  //provider notification
863  sVariableHelpTexts()->insert( QStringLiteral( "notification_message" ), QCoreApplication::translate( "notification_message", "Content of the notification message sent by the provider (available only for actions triggered by provider notifications)." ) );
864 
865  //form context variable
866  sVariableHelpTexts()->insert( QStringLiteral( "current_geometry" ), QCoreApplication::translate( "current_geometry", "Represents the geometry of the feature currently being edited in the form or the table row. Can be used in a form/row context to filter the related features." ) );
867  sVariableHelpTexts()->insert( QStringLiteral( "current_feature" ), QCoreApplication::translate( "current_feature", "Represents the feature currently being edited in the form or the table row. Can be used in a form/row context to filter the related features." ) );
868 
869  //parent form context variable
870  sVariableHelpTexts()->insert( QStringLiteral( "current_parent_geometry" ), QCoreApplication::translate( "current_parent_geometry",
871  "Only usable in an embedded form context, "
872  "represents the geometry of the feature currently being edited in the parent form.\n"
873  "Can be used in a form/row context to filter the related features using a value "
874  "from the feature currently edited in the parent form, to make sure that the filter "
875  "still works with standalone forms it is recommended to wrap this variable in a "
876  "'coalesce()'." ) );
877  sVariableHelpTexts()->insert( QStringLiteral( "current_parent_feature" ), QCoreApplication::translate( "current_parent_feature",
878  "Only usable in an embedded form context, "
879  "represents the feature currently being edited in the parent form.\n"
880  "Can be used in a form/row context to filter the related features using a value "
881  "from the feature currently edited in the parent form, to make sure that the filter "
882  "still works with standalone forms it is recommended to wrap this variable in a "
883  "'coalesce()'." ) );
884 
885  //form variable
886  sVariableHelpTexts()->insert( QStringLiteral( "form_mode" ), QCoreApplication::translate( "form_mode", "What the form is used for, like AddFeatureMode, SingleEditMode, MultiEditMode, SearchMode, AggregateSearchMode or IdentifyMode as string." ) );
887 }
888 
889 QString QgsExpression::variableHelpText( const QString &variableName )
890 {
891  QgsExpression::initVariableHelp();
892  return sVariableHelpTexts()->value( variableName, QString() );
893 }
894 
895 QString QgsExpression::formatVariableHelp( const QString &description, bool showValue, const QVariant &value )
896 {
897  QString text = !description.isEmpty() ? QStringLiteral( "<p>%1</p>" ).arg( description ) : QString();
898  if ( showValue )
899  {
900  QString valueString;
901  if ( !value.isValid() )
902  {
903  valueString = QCoreApplication::translate( "variable_help", "not set" );
904  }
905  else
906  {
907  valueString = QStringLiteral( "<b>%1</b>" ).arg( formatPreviewString( value ) );
908  }
909  text.append( QCoreApplication::translate( "variable_help", "<p>Current value: %1</p>" ).arg( valueString ) );
910  }
911  return text;
912 }
913 
914 QString QgsExpression::group( const QString &name )
915 {
916  if ( sGroups()->isEmpty() )
917  {
918  sGroups()->insert( QStringLiteral( "Aggregates" ), tr( "Aggregates" ) );
919  sGroups()->insert( QStringLiteral( "Arrays" ), tr( "Arrays" ) );
920  sGroups()->insert( QStringLiteral( "Color" ), tr( "Color" ) );
921  sGroups()->insert( QStringLiteral( "Conditionals" ), tr( "Conditionals" ) );
922  sGroups()->insert( QStringLiteral( "Conversions" ), tr( "Conversions" ) );
923  sGroups()->insert( QStringLiteral( "Date and Time" ), tr( "Date and Time" ) );
924  sGroups()->insert( QStringLiteral( "Fields and Values" ), tr( "Fields and Values" ) );
925  sGroups()->insert( QStringLiteral( "Files and Paths" ), tr( "Files and Paths" ) );
926  sGroups()->insert( QStringLiteral( "Fuzzy Matching" ), tr( "Fuzzy Matching" ) );
927  sGroups()->insert( QStringLiteral( "General" ), tr( "General" ) );
928  sGroups()->insert( QStringLiteral( "GeometryGroup" ), tr( "Geometry" ) );
929  sGroups()->insert( QStringLiteral( "Map Layers" ), tr( "Map Layers" ) );
930  sGroups()->insert( QStringLiteral( "Maps" ), tr( "Maps" ) );
931  sGroups()->insert( QStringLiteral( "Math" ), tr( "Math" ) );
932  sGroups()->insert( QStringLiteral( "Operators" ), tr( "Operators" ) );
933  sGroups()->insert( QStringLiteral( "Rasters" ), tr( "Rasters" ) );
934  sGroups()->insert( QStringLiteral( "Record and Attributes" ), tr( "Record and Attributes" ) );
935  sGroups()->insert( QStringLiteral( "String" ), tr( "String" ) );
936  sGroups()->insert( QStringLiteral( "Variables" ), tr( "Variables" ) );
937  sGroups()->insert( QStringLiteral( "Recent (%1)" ), tr( "Recent (%1)" ) );
938  sGroups()->insert( QStringLiteral( "UserGroup" ), tr( "User expressions" ) );
939  }
940 
941  //return the translated name for this group. If group does not
942  //have a translated name in the gGroups hash, return the name
943  //unchanged
944  return sGroups()->value( name, name );
945 }
946 
947 QString QgsExpression::formatPreviewString( const QVariant &value, const bool htmlOutput, int maximumPreviewLength )
948 {
949  const QString startToken = htmlOutput ? QStringLiteral( "<i>&lt;" ) : QStringLiteral( "<" );
950  const QString endToken = htmlOutput ? QStringLiteral( "&gt;</i>" ) : QStringLiteral( ">" );
951 
952  if ( value.canConvert<QgsGeometry>() )
953  {
954  //result is a geometry
955  QgsGeometry geom = value.value<QgsGeometry>();
956  if ( geom.isNull() )
957  return startToken + tr( "empty geometry" ) + endToken;
958  else
959  return startToken + tr( "geometry: %1" ).arg( QgsWkbTypes::displayString( geom.constGet()->wkbType() ) )
960  + endToken;
961  }
962  else if ( value.value< QgsWeakMapLayerPointer >().data() )
963  {
964  return startToken + tr( "map layer" ) + endToken;
965  }
966  else if ( !value.isValid() )
967  {
968  return htmlOutput ? tr( "<i>NULL</i>" ) : QString();
969  }
970  else if ( value.canConvert< QgsFeature >() )
971  {
972  //result is a feature
973  QgsFeature feat = value.value<QgsFeature>();
974  return startToken + tr( "feature: %1" ).arg( feat.id() ) + endToken;
975  }
976  else if ( value.canConvert< QgsInterval >() )
977  {
978  QgsInterval interval = value.value<QgsInterval>();
979  if ( interval.days() > 1 )
980  {
981  return startToken + tr( "interval: %1 days" ).arg( interval.days() ) + endToken;
982  }
983  else if ( interval.hours() > 1 )
984  {
985  return startToken + tr( "interval: %1 hours" ).arg( interval.hours() ) + endToken;
986  }
987  else if ( interval.minutes() > 1 )
988  {
989  return startToken + tr( "interval: %1 minutes" ).arg( interval.minutes() ) + endToken;
990  }
991  else
992  {
993  return startToken + tr( "interval: %1 seconds" ).arg( interval.seconds() ) + endToken;
994  }
995  }
996  else if ( value.canConvert< QgsGradientColorRamp >() )
997  {
998  return startToken + tr( "gradient ramp" ) + endToken;
999  }
1000  else if ( value.type() == QVariant::Date )
1001  {
1002  QDate dt = value.toDate();
1003  return startToken + tr( "date: %1" ).arg( dt.toString( QStringLiteral( "yyyy-MM-dd" ) ) ) + endToken;
1004  }
1005  else if ( value.type() == QVariant::Time )
1006  {
1007  QTime tm = value.toTime();
1008  return startToken + tr( "time: %1" ).arg( tm.toString( QStringLiteral( "hh:mm:ss" ) ) ) + endToken;
1009  }
1010  else if ( value.type() == QVariant::DateTime )
1011  {
1012  QDateTime dt = value.toDateTime();
1013  return startToken + tr( "datetime: %1" ).arg( dt.toString( QStringLiteral( "yyyy-MM-dd hh:mm:ss" ) ) ) + endToken;
1014  }
1015  else if ( value.type() == QVariant::String )
1016  {
1017  const QString previewString = value.toString();
1018  if ( previewString.length() > maximumPreviewLength + 3 )
1019  {
1020  return tr( "'%1…'" ).arg( previewString.left( maximumPreviewLength ) );
1021  }
1022  else
1023  {
1024  return '\'' + previewString + '\'';
1025  }
1026  }
1027  else if ( value.type() == QVariant::Map )
1028  {
1029  QString mapStr = QStringLiteral( "{" );
1030  const QVariantMap map = value.toMap();
1031  QString separator;
1032  for ( QVariantMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it )
1033  {
1034  mapStr.append( separator );
1035  if ( separator.isEmpty() )
1036  separator = QStringLiteral( "," );
1037 
1038  mapStr.append( QStringLiteral( " '%1': %2" ).arg( it.key(), formatPreviewString( it.value(), htmlOutput ) ) );
1039  if ( mapStr.length() > maximumPreviewLength - 3 )
1040  {
1041  mapStr = tr( "%1…" ).arg( mapStr.left( maximumPreviewLength - 2 ) );
1042  break;
1043  }
1044  }
1045  if ( !map.empty() )
1046  mapStr += QLatin1Char( ' ' );
1047  mapStr += QLatin1Char( '}' );
1048  return mapStr;
1049  }
1050  else if ( value.type() == QVariant::List || value.type() == QVariant::StringList )
1051  {
1052  QString listStr = QStringLiteral( "[" );
1053  const QVariantList list = value.toList();
1054  QString separator;
1055  for ( const QVariant &arrayValue : list )
1056  {
1057  listStr.append( separator );
1058  if ( separator.isEmpty() )
1059  separator = QStringLiteral( "," );
1060 
1061  listStr.append( " " );
1062  listStr.append( formatPreviewString( arrayValue, htmlOutput ) );
1063  if ( listStr.length() > maximumPreviewLength - 3 )
1064  {
1065  listStr = QString( tr( "%1…" ) ).arg( listStr.left( maximumPreviewLength - 2 ) );
1066  break;
1067  }
1068  }
1069  if ( !list.empty() )
1070  listStr += QLatin1Char( ' ' );
1071  listStr += QLatin1Char( ']' );
1072  return listStr;
1073  }
1074  else
1075  {
1076  return value.toString();
1077  }
1078 }
1079 
1080 QString QgsExpression::createFieldEqualityExpression( const QString &fieldName, const QVariant &value )
1081 {
1082  QString expr;
1083 
1084  if ( value.isNull() )
1085  expr = QStringLiteral( "%1 IS NULL" ).arg( quotedColumnRef( fieldName ) );
1086  else
1087  expr = QStringLiteral( "%1 = %2" ).arg( quotedColumnRef( fieldName ), quotedValue( value ) );
1088 
1089  return expr;
1090 }
1091 
1092 bool QgsExpression::isFieldEqualityExpression( const QString &expression, QString &field, QVariant &value )
1093 {
1095 
1096  if ( !e.rootNode() )
1097  return false;
1098 
1099  if ( const QgsExpressionNodeBinaryOperator *binOp = dynamic_cast<const QgsExpressionNodeBinaryOperator *>( e.rootNode() ) )
1100  {
1101  if ( binOp->op() == QgsExpressionNodeBinaryOperator::boEQ )
1102  {
1103  const QgsExpressionNodeColumnRef *columnRef = dynamic_cast<const QgsExpressionNodeColumnRef *>( binOp->opLeft() );
1104  const QgsExpressionNodeLiteral *literal = dynamic_cast<const QgsExpressionNodeLiteral *>( binOp->opRight() );
1105  if ( columnRef && literal )
1106  {
1107  field = columnRef->name();
1108  value = literal->value();
1109  return true;
1110  }
1111  }
1112  }
1113  return false;
1114 }
1115 
1116 bool QgsExpression::attemptReduceToInClause( const QStringList &expressions, QString &result )
1117 {
1118  if ( expressions.empty() )
1119  return false;
1120 
1121  QString inField;
1122  bool first = true;
1123  QStringList values;
1124  for ( const QString &expression : expressions )
1125  {
1126  QString field;
1127  QVariant value;
1129  {
1130  if ( first )
1131  {
1132  inField = field;
1133  first = false;
1134  }
1135  else if ( field != inField )
1136  {
1137  return false;
1138  }
1139  values << QgsExpression::quotedValue( value );
1140  }
1141  else
1142  {
1143  // we also allow reducing similar 'field IN (...)' expressions!
1145 
1146  if ( !e.rootNode() )
1147  return false;
1148 
1149  if ( const QgsExpressionNodeInOperator *inOp = dynamic_cast<const QgsExpressionNodeInOperator *>( e.rootNode() ) )
1150  {
1151  if ( inOp->isNotIn() )
1152  return false;
1153 
1154  const QgsExpressionNodeColumnRef *columnRef = dynamic_cast<const QgsExpressionNodeColumnRef *>( inOp->node() );
1155  if ( !columnRef )
1156  return false;
1157 
1158  if ( first )
1159  {
1160  inField = columnRef->name();
1161  first = false;
1162  }
1163  else if ( columnRef->name() != inField )
1164  {
1165  return false;
1166  }
1167 
1168  if ( QgsExpressionNode::NodeList *nodeList = inOp->list() )
1169  {
1170  const QList<QgsExpressionNode *> nodes = nodeList->list();
1171  for ( const QgsExpressionNode *node : nodes )
1172  {
1173  const QgsExpressionNodeLiteral *literal = dynamic_cast<const QgsExpressionNodeLiteral *>( node );
1174  if ( !literal )
1175  return false;
1176 
1177  values << QgsExpression::quotedValue( literal->value() );
1178  }
1179  }
1180  }
1181  else
1182  {
1183  return false;
1184  }
1185  }
1186  }
1187  result = QStringLiteral( "%1 IN (%2)" ).arg( inField, values.join( ',' ) );
1188  return true;
1189 }
1190 
1192 {
1193  return d->mRootNode;
1194 }
1195 
1197 {
1198  return d->mRootNode && d->mRootNode->nodeType() == QgsExpressionNode::ntColumnRef;
1199 }
1200 
1201 QList<const QgsExpressionNode *> QgsExpression::nodes() const
1202 {
1203  if ( !d->mRootNode )
1204  return QList<const QgsExpressionNode *>();
1205 
1206  return d->mRootNode->nodes();
1207 }
1208 
1209 
1210 
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 id, geometry and a list of field/values...
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:501
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...
CONSTLATIN1STRING geoNone()
Constant that holds the string representation for "No ellips/No CRS".
Definition: qgis.h:716
QMap< QString, QString > QgsStringMap
Definition: qgis.h:759
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
QgsExpressionNode * parseExpression(const QString &str, QString &parserErrorMsg, QList< QgsExpression::ParserError > &parserErrors)
HelpTextHash & functionHelpTexts()
const QgsField & field
Definition: qgsfield.h:472
#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:1769