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