Skip to content

Commit

Permalink
Merge pull request #51260 from elpaso/bugfix-gh22172-sld-else-export
Browse files Browse the repository at this point in the history
SLD: <se:ElseFilter/> nor working when styling data with sld
  • Loading branch information
elpaso committed Dec 28, 2022
2 parents 29fd311 + 74dfc9b commit 85da91e
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 17 deletions.
9 changes: 9 additions & 0 deletions python/core/auto_generated/qgsogcutils.sip.in
Expand Up @@ -184,6 +184,15 @@ with default values for the geometry name, srs name, honourAsisOrientation and i
otherwise null QDomElement
%End

static QDomElement elseFilterExpression( QDomDocument &doc );
%Docstring
Creates an ElseFilter from ``doc``

:return: valid OGC ElseFilter QDomElement

.. versionadded:: 3.28
%End

static QDomElement expressionToOgcExpression( const QgsExpression &exp,
QDomDocument &doc,
QgsOgcUtils::GMLVersion gmlVersion,
Expand Down
6 changes: 6 additions & 0 deletions src/core/qgsogcutils.cpp
Expand Up @@ -44,6 +44,7 @@
#define GML32_NAMESPACE QStringLiteral( "http://www.opengis.net/gml/3.2" )
#define OGC_NAMESPACE QStringLiteral( "http://www.opengis.net/ogc" )
#define FES_NAMESPACE QStringLiteral( "http://www.opengis.net/fes/2.0" )
#define SE_NAMESPACE QStringLiteral( "http://www.opengis.net/se" )

QgsOgcUtilsExprToFilter::QgsOgcUtilsExprToFilter( QDomDocument &doc,
QgsOgcUtils::GMLVersion gmlVersion,
Expand Down Expand Up @@ -1888,6 +1889,11 @@ QDomElement QgsOgcUtils::expressionToOgcExpression( const QgsExpression &exp, QD
QStringLiteral( "geometry" ), QString(), false, false, errorMessage, requiresFilterElement );
}

QDomElement QgsOgcUtils::elseFilterExpression( QDomDocument &doc )
{
return doc.createElementNS( SE_NAMESPACE, QStringLiteral( "se:ElseFilter" ) );
}


QDomElement QgsOgcUtils::expressionToOgcFilter( const QgsExpression &expression,
QDomDocument &doc,
Expand Down
7 changes: 7 additions & 0 deletions src/core/qgsogcutils.h
Expand Up @@ -220,6 +220,13 @@ class CORE_EXPORT QgsOgcUtils
static QDomElement expressionToOgcExpression( const QgsExpression &exp, QDomDocument &doc, QString *errorMessage = nullptr,
bool requiresFilterElement = false );

/**
* Creates an ElseFilter from \a doc
* \returns valid OGC ElseFilter QDomElement
* \since QGIS 3.28
*/
static QDomElement elseFilterExpression( QDomDocument &doc );

/**
* Creates an OGC expression XML element from the \a exp expression.
* \returns valid OGC expression QDomElement on success or a valid \verbatim <Filter> \endverbatim QDomElement when \a requiresFilterElement is set.
Expand Down
4 changes: 1 addition & 3 deletions src/core/symbology/qgscategorizedsymbolrenderer.cpp
Expand Up @@ -147,9 +147,7 @@ void QgsRendererCategory::toSld( QDomDocument &doc, QDomElement &element, QVaria
QString filterFunc;
if ( QgsVariantUtils::isNull( mValue ) || mValue.toString().isEmpty() )
{
filterFunc = QStringLiteral( "%1 = '%2' or %1 is null" )
.arg( attrName.replace( '\"', QLatin1String( "\"\"" ) ).append( '"' ).prepend( '"' ),
mValue.toString().replace( '\'', QLatin1String( "''" ) ) );
filterFunc = QStringLiteral( "ELSE" );
}
else
{
Expand Down
1 change: 1 addition & 0 deletions src/core/symbology/qgsrenderer.cpp
Expand Up @@ -265,6 +265,7 @@ QgsFeatureRenderer *QgsFeatureRenderer::loadSld( const QDomNode &node, QgsWkbTyp
{
// rule has filter or min/max scale denominator, use the RuleRenderer
if ( ruleChildElem.localName() == QLatin1String( "Filter" ) ||
ruleChildElem.localName() == QLatin1String( "ElseFilter" ) ||
ruleChildElem.localName() == QLatin1String( "MinScaleDenominator" ) ||
ruleChildElem.localName() == QLatin1String( "MaxScaleDenominator" ) )
{
Expand Down
5 changes: 5 additions & 0 deletions src/core/symbology/qgsrulebasedrenderer.cpp
Expand Up @@ -864,6 +864,11 @@ QgsRuleBasedRenderer::Rule *QgsRuleBasedRenderer::Rule::createFromSld( QDomEleme
delete filter;
}
}
else if ( childElem.localName() == QLatin1String( "ElseFilter" ) )
{
filterExp = QLatin1String( "ELSE" );

}
else if ( childElem.localName() == QLatin1String( "MinScaleDenominator" ) )
{
bool ok;
Expand Down
28 changes: 19 additions & 9 deletions src/core/symbology/qgssymbollayerutils.cpp
Expand Up @@ -2980,17 +2980,27 @@ bool QgsSymbolLayerUtils::createExpressionElement( QDomDocument &doc, QDomElemen

bool QgsSymbolLayerUtils::createFunctionElement( QDomDocument &doc, QDomElement &element, const QString &function )
{
// let's use QgsExpression to generate the SLD for the function
const QgsExpression expr( function );
if ( expr.hasParserError() )
// else rule is not a valid expression
if ( function == QLatin1String( "ELSE" ) )
{
element.appendChild( doc.createComment( "Parser Error: " + expr.parserErrorString() + " - Expression was: " + function ) );
return false;
}
const QDomElement filterElem = QgsOgcUtils::expressionToOgcFilter( expr, doc );
if ( !filterElem.isNull() )
const QDomElement filterElem = QgsOgcUtils::elseFilterExpression( doc );
element.appendChild( filterElem );
return true;
return true;
}
else
{
// let's use QgsExpression to generate the SLD for the function
const QgsExpression expr( function );
if ( expr.hasParserError() )
{
element.appendChild( doc.createComment( "Parser Error: " + expr.parserErrorString() + " - Expression was: " + function ) );
return false;
}
const QDomElement filterElem = QgsOgcUtils::expressionToOgcFilter( expr, doc );
if ( !filterElem.isNull() )
element.appendChild( filterElem );
return true;
}
}

bool QgsSymbolLayerUtils::functionFromSldElement( QDomElement &element, QString &function )
Expand Down
51 changes: 51 additions & 0 deletions tests/src/core/testqgsrulebasedrenderer.cpp
Expand Up @@ -15,6 +15,7 @@
#include "qgstest.h"
#include <QDomDocument>
#include <QFile>
#include <QTemporaryFile>
//header for class being tested
#include <qgsrulebasedrenderer.h>

Expand Down Expand Up @@ -1212,6 +1213,56 @@ class TestQgsRuleBasedRenderer: public QgsTest
QCOMPARE( exp, "(TRUE) AND ((\"field_name\" = 6) AND (@map_scale >= 2000))" );
}

void testElseRuleSld()
{
QgsRuleBasedRenderer::Rule *rootRule = new QgsRuleBasedRenderer::Rule( nullptr );
std::unique_ptr< QgsRuleBasedRenderer > renderer = std::make_unique< QgsRuleBasedRenderer >( rootRule );

QgsRuleBasedRenderer::Rule *rule1 = new QgsRuleBasedRenderer::Rule( QgsSymbol::defaultSymbol( QgsWkbTypes::GeometryType::PointGeometry ), 0, 0, "\"field_name\" = 1" );
QgsRuleBasedRenderer::Rule *rule2 = new QgsRuleBasedRenderer::Rule( QgsSymbol::defaultSymbol( QgsWkbTypes::GeometryType::PointGeometry ), 0, 0, "\"field_name\" = 6" );
QgsRuleBasedRenderer::Rule *ruleElse = new QgsRuleBasedRenderer::Rule( QgsSymbol::defaultSymbol( QgsWkbTypes::GeometryType::PointGeometry ), 0, 0, "ELSE" );

Q_ASSERT( ruleElse->isElse() );

rootRule->appendChild( rule1 );
rootRule->appendChild( rule2 );
rootRule->appendChild( ruleElse );

bool ok;

QString exp = renderer->legendKeyToExpression( ruleElse->ruleKey(), nullptr, ok );
QVERIFY( ok );
QCOMPARE( exp, "NOT ((\"field_name\" = 1) OR (\"field_name\" = 6))" );

QgsFields fields;
std::unique_ptr<QgsVectorLayer> vl = std::make_unique<QgsVectorLayer>( QStringLiteral( "Point?crs=epsg:4326&field=field_name:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
vl->setRenderer( renderer.release() );
QString error;
QDomDocument dom;
vl->exportSldStyle( dom, error );

const QString sld = dom.toString();

Q_ASSERT( sld.contains( QStringLiteral( "<se:ElseFilter" ) ) );

QTemporaryFile sldFile;
sldFile.open();
sldFile.write( sld.toUtf8() );
sldFile.close();

// Recreate the test layer for round trip test
vl = std::make_unique<QgsVectorLayer>( QStringLiteral( "Point?crs=epsg:4326&field=field_name:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
vl->loadSldStyle( sldFile.fileName(), ok );

Q_ASSERT( ok );

QgsRuleBasedRenderer *renderer2 = static_cast<QgsRuleBasedRenderer *>( vl->renderer() );
ruleElse = renderer2->rootRule()->children().last();
Q_ASSERT( ruleElse->isElse() );

}


private:
void xml2domElement( const QString &testFile, QDomDocument &doc )
{
Expand Down
7 changes: 2 additions & 5 deletions tests/src/python/test_qgssymbollayer_createsld.py
Expand Up @@ -1220,11 +1220,8 @@ def testRuleBaseEmptyFilter(self):

# get the third rule
rule = root.elementsByTagName('se:Rule').item(2).toElement()
filter = rule.elementsByTagName('Filter').item(0).toElement()
filter = filter.firstChild().toElement()
self.assertEqual("ogc:Or", filter.nodeName())
self.assertEqual(1, filter.elementsByTagName('ogc:PropertyIsEqualTo').size())
self.assertEqual(1, filter.elementsByTagName('ogc:PropertyIsNull').size())
filter = rule.elementsByTagName('ElseFilter').item(0).toElement()
self.assertEqual("se:ElseFilter", filter.nodeName())

def assertScaleDenominator(self, root, expectedMinScale, expectedMaxScale, index=0):
rule = root.elementsByTagName('se:Rule').item(index).toElement()
Expand Down

0 comments on commit 85da91e

Please sign in to comment.