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