[expressions] Add 'reverse' variant for reversing a string

Allows using a string argument for the reverse function, which
returns a reversed version of the string
This commit is contained in:
Nyall Dawson 2025-04-04 11:17:22 +10:00
parent 117a69e465
commit 59d6850aac
3 changed files with 62 additions and 36 deletions

View File

@ -1,15 +1,30 @@
{
"name": "reverse",
"type": "function",
"groups": ["GeometryGroup"],
"description": "Reverses the direction of a line string by reversing the order of its vertices.",
"arguments": [{
"arg": "geometry",
"description": "a geometry"
}],
"examples": [{
"expression": "geom_to_wkt(reverse(geom_from_wkt('LINESTRING(0 0, 1 1, 2 2)')))",
"returns": "'LINESTRING(2 2, 1 1, 0 0)'"
"groups": ["String", "GeometryGroup"],
"description": "Reverses the direction of a line string or reverses a string of text.",
"variants": [{
"variant": "String variant",
"variant_description": "Reverses the order of characters in a string.",
"arguments": [{
"arg": "string",
"description": "string to reverse"
}],
"examples": [{
"expression": "reverse('hello')",
"returns": "'olleh'"
}]
}, {
"variant": "Geometry variant",
"variant_description": "Reverses the direction of a line string by reversing the order of its vertices.",
"arguments": [{
"arg": "geometry",
"description": "a geometry"
}],
"examples": [{
"expression": "geom_to_wkt(reverse(geom_from_wkt('LINESTRING(0 0, 1 1, 2 2)')))",
"returns": "'LINESTRING(2 2, 1 1, 0 0)'"
}]
}],
"tags": ["direction", "order", "vertices", "reverses", "line", "reversing"]
}

View File

@ -5252,38 +5252,48 @@ static QVariant fcnDifference( const QVariantList &values, const QgsExpressionCo
static QVariant fcnReverse( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QgsGeometry fGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );
if ( fGeom.isNull() )
if ( QgsVariantUtils::isNull( values.at( 0 ) ) )
return QVariant();
QVariant result;
if ( !fGeom.isMultipart() )
{
const QgsCurve *curve = qgsgeometry_cast<const QgsCurve * >( fGeom.constGet() );
if ( !curve )
return QVariant();
// two variants, one for geometry, one for string
QgsCurve *reversed = curve->reversed();
result = reversed ? QVariant::fromValue( QgsGeometry( reversed ) ) : QVariant();
}
else
QgsGeometry fGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent, true );
if ( !fGeom.isNull() )
{
const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection *>( fGeom.constGet() );
std::unique_ptr< QgsGeometryCollection > reversed( collection->createEmptyWithSameType() );
for ( int i = 0; i < collection->numGeometries(); ++i )
QVariant result;
if ( !fGeom.isMultipart() )
{
if ( const QgsCurve *curve = qgsgeometry_cast<const QgsCurve * >( collection->geometryN( i ) ) )
{
reversed->addGeometry( curve->reversed() );
}
else
{
reversed->addGeometry( collection->geometryN( i )->clone() );
}
const QgsCurve *curve = qgsgeometry_cast<const QgsCurve * >( fGeom.constGet() );
if ( !curve )
return QVariant();
QgsCurve *reversed = curve->reversed();
result = reversed ? QVariant::fromValue( QgsGeometry( reversed ) ) : QVariant();
}
result = reversed ? QVariant::fromValue( QgsGeometry( std::move( reversed ) ) ) : QVariant();
else
{
const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection *>( fGeom.constGet() );
std::unique_ptr< QgsGeometryCollection > reversed( collection->createEmptyWithSameType() );
for ( int i = 0; i < collection->numGeometries(); ++i )
{
if ( const QgsCurve *curve = qgsgeometry_cast<const QgsCurve * >( collection->geometryN( i ) ) )
{
reversed->addGeometry( curve->reversed() );
}
else
{
reversed->addGeometry( collection->geometryN( i )->clone() );
}
}
result = reversed ? QVariant::fromValue( QgsGeometry( std::move( reversed ) ) ) : QVariant();
}
return result;
}
return result;
//fall back to string variant
QString string = QgsExpressionUtils::getStringValue( values.at( 0 ), parent );
std::reverse( string.begin(), string.end() );
return string;
}
static QVariant fcnExteriorRing( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
@ -9056,7 +9066,7 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< new QgsStaticExpressionFunction( QStringLiteral( "point_on_surface" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ), fcnPointOnSurface, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "pole_of_inaccessibility" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "tolerance" ) ), fcnPoleOfInaccessibility, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "reverse" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ), fcnReverse, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "reverse" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ), fcnReverse, { QStringLiteral( "String" ), QStringLiteral( "GeometryGroup" ) } )
<< new QgsStaticExpressionFunction( QStringLiteral( "exterior_ring" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ), fcnExteriorRing, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "interior_ring_n" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "index" ) ),

View File

@ -1242,7 +1242,6 @@ class TestQgsExpression : public QObject
QTest::newRow( "end_point multipoint" ) << "geom_to_wkt(end_point(geom_from_wkt('MULTIPOINT((3 3), (1 1), (2 2))')))" << false << QVariant( "Point (2 2)" );
QTest::newRow( "end_point line" ) << "geom_to_wkt(end_point(geom_from_wkt('LINESTRING(4 1, 1 1, 2 2)')))" << false << QVariant( "Point (2 2)" );
QTest::newRow( "end_point polygon" ) << "geom_to_wkt(end_point(geom_from_wkt('POLYGON((-1 -1, 4 0, 4 2, 0 2, -1 -1))')))" << false << QVariant( "Point (-1 -1)" );
QTest::newRow( "reverse not geom" ) << "reverse('g')" << true << QVariant();
QTest::newRow( "reverse null" ) << "reverse(NULL)" << false << QVariant();
QTest::newRow( "reverse point" ) << "reverse(geom_from_wkt('POINT(1 2)'))" << false << QVariant();
QTest::newRow( "reverse polygon" ) << "reverse(geom_from_wkt('POLYGON((-1 -1, 4 0, 4 2, 0 2, -1 -1))'))" << false << QVariant();
@ -1696,6 +1695,8 @@ class TestQgsExpression : public QObject
QTest::newRow( "regexp_replace non greedy" ) << "regexp_replace('HeLLo','(?<=H).*?L', '-')" << false << QVariant( "H-Lo" );
QTest::newRow( "regexp_replace cap group" ) << "regexp_replace('HeLLo','(eL)', 'x\\\\1x')" << false << QVariant( "HxeLxLo" );
QTest::newRow( "regexp_replace invalid" ) << "regexp_replace('HeLLo','[[[', '-')" << true << QVariant();
QTest::newRow( "reverse string" ) << "reverse('HeLLo')" << false << QVariant( "oLLeH" );
QTest::newRow( "reverse empty string" ) << "reverse('')" << false << QVariant( "" );
QTest::newRow( "substr" ) << "substr('HeLLo', 3,2)" << false << QVariant( "LL" );
QTest::newRow( "substr named parameters" ) << "substr(string:='HeLLo',start:=3,length:=2)" << false << QVariant( "LL" );
QTest::newRow( "substr negative start" ) << "substr('HeLLo', -4)" << false << QVariant( "eLLo" );