diff --git a/.gitignore b/.gitignore index a5dc157afe6..e87e0c1a103 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,8 @@ scripts/astyle.exe scripts/Debug scripts/qgisstyle* scripts/RelWithDebInfo +src/core/qgscontexthelp_texts.cpp +src/core/qgsexpression_texts.cpp tests/testdata/cache/ tests/testdata/checker360by180.asc.aux.xml tests/testdata/grass/wgs84/test/.gislock diff --git a/resources/function_help/json/array b/resources/function_help/json/array new file mode 100644 index 00000000000..ee71d768809 --- /dev/null +++ b/resources/function_help/json/array @@ -0,0 +1,12 @@ +{ + "name": "array", + "type": "function", + "description": "Returns an array containing all the values passed as parameter.", + "variableLenArguments": true, + "arguments": [ + {"arg":"value1", "syntaxOnly": true}, + {"arg":"value2", "syntaxOnly": true}, + {"arg":"value", "descOnly": true, "description":"a value"}], + "examples": [ { "expression":"array(2,10)", "returns":"array: 2, 10"} + ] +} diff --git a/resources/function_help/json/array_append b/resources/function_help/json/array_append new file mode 100644 index 00000000000..4da5a150087 --- /dev/null +++ b/resources/function_help/json/array_append @@ -0,0 +1,8 @@ +{ + "name": "array_append", + "type": "function", + "description": "Returns an array with the given value added at the end.", + "arguments": [ {"arg":"array","description":"an array"}, + {"arg":"value","description":"the value to add"}], + "examples": [ { "expression":"array_append(array(1,2,3),4)", "returns":"array: 1,2,3,4"}] +} diff --git a/resources/function_help/json/array_cat b/resources/function_help/json/array_cat new file mode 100644 index 00000000000..7529a3f7132 --- /dev/null +++ b/resources/function_help/json/array_cat @@ -0,0 +1,12 @@ +{ + "name": "array_cat", + "type": "function", + "description": "Returns an array containing all the given arrays concatenated.", + "variableLenArguments": true, + "arguments": [ + {"arg":"array1", "syntaxOnly": true}, + {"arg":"array2", "syntaxOnly": true}, + {"arg":"array", "descOnly": true, "description":"an array"}], + "examples": [ { "expression":"array_cat(array(1,2),array(2,3))", "returns":"array: 1,2,2,3"} + ] +} diff --git a/resources/function_help/json/array_contains b/resources/function_help/json/array_contains new file mode 100644 index 00000000000..a7abe30b4d2 --- /dev/null +++ b/resources/function_help/json/array_contains @@ -0,0 +1,8 @@ +{ + "name": "array_contains", + "type": "function", + "description": "Returns true if an array contains the given value.", + "arguments": [ {"arg":"array","description":"an array"}, + {"arg":"value","description":"the value to search"}], + "examples": [ { "expression":"array_contains(array(1,2,3),2)", "returns":"true"}] +} diff --git a/resources/function_help/json/array_find b/resources/function_help/json/array_find new file mode 100644 index 00000000000..4217978b9a9 --- /dev/null +++ b/resources/function_help/json/array_find @@ -0,0 +1,8 @@ +{ + "name": "array_find", + "type": "function", + "description": "Returns the index (0 for the first one) of a value within an array. Returns -1 if the value is not found.", + "arguments": [ {"arg":"array","description":"an array"}, + {"arg":"value","description":"the value to search"}], + "examples": [ { "expression":"array_find(array(1,2,3),2)", "returns":"1"}] +} diff --git a/resources/function_help/json/array_get b/resources/function_help/json/array_get new file mode 100644 index 00000000000..f713fd372fa --- /dev/null +++ b/resources/function_help/json/array_get @@ -0,0 +1,8 @@ +{ + "name": "array_get", + "type": "function", + "description": "Returns the Nth value (0 for the first one) of an array.", + "arguments": [ {"arg":"array","description":"an array"}, + {"arg":"index","description":"the index to get (0 based)"}], + "examples": [ { "expression":"array_get(array('a','b','c'),1)", "returns":"'b'"}] +} diff --git a/resources/function_help/json/array_insert b/resources/function_help/json/array_insert new file mode 100644 index 00000000000..4b3904a6c59 --- /dev/null +++ b/resources/function_help/json/array_insert @@ -0,0 +1,9 @@ +{ + "name": "array_insert", + "type": "function", + "description": "Returns an array with the given value added at the given position.", + "arguments": [ {"arg":"array","description":"an array"}, + {"arg":"pos","description":"the position where to add (0 based"}, + {"arg":"value","description":"the value to add"}], + "examples": [ { "expression":"array_insert(array(1,2,3),1,100)", "returns":"array: 1,100,2,3"}] +} diff --git a/resources/function_help/json/array_intersect b/resources/function_help/json/array_intersect new file mode 100644 index 00000000000..609782b6cf3 --- /dev/null +++ b/resources/function_help/json/array_intersect @@ -0,0 +1,8 @@ +{ + "name": "array_intersect", + "type": "function", + "description": "Returns true if any element of array1 exists in array2.", + "arguments": [ {"arg":"array1","description":"an array"}, + {"arg":"array2","description":"an other array"}], + "examples": [ { "expression":"array_intersect(array(1,2,3,4),array(4,0,2,5))", "returns":"true"}] +} diff --git a/resources/function_help/json/array_length b/resources/function_help/json/array_length new file mode 100644 index 00000000000..56ba76cce62 --- /dev/null +++ b/resources/function_help/json/array_length @@ -0,0 +1,7 @@ +{ + "name": "array_length", + "type": "function", + "description": "Returns the number of elements of an array.", + "arguments": [ {"arg":"array","description":"an array"}], + "examples": [ { "expression":"array_length(array(1,2,3))", "returns":"3"}] +} diff --git a/resources/function_help/json/array_prepend b/resources/function_help/json/array_prepend new file mode 100644 index 00000000000..ead644e71bb --- /dev/null +++ b/resources/function_help/json/array_prepend @@ -0,0 +1,8 @@ +{ + "name": "array_prepend", + "type": "function", + "description": "Returns an array with the given value added at the begining.", + "arguments": [ {"arg":"array","description":"an array"}, + {"arg":"value","description":"the value to add"}], + "examples": [ { "expression":"array_prepend(array(1,2,3),0)", "returns":"array: 0,1,2,3"}] +} diff --git a/resources/function_help/json/array_remove_all b/resources/function_help/json/array_remove_all new file mode 100644 index 00000000000..14973638684 --- /dev/null +++ b/resources/function_help/json/array_remove_all @@ -0,0 +1,8 @@ +{ + "name": "array_remove_all", + "type": "function", + "description": "Returns an array with all the entries of the given value removed.", + "arguments": [ {"arg":"array","description":"an array"}, + {"arg":"value","description":"the value to add"}], + "examples": [ { "expression":"array_remove_all(array('a','b','c','b'),'b')", "returns":"array: 'a','c'"}] +} diff --git a/resources/function_help/json/array_remove_at b/resources/function_help/json/array_remove_at new file mode 100644 index 00000000000..3bd54e1e320 --- /dev/null +++ b/resources/function_help/json/array_remove_at @@ -0,0 +1,8 @@ +{ + "name": "array_remove_at", + "type": "function", + "description": "Returns an array with the given index removed.", + "arguments": [ {"arg":"array","description":"an array"}, + {"arg":"pos","description":"the position to remove (0 based"}], + "examples": [ { "expression":"array_remove_at(array(1,2,3),1)", "returns":"array: 1,3"}] +} diff --git a/resources/function_help/json/map b/resources/function_help/json/map new file mode 100644 index 00000000000..a3638fce8cc --- /dev/null +++ b/resources/function_help/json/map @@ -0,0 +1,15 @@ +{ + "name": "map", + "type": "function", + "description": "Returns a map containing all the keys and values passed as pair of parameters.", + "variableLenArguments": true, + "arguments": [ + {"arg":"key1", "syntaxOnly": true}, + {"arg":"value1", "syntaxOnly": true}, + {"arg":"key2", "syntaxOnly": true}, + {"arg":"value2", "syntaxOnly": true}, + {"arg":"key", "descOnly": true, "description":"a key (string)"}, + {"arg":"value", "descOnly": true, "description":"a value"}], + "examples": [ { "expression":"map('1','one','2', 'two')", "returns":"map: 1: 'one', 2: 'two'"} + ] +} diff --git a/resources/function_help/json/map_akeys b/resources/function_help/json/map_akeys new file mode 100644 index 00000000000..b1904a78f3a --- /dev/null +++ b/resources/function_help/json/map_akeys @@ -0,0 +1,7 @@ +{ + "name": "map_akeys", + "type": "function", + "description": "Returns all the keys of a map as an array.", + "arguments": [ {"arg":"map","description":"a map"}], + "examples": [ { "expression":"map_akeys(map('1','one','2','two'))", "returns":"array: '1', '2'"}] +} diff --git a/resources/function_help/json/map_avals b/resources/function_help/json/map_avals new file mode 100644 index 00000000000..4ea549a3a10 --- /dev/null +++ b/resources/function_help/json/map_avals @@ -0,0 +1,7 @@ +{ + "name": "map_avals", + "type": "function", + "description": "Returns all the values of a map as an array.", + "arguments": [ {"arg":"map","description":"a map"}], + "examples": [ { "expression":"map_avals(map('1','one','2','two'))", "returns":"array: 'one', 'two'"}] +} diff --git a/resources/function_help/json/map_concat b/resources/function_help/json/map_concat new file mode 100644 index 00000000000..3130934fde2 --- /dev/null +++ b/resources/function_help/json/map_concat @@ -0,0 +1,12 @@ +{ + "name": "map_concat", + "type": "function", + "description": "Returns a map containing all the entries of the given map. If two maps contain the same key, the value of the second map is taken.", + "variableLenArguments": true, + "arguments": [ + {"arg":"map1", "syntaxOnly": true}, + {"arg":"map2", "syntaxOnly": true}, + {"arg":"map", "descOnly": true, "description":"a map"}], + "examples": [ { "expression":"map_concat(map('1','one', '2','overriden'),map('2','two', '3','three'))", "returns":"map: 1: 'one, 2: 'two', 3: 'three'"} + ] +} diff --git a/resources/function_help/json/map_delete b/resources/function_help/json/map_delete new file mode 100644 index 00000000000..b9a14f00101 --- /dev/null +++ b/resources/function_help/json/map_delete @@ -0,0 +1,8 @@ +{ + "name": "map_delete", + "type": "function", + "description": "Returns a map with the given key and its corresponding value deleted.", + "arguments": [ {"arg":"map","description":"a map"}, + {"arg":"key","description":"the key to delete"}], + "examples": [ { "expression":"map_delete(map('1','one','2','two'),'2')", "returns":"map: 1: 'one'"}] +} diff --git a/resources/function_help/json/map_exist b/resources/function_help/json/map_exist new file mode 100644 index 00000000000..afcb5ebfcc3 --- /dev/null +++ b/resources/function_help/json/map_exist @@ -0,0 +1,8 @@ +{ + "name": "map_exist", + "type": "function", + "description": "Returns true if the given key exists in the map.", + "arguments": [ {"arg":"map","description":"a map"}, + {"arg":"key","description":"the key to lookup"}], + "examples": [ { "expression":"map_exist(map('1','one','2','two'),'3')", "returns":"false"}] +} diff --git a/resources/function_help/json/map_get b/resources/function_help/json/map_get new file mode 100644 index 00000000000..84b3ab4902b --- /dev/null +++ b/resources/function_help/json/map_get @@ -0,0 +1,8 @@ +{ + "name": "map_get", + "type": "function", + "description": "Returns the value of a map, given it's key.", + "arguments": [ {"arg":"map","description":"a map"}, + {"arg":"key","description":"the key to lookup"}], + "examples": [ { "expression":"map_get(map('1','one','2','two'),'2')", "returns":"'two'"}] +} diff --git a/resources/function_help/json/map_insert b/resources/function_help/json/map_insert new file mode 100644 index 00000000000..0d1fc47f9d1 --- /dev/null +++ b/resources/function_help/json/map_insert @@ -0,0 +1,9 @@ +{ + "name": "map_insert", + "type": "function", + "description": "Returns a map with an added key/value.", + "arguments": [ {"arg":"map","description":"a map"}, + {"arg":"key","description":"the key to add"}, + {"arg":"value","description":"the value to add"}], + "examples": [ { "expression":"map_insert(map('1','one'),'3','three')", "returns":"map: 1: 'one', 3: 'three'"}] +} diff --git a/src/core/qgsexpression.cpp b/src/core/qgsexpression.cpp index 5cb63124feb..7e139c5a98d 100644 --- a/src/core/qgsexpression.cpp +++ b/src/core/qgsexpression.cpp @@ -365,6 +365,32 @@ static TVL getTVLValue( const QVariant& value, QgsExpression* parent ) return !qgsDoubleNear( x, 0.0 ) ? True : False; } +static QVariantList getListValue( const QVariant& value, QgsExpression* parent ) +{ + if ( value.type() == QVariant::List || value.type() == QVariant::StringList ) + { + return value.toList(); + } + else + { + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to array" ).arg( value.toString() ) ); + return QVariantList(); + } +} + +static QVariantMap getMapValue( const QVariant& value, QgsExpression* parent ) +{ + if ( value.type() == QVariant::Map ) + { + return value.toMap(); + } + else + { + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to map" ).arg( value.toString() ) ); + return QVariantMap(); + } +} + ////// static QVariant fcnGetVariable( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent ) @@ -3156,6 +3182,150 @@ static QVariant fcnGetLayerProperty( const QVariantList& values, const QgsExpres return QVariant(); } +static QVariant fcnArray( const QVariantList& values, const QgsExpressionContext*, QgsExpression* ) +{ + return values; +} + +static QVariant fcnArrayLength( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + return getListValue( values.at( 0 ), parent ).length(); +} + +static QVariant fcnArrayContains( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + return QVariant( getListValue( values.at( 0 ), parent ).contains( values.at( 1 ) ) ); +} + +static QVariant fcnArrayFind( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + return getListValue( values.at( 0 ), parent ).indexOf( values.at( 1 ) ); +} + +static QVariant fcnArrayGet( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + const QVariantList list = getListValue( values.at( 0 ), parent ); + const int pos = getIntValue( values.at( 1 ), parent ); + if ( pos < 0 || pos >= list.length() ) return QVariant(); + return list.at( pos ); +} + +static QVariant convertToSameType( const QVariant& value, QVariant::Type type ) +{ + QVariant result = value; + result.convert( type ); + return result; +} + +static QVariant fcnArrayAppend( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + QVariantList list = getListValue( values.at( 0 ), parent ); + list.append( values.at( 1 ) ); + return convertToSameType( list , values.at( 0 ).type() ); +} + +static QVariant fcnArrayPrepend( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + QVariantList list = getListValue( values.at( 0 ), parent ); + list.prepend( values.at( 1 ) ); + return convertToSameType( list , values.at( 0 ).type() ); +} + +static QVariant fcnArrayInsert( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + QVariantList list = getListValue( values.at( 0 ), parent ); + list.insert( getIntValue( values.at( 1 ), parent ), values.at( 2 ) ); + return convertToSameType( list , values.at( 0 ).type() ); +} + +static QVariant fcnArrayRemoveAt( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + QVariantList list = getListValue( values.at( 0 ), parent ); + list.removeAt( getIntValue( values.at( 1 ), parent ) ); + return convertToSameType( list , values.at( 0 ).type() ); +} + +static QVariant fcnArrayRemoveAll( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + QVariantList list = getListValue( values.at( 0 ), parent ); + list.removeAll( values.at( 1 ) ); + return convertToSameType( list , values.at( 0 ).type() ); +} + +static QVariant fcnArrayCat( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + QVariantList list; + Q_FOREACH ( QVariant cur, values ) + { + list += getListValue( cur, parent ); + } + return convertToSameType( list , values.at( 0 ).type() ); +} + +static QVariant fcnArrayIntersect( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + const QVariantList array1 = getListValue( values.at( 0 ), parent ); + Q_FOREACH ( QVariant cur, getListValue( values.at( 1 ), parent ) ) if ( array1.contains( cur ) ) return QVariant( true ); + return QVariant( false ); +} + +static QVariant fcnMap( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + QVariantMap result; + for ( int i = 0; i + 1 < values.length(); i += 2 ) + { + result.insert( getStringValue( values.at( i ), parent ), values.at( i + 1 ) ); + } + return result; +} + +static QVariant fcnMapGet( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + return getMapValue( values.at( 0 ), parent ).value( values.at( 1 ).toString() ); +} + +static QVariant fcnMapExist( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + return getMapValue( values.at( 0 ), parent ).contains( values.at( 1 ).toString() ); +} + +static QVariant fcnMapDelete( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + QVariantMap map = getMapValue( values.at( 0 ), parent ); + map.remove( values.at( 1 ).toString() ); + return map; +} + +static QVariant fcnMapInsert( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + QVariantMap map = getMapValue( values.at( 0 ), parent ); + map.insert( values.at( 1 ).toString(), values.at( 2 ) ); + return map; +} + +static QVariant fcnMapConcat( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + QVariantMap result; + Q_FOREACH ( QVariant cur, values ) + { + const QVariantMap curMap = getMapValue( cur, parent ); + for ( QVariantMap::const_iterator it = curMap.constBegin(); it != curMap.constEnd(); ++it ) + result.insert( it.key(), it.value() ); + } + return result; +} + +static QVariant fcnMapAKeys( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + return QStringList( getMapValue( values.at( 0 ), parent ).keys() ); +} + +static QVariant fcnMapAVals( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + return getMapValue( values.at( 0 ), parent ).values(); +} + + bool QgsExpression::registerFunction( QgsExpression::Function* function, bool transferOwnership ) { int fnIdx = functionIndex( function->name() ); @@ -3491,6 +3661,30 @@ const QList& QgsExpression::Functions() // feature request << new StaticFunction( "eval", 1, fcnEval, "General", QString(), true, QStringList( QgsFeatureRequest::AllAttributes ) ) << new StaticFunction( "attribute", 2, fcnAttribute, "Record", QString(), false, QStringList( QgsFeatureRequest::AllAttributes ) ) + + // functions for arrays + << new StaticFunction( "array", -1, fcnArray, "Arrays" ) + << new StaticFunction( "array_length", 1, fcnArrayLength, "Arrays" ) + << new StaticFunction( "array_contains", ParameterList() << Parameter( "array" ) << Parameter( "value" ), fcnArrayContains, "Arrays" ) + << new StaticFunction( "array_find", ParameterList() << Parameter( "array" ) << Parameter( "value" ), fcnArrayFind, "Arrays" ) + << new StaticFunction( "array_get", ParameterList() << Parameter( "array" ) << Parameter( "pos" ), fcnArrayGet, "Arrays" ) + << new StaticFunction( "array_append", ParameterList() << Parameter( "array" ) << Parameter( "value" ), fcnArrayAppend, "Arrays" ) + << new StaticFunction( "array_prepend", ParameterList() << Parameter( "array" ) << Parameter( "value" ), fcnArrayPrepend, "Arrays" ) + << new StaticFunction( "array_insert", ParameterList() << Parameter( "array" ) << Parameter( "pos" ) << Parameter( "value" ), fcnArrayInsert, "Arrays" ) + << new StaticFunction( "array_remove_at", ParameterList() << Parameter( "array" ) << Parameter( "pos" ), fcnArrayRemoveAt, "Arrays" ) + << new StaticFunction( "array_remove_all", ParameterList() << Parameter( "array" ) << Parameter( "value" ), fcnArrayRemoveAll, "Arrays" ) + << new StaticFunction( "array_cat", -1, fcnArrayCat, "Arrays" ) + << new StaticFunction( "array_intersect", ParameterList() << Parameter( "array1" ) << Parameter( "array2" ), fcnArrayIntersect, "Arrays" ) + + //functions for maps + << new StaticFunction( "map", -1, fcnMap, "Maps" ) + << new StaticFunction( "map_get", ParameterList() << Parameter( "map" ) << Parameter( "key" ), fcnMapGet, "Maps" ) + << new StaticFunction( "map_exist", ParameterList() << Parameter( "map" ) << Parameter( "key" ), fcnMapExist, "Maps" ) + << new StaticFunction( "map_delete", ParameterList() << Parameter( "map" ) << Parameter( "key" ), fcnMapDelete, "Maps" ) + << new StaticFunction( "map_insert", ParameterList() << Parameter( "map" ) << Parameter( "key" ) << Parameter( "value" ), fcnMapInsert, "Maps" ) + << new StaticFunction( "map_concat", -1, fcnMapConcat, "Maps" ) + << new StaticFunction( "map_akeys", ParameterList() << Parameter( "map" ), fcnMapAKeys, "Maps" ) + << new StaticFunction( "map_avals", ParameterList() << Parameter( "map" ), fcnMapAVals, "Maps" ) ; QgsExpressionContextUtils::registerContextFunctions(); @@ -5088,7 +5282,7 @@ QString QgsExpression::formatPreviewString( const QVariant& value ) break; } } - return tr( "<list: %1>" ).arg( listStr ); + return tr( "<array: %1>" ).arg( listStr ); } else { diff --git a/src/gui/editorwidgets/qgskeyvaluewidgetfactory.cpp b/src/gui/editorwidgets/qgskeyvaluewidgetfactory.cpp index 70c75d15000..c1d08f30e8e 100644 --- a/src/gui/editorwidgets/qgskeyvaluewidgetfactory.cpp +++ b/src/gui/editorwidgets/qgskeyvaluewidgetfactory.cpp @@ -92,5 +92,5 @@ Qt::AlignmentFlag QgsKeyValueWidgetFactory::alignmentFlag( QgsVectorLayer *vl, i unsigned int QgsKeyValueWidgetFactory::fieldScore( const QgsVectorLayer* vl, int fieldIdx ) const { const QgsField field = vl->fields().field( fieldIdx ); - return field.type() == QVariant::Map ? 20 : 0; + return field.type() == QVariant::Map && field.subType() != QVariant::Invalid ? 20 : 0; } \ No newline at end of file diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 3ef8bbc1136..692cfe86b4d 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -2148,6 +2148,189 @@ class TestQgsExpression: public QObject QCOMPARE( v4, QVariant( "test value" ) ); } + void eval_string_array() + { + QgsFeature f( 100 ); + QgsFields fields; + fields.append( QgsField( "col1" ) ); + fields.append( QgsField( "strings", QVariant::StringList, "string[]", 0, 0, QString(), QVariant::String ) ); + f.setFields( fields, true ); + f.setAttribute( "col1", QString( "test value" ) ); + QStringList array; + array << "one" << "two"; + f.setAttribute( "strings", array ); + + QgsExpressionContext context = QgsExpressionContextUtils::createFeatureBasedContext( f, QgsFields() ); + + QVariantList builderExpected; + QCOMPARE( QgsExpression( "array()" ).evaluate( &context ), QVariant( ) ); + builderExpected << "hello"; + QCOMPARE( QgsExpression( "array('hello')" ).evaluate( &context ), QVariant( builderExpected ) ); + builderExpected << "world"; + QCOMPARE( QgsExpression( "array('hello', 'world')" ).evaluate( &context ), QVariant( builderExpected ) ); + + QCOMPARE( QgsExpression( "array_length(\"strings\")" ).evaluate( &context ), QVariant( 2 ) ); + + QCOMPARE( QgsExpression( "array_contains(\"strings\", 'two')" ).evaluate( &context ), QVariant( true ) ); + QCOMPARE( QgsExpression( "array_contains(\"strings\", 'three')" ).evaluate( &context ), QVariant( false ) ); + + QCOMPARE( QgsExpression( "array_find(\"strings\", 'two')" ).evaluate( &context ), QVariant( 1 ) ); + QCOMPARE( QgsExpression( "array_find(\"strings\", 'three')" ).evaluate( &context ), QVariant( -1 ) ); + + QCOMPARE( QgsExpression( "array_get(\"strings\", 1)" ).evaluate( &context ), QVariant( "two" ) ); + QCOMPARE( QgsExpression( "array_get(\"strings\", 2)" ).evaluate( &context ), QVariant() ); + QCOMPARE( QgsExpression( "array_get(\"strings\", -1)" ).evaluate( &context ), QVariant() ); + + QStringList appendExpected = array; + appendExpected << "three"; + QCOMPARE( QgsExpression( "array_append(\"strings\", 'three')" ).evaluate( &context ), QVariant( appendExpected ) ); + + QStringList prependExpected = array; + prependExpected.prepend( "zero" ); + QCOMPARE( QgsExpression( "array_prepend(\"strings\", 'zero')" ).evaluate( &context ), QVariant( prependExpected ) ); + + QStringList insertExpected = array; + insertExpected.insert( 1, "one and a half" ); + QCOMPARE( QgsExpression( "array_insert(\"strings\", 1, 'one and a half')" ).evaluate( &context ), QVariant( insertExpected ) ); + + QStringList removeAtExpected = array; + removeAtExpected.removeAt( 0 ); + QCOMPARE( QgsExpression( "array_remove_at(\"strings\", 0)" ).evaluate( &context ), QVariant( removeAtExpected ) ); + + QStringList removeAllExpected; + removeAllExpected << "a" << "b" << "d"; + QCOMPARE( QgsExpression( "array_remove_all(array('a', 'b', 'c', 'd', 'c'), 'c')" ).evaluate( &context ), QVariant( removeAllExpected ) ); + + QStringList concatExpected = array; + concatExpected << "a" << "b" << "c"; + QCOMPARE( QgsExpression( "array_cat(\"strings\", array('a', 'b'), array('c'))" ).evaluate( &context ), QVariant( concatExpected ) ); + + QCOMPARE( QgsExpression( "array_intersect(array('1', '2', '3', '4'), array('4', '0', '2', '5'))" ).evaluate( &context ), QVariant( true ) ); + QCOMPARE( QgsExpression( "array_intersect(array('1', '2', '3', '4'), array('0', '5'))" ).evaluate( &context ), QVariant( false ) ); + } + + void eval_int_array() + { + QgsFeature f( 100 ); + QgsFields fields; + fields.append( QgsField( "col1" ) ); + fields.append( QgsField( "ints", QVariant::List, "int[]", 0, 0, QString(), QVariant::Int ) ); + f.setFields( fields, true ); + f.setAttribute( "col1", QString( "test value" ) ); + QVariantList array; + array << 1 << -2; + f.setAttribute( "ints", array ); + + QgsExpressionContext context = QgsExpressionContextUtils::createFeatureBasedContext( f, QgsFields() ); + + QVariantList builderExpected; + builderExpected << 1; + QCOMPARE( QgsExpression( "array(1)" ).evaluate( &context ), QVariant( builderExpected ) ); + builderExpected << 2; + QCOMPARE( QgsExpression( "array(1, 2)" ).evaluate( &context ), QVariant( builderExpected ) ); + + QCOMPARE( QgsExpression( "array_contains(\"ints\", 1)" ).evaluate( &context ), QVariant( true ) ); + QCOMPARE( QgsExpression( "array_contains(\"ints\", 2)" ).evaluate( &context ), QVariant( false ) ); + + QCOMPARE( QgsExpression( "array_find(\"ints\", -2)" ).evaluate( &context ), QVariant( 1 ) ); + QCOMPARE( QgsExpression( "array_find(\"ints\", 3)" ).evaluate( &context ), QVariant( -1 ) ); + + QCOMPARE( QgsExpression( "array_get(\"ints\", 1)" ).evaluate( &context ), QVariant( -2 ) ); + QCOMPARE( QgsExpression( "array_get(\"ints\", 2)" ).evaluate( &context ), QVariant() ); + QCOMPARE( QgsExpression( "array_get(\"ints\", -1)" ).evaluate( &context ), QVariant() ); + + QVariantList appendExpected = array; + appendExpected << 3; + QCOMPARE( QgsExpression( "array_append(\"ints\", 3)" ).evaluate( &context ), QVariant( appendExpected ) ); + + QVariantList prependExpected = array; + prependExpected.prepend( 0 ); + QCOMPARE( QgsExpression( "array_prepend(\"ints\", 0)" ).evaluate( &context ), QVariant( prependExpected ) ); + + QVariantList insertExpected = array; + insertExpected.insert( 1, 2 ); + QCOMPARE( QgsExpression( "array_insert(\"ints\", 1, 2)" ).evaluate( &context ), QVariant( insertExpected ) ); + + QVariantList removeAtExpected = array; + removeAtExpected.removeAt( 0 ); + QCOMPARE( QgsExpression( "array_remove_at(\"ints\", 0)" ).evaluate( &context ), QVariant( removeAtExpected ) ); + + QVariantList removeAllExpected; + removeAllExpected << 1 << 2 << 4; + QCOMPARE( QgsExpression( "array_remove_all(array(1, 2, 3, 4, 3), 3)" ).evaluate( &context ), QVariant( removeAllExpected ) ); + QCOMPARE( QgsExpression( "array_remove_all(array(1, 2, 3, 4, 3), '3')" ).evaluate( &context ), QVariant( removeAllExpected ) ); + + QVariantList concatExpected = array; + concatExpected << 56 << 57; + QCOMPARE( QgsExpression( "array_cat(\"ints\", array(56, 57))" ).evaluate( &context ), QVariant( concatExpected ) ); + + QCOMPARE( QgsExpression( "array_intersect(array(1, 2, 3, 4), array(4, 0, 2, 5))" ).evaluate( &context ), QVariant( true ) ); + QCOMPARE( QgsExpression( "array_intersect(array(1, 2, 3, 4), array(0, 5))" ).evaluate( &context ), QVariant( false ) ); + + QgsExpression badArray( "array_get('not an array', 0)" ); + QCOMPARE( badArray.evaluate( &context ), QVariant() ); + QVERIFY( badArray.hasEvalError() ); + QCOMPARE( badArray.evalErrorString(), QString( "Cannot convert 'not an array' to array" ) ); + } + + void eval_map() + { + QgsFeature f( 100 ); + QgsFields fields; + fields.append( QgsField( "col1" ) ); + fields.append( QgsField( "map", QVariant::Map, "map", 0, 0, QString(), QVariant::String ) ); + f.setFields( fields, true ); + f.setAttribute( "col1", QString( "test value" ) ); + QVariantMap map; + map["1"] = "one"; + map["2"] = "two"; + f.setAttribute( "map", map ); + + QgsExpressionContext context = QgsExpressionContextUtils::createFeatureBasedContext( f, QgsFields() ); + + QVariantMap builderExpected; + QCOMPARE( QgsExpression( "map()" ).evaluate( &context ), QVariant( ) ); + builderExpected["1"] = "hello"; + QCOMPARE( QgsExpression( "map('1', 'hello')" ).evaluate( &context ), QVariant( builderExpected ) ); + builderExpected["2"] = "world"; + QCOMPARE( QgsExpression( "map('1', 'hello', '2', 'world')" ).evaluate( &context ), QVariant( builderExpected ) ); + QCOMPARE( QgsExpression( "map('1', 'hello', '2', 'world', 'ignoredOddParam')" ).evaluate( &context ), QVariant( builderExpected ) ); + + QCOMPARE( QgsExpression( "map_get(\"map\", '2')" ).evaluate( &context ), QVariant( "two" ) ); + QCOMPARE( QgsExpression( "map_get(\"map\", '3')" ).evaluate( &context ), QVariant() ); + + QCOMPARE( QgsExpression( "map_exist(\"map\", '2')" ).evaluate( &context ), QVariant( true ) ); + QCOMPARE( QgsExpression( "map_exist(\"map\", '3')" ).evaluate( &context ), QVariant( false ) ); + + QVariantMap deleteExpected = map; + deleteExpected.remove( "1" ); + QCOMPARE( QgsExpression( "map_delete(\"map\", '1')" ).evaluate( &context ), QVariant( deleteExpected ) ); + QCOMPARE( QgsExpression( "map_delete(\"map\", '3')" ).evaluate( &context ), QVariant( map ) ); + + QVariantMap insertExpected = map; + insertExpected.insert( "3", "three" ); + QCOMPARE( QgsExpression( "map_insert(\"map\", '3', 'three')" ).evaluate( &context ), QVariant( insertExpected ) ); + + QVariantMap concatExpected; + concatExpected["1"] = "one"; + concatExpected["2"] = "two"; + concatExpected["3"] = "three"; + QCOMPARE( QgsExpression( "map_concat(map('1', 'one', '2', 'overriden by next map'), map('2', 'two', '3', 'three'))" ).evaluate( &context ), QVariant( concatExpected ) ); + + QStringList keysExpected; + keysExpected << "1" << "2"; + QCOMPARE( QgsExpression( "map_akeys(\"map\")" ).evaluate( &context ), QVariant( keysExpected ) ); + + QVariantList valuesExpected; + valuesExpected << "one" << "two"; + QCOMPARE( QgsExpression( "map_avals(\"map\")" ).evaluate( &context ), QVariant( valuesExpected ) ); + + QgsExpression badMap( "map_get('not a map', '0')" ); + QCOMPARE( badMap.evaluate( &context ), QVariant() ); + QVERIFY( badMap.hasEvalError() ); + QCOMPARE( badMap.evalErrorString(), QString( "Cannot convert 'not a map' to map" ) ); + } + void expression_from_expression_data() { QTest::addColumn( "string" ); @@ -2346,12 +2529,12 @@ class TestQgsExpression: public QObject QVariantList list; list << 1 << 2 << 3; - QCOMPARE( QgsExpression::formatPreviewString( QVariant( list ) ), QString( "<list: 1, 2, 3>" ) ); + QCOMPARE( QgsExpression::formatPreviewString( QVariant( list ) ), QString( "<array: 1, 2, 3>" ) ); QStringList stringList; stringList << "One" << "Two" << "A very long string that is going to be truncated"; QCOMPARE( QgsExpression::formatPreviewString( QVariant( stringList ) ), - QString( "<list: 'One', 'Two', 'A very long string that is going to be trunca...>" ) ); + QString( "<array: 'One', 'Two', 'A very long string that is going to be trunca...>" ) ); } };