Skip to content

Commit 51ae8e9

Browse files
authored
Merge pull request #7195 from elpaso/bugfix-18981-export-qlr-crash-2
[bugfix] Crash when exporting (invalid) legend to qlr Fixes #18981
2 parents 22a98fb + b307f39 commit 51ae8e9

6 files changed

+265
-3
lines changed

src/core/layertree/qgslayertreegroup.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ QList<QgsLayerTreeLayer *> QgsLayerTreeGroup::findLayers() const
221221
QList<QgsLayerTreeLayer *> list;
222222
Q_FOREACH ( QgsLayerTreeNode *child, mChildren )
223223
{
224-
if ( QgsLayerTree::isLayer( child ) )
224+
if ( QgsLayerTree::isLayer( child ) && QgsLayerTree::toLayer( child )->layer( ) )
225225
list << QgsLayerTree::toLayer( child );
226226
else if ( QgsLayerTree::isGroup( child ) )
227227
list << QgsLayerTree::toGroup( child )->findLayers();

src/core/qgslayerdefinition.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
* (at your option) any later version. *
1313
* *
1414
***************************************************************************/
15-
#include <QDomNode>
1615
#include <QFileInfo>
1716
#include <QFile>
1817
#include <QDir>
@@ -214,6 +213,11 @@ bool QgsLayerDefinition::exportLayerDefinition( QDomDocument doc, const QList<Qg
214213
QList<QgsLayerTreeLayer *> layers = root->findLayers();
215214
Q_FOREACH ( QgsLayerTreeLayer *layer, layers )
216215
{
216+
if ( ! layer->layer() )
217+
{
218+
QgsDebugMsgLevel( QStringLiteral( "Not a valid map layer: skipping %1" ).arg( layer->name( ) ), 4 );
219+
continue;
220+
}
217221
QDomElement layerelm = doc.createElement( QStringLiteral( "maplayer" ) );
218222
layer->layer()->writeLayerXml( layerelm, doc, context );
219223
layerselm.appendChild( layerelm );

src/core/qgslayerdefinition.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121

2222
#include <QString>
2323
#include <QVector>
24+
#include <QDomNode>
2425

25-
class QDomNode;
2626
class QDomDocument;
2727

2828
class QgsLayerTreeGroup;

tests/src/core/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ SET(TESTS
191191
testziplayer.cpp
192192
testqgsmeshlayer.cpp
193193
testqgsmeshlayerrenderer.cpp
194+
testqgslayerdefinition.cpp
194195
)
195196

196197
IF(WITH_QTWEBKIT)
+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/***************************************************************************
2+
testqgsfilefiledownloader.cpp
3+
--------------------------------------
4+
Date : 07.06.2018
5+
Copyright : (C) 2018 Alessandro Pasotti
6+
Email : elpaso at itopen dot it
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+
17+
#include "qgstest.h"
18+
#include <QObject>
19+
#include <QTemporaryFile>
20+
#include <QTemporaryDir>
21+
22+
#include <qgsapplication.h>
23+
#include <qgsproject.h>
24+
#include <qgslayertree.h>
25+
#include <qgslayerdefinition.h>
26+
27+
class TestQgsLayerDefinition: public QObject
28+
{
29+
Q_OBJECT
30+
public:
31+
TestQgsLayerDefinition() = default;
32+
33+
private slots:
34+
void initTestCase(); // will be called before the first testfunction is executed.
35+
void cleanupTestCase(); // will be called after the last testfunction was executed.
36+
void init(); // will be called before each testfunction is executed.
37+
void cleanup(); // will be called after every testfunction.
38+
39+
/**
40+
* test that findLayers() skips invalid layers
41+
*/
42+
void testFindLayers();
43+
44+
/**
45+
* test that export does not crash: regression #18981
46+
* https://issues.qgis.org/issues/18981 - Save QLR crashes QGIS 3
47+
*/
48+
void testExportDoesNotCrash();
49+
50+
private:
51+
QTemporaryFile *mTempFile;
52+
};
53+
54+
55+
void TestQgsLayerDefinition::initTestCase()
56+
{
57+
QgsApplication::init();
58+
QgsApplication::initQgis();
59+
60+
}
61+
62+
void TestQgsLayerDefinition::cleanupTestCase()
63+
{
64+
QgsApplication::exitQgis();
65+
}
66+
67+
void TestQgsLayerDefinition::init()
68+
{
69+
mTempFile = new QTemporaryFile();
70+
QVERIFY( mTempFile->open() );
71+
mTempFile->close();
72+
QString errorMessage;
73+
const QString path = QString( TEST_DATA_DIR + QStringLiteral( "/bug_18981_broken.qlr" ) );
74+
QgsLayerDefinition::loadLayerDefinition( path, QgsProject::instance(), QgsProject::instance()->layerTreeRoot(), errorMessage );
75+
QVERIFY( errorMessage.isEmpty() );
76+
}
77+
78+
79+
void TestQgsLayerDefinition::cleanup()
80+
{
81+
delete mTempFile;
82+
}
83+
84+
void TestQgsLayerDefinition::testFindLayers()
85+
{
86+
QCOMPARE( QgsProject::instance()->layerTreeRoot()->findLayers().count(), 1 );
87+
QCOMPARE( QgsProject::instance()->layerTreeRoot()->findLayers().at( 0 )->name(), QStringLiteral( "NewMemory" ) );
88+
}
89+
90+
void TestQgsLayerDefinition::testExportDoesNotCrash()
91+
{
92+
QString errorMessage;
93+
QVERIFY( QgsLayerDefinition::exportLayerDefinition( mTempFile->fileName(), QgsProject::instance()->layerTreeRoot()->children(), errorMessage ) );
94+
QVERIFY( errorMessage.isEmpty() );
95+
// Reload
96+
const QString path = QString( TEST_DATA_DIR + QStringLiteral( "/bug_18981_broken.qlr" ) );
97+
QgsProject::instance()->removeAllMapLayers();
98+
QgsLayerDefinition::loadLayerDefinition( path, QgsProject::instance(), QgsProject::instance()->layerTreeRoot(), errorMessage );
99+
testFindLayers();
100+
}
101+
102+
103+
104+
QGSTEST_MAIN( TestQgsLayerDefinition )
105+
#include "testqgslayerdefinition.moc"
106+
107+

tests/testdata/bug_18981_broken.qlr

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<!DOCTYPE qgis-layer-definition>
2+
<qlr>
3+
<layer-tree-group expanded="1" checked="Qt::Checked" name="">
4+
<customproperties/>
5+
<layer-tree-group expanded="1" checked="Qt::Checked" name="qgis_plugin">
6+
<customproperties/>
7+
<layer-tree-group expanded="0" checked="Qt::Checked" name="Baggrundskort">
8+
<customproperties/>
9+
<layer-tree-layer expanded="1" providerKey="wms" checked="Qt::Checked" id="DTK_D8502016120170201702017020170201702017201801241314424672312" source="IgnoreGetMapUrl=1&amp;contextualWMSLegend=0&amp;crs=EPSG:25832&amp;dpiMode=7&amp;featureCount=10&amp;format=image/png&amp;layers=dtk_d850&amp;styles=&amp;url=http://kortforsyningen.kms.dk/service?servicename%3Dtopo_basis%26client%3DQGIS%26version%3D1.1.1%26login%3Dj%26password%3Dj" name="DTK/D850">
10+
<customproperties/>
11+
</layer-tree-layer>
12+
<layer-tree-layer providerKey="memory" checked="Qt::Checked" expanded="1" name="NewMemory" source="NoGeometry?crs=EPSG:4326&amp;uid={64c9bcbf-cabe-4bd9-9058-2b80ebf87a72}" id="NewMemory_ffa4d8a4_e5be_46a9_a0e0_fb6ee924f3cd">
13+
<customproperties/>
14+
</layer-tree-layer>
15+
</layer-tree-group>
16+
</layer-tree-group>
17+
</layer-tree-group>
18+
<maplayers>
19+
<maplayer minimumScale="0" maximumScale="1e+08" type="raster" hasScaleBasedVisibilityFlag="0">
20+
<extent>
21+
<xmin>120000</xmin>
22+
<ymin>5900000</ymin>
23+
<xmax>1000000</xmax>
24+
<ymax>6500000</ymax>
25+
</extent>
26+
<id>DTK_D8502016120170201702017020170201702017201801241314424672312</id>
27+
<datasource>IgnoreGetMapUrl=1&amp;contextualWMSLegend=0&amp;crs=EPSG:25832&amp;dpiMode=7&amp;featureCount=10&amp;format=image/png&amp;layers=dtk_d850&amp;styles=&amp;url=http://kortforsyningen.kms.dk/service?servicename%3Dtopo_basis%26client%3DQGIS%26version%3D1.1.1%26login%3Dj%26password%3Dj</datasource>
28+
<keywordList>
29+
<value></value>
30+
</keywordList>
31+
<layername>DTK/D850</layername>
32+
<srs>
33+
<spatialrefsys>
34+
<proj4>+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs</proj4>
35+
<srsid>2105</srsid>
36+
<srid>25832</srid>
37+
<authid>EPSG:25832</authid>
38+
<description>ETRS89 / UTM zone 32N</description>
39+
<projectionacronym>utm</projectionacronym>
40+
<ellipsoidacronym>GRS80</ellipsoidacronym>
41+
<geographicflag>false</geographicflag>
42+
</spatialrefsys>
43+
</srs>
44+
<customproperties>
45+
<property key="identify/format" value="Undefined"/>
46+
</customproperties>
47+
<provider>wms</provider>
48+
<noData>
49+
<noDataList bandNo="1" useSrcNoData="0"/>
50+
</noData>
51+
<map-layer-style-manager current="">
52+
<map-layer-style name=""/>
53+
</map-layer-style-manager>
54+
<pipe>
55+
<rasterrenderer opacity="1" alphaBand="-1" band="1" type="singlebandcolordata">
56+
<rasterTransparency/>
57+
</rasterrenderer>
58+
<brightnesscontrast brightness="0" contrast="0"/>
59+
<huesaturation colorizeGreen="128" colorizeOn="0" colorizeRed="255" colorizeBlue="128" grayscaleMode="0" saturation="0" colorizeStrength="100"/>
60+
<rasterresampler maxOversampling="2"/>
61+
</pipe>
62+
<blendMode>0</blendMode>
63+
</maplayer>
64+
<maplayer refreshOnNotifyEnabled="0" readOnly="0" minScale="1e+8" type="vector" autoRefreshTime="0" hasScaleBasedVisibilityFlag="0" geometry="No geometry" autoRefreshEnabled="0" refreshOnNotifyMessage="" maxScale="0">
65+
<id>NewMemory_ffa4d8a4_e5be_46a9_a0e0_fb6ee924f3cd</id>
66+
<datasource>memory?geometry=NoGeometry&amp;crs=EPSG:4326</datasource>
67+
<keywordList>
68+
<value></value>
69+
</keywordList>
70+
<layername>NewMemory</layername>
71+
<srs>
72+
<spatialrefsys>
73+
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
74+
<srsid>3452</srsid>
75+
<srid>4326</srid>
76+
<authid>EPSG:4326</authid>
77+
<description>WGS 84</description>
78+
<projectionacronym>longlat</projectionacronym>
79+
<ellipsoidacronym>WGS84</ellipsoidacronym>
80+
<geographicflag>true</geographicflag>
81+
</spatialrefsys>
82+
</srs>
83+
<resourceMetadata>
84+
<identifier></identifier>
85+
<parentidentifier></parentidentifier>
86+
<language></language>
87+
<type></type>
88+
<title></title>
89+
<abstract></abstract>
90+
<links/>
91+
<fees></fees>
92+
<encoding></encoding>
93+
<crs>
94+
<spatialrefsys>
95+
<proj4></proj4>
96+
<srsid>0</srsid>
97+
<srid>0</srid>
98+
<authid></authid>
99+
<description></description>
100+
<projectionacronym></projectionacronym>
101+
<ellipsoidacronym></ellipsoidacronym>
102+
<geographicflag>false</geographicflag>
103+
</spatialrefsys>
104+
</crs>
105+
<extent/>
106+
</resourceMetadata>
107+
<customproperties/>
108+
<provider encoding="UTF-8">memory</provider>
109+
<vectorjoins/>
110+
<layerDependencies/>
111+
<dataDependencies/>
112+
<legend type="default-vector"/>
113+
<expressionfields/>
114+
<map-layer-style-manager current="predefinito">
115+
<map-layer-style name="predefinito"/>
116+
</map-layer-style-manager>
117+
<auxiliaryLayer/>
118+
<fieldConfiguration/>
119+
<aliases/>
120+
<excludeAttributesWMS/>
121+
<excludeAttributesWFS/>
122+
<defaults/>
123+
<constraints/>
124+
<constraintExpressions/>
125+
<attributeactions>
126+
<defaultAction key="Canvas" value="{00000000-0000-0000-0000-000000000000}"/>
127+
</attributeactions>
128+
<attributetableconfig sortOrder="0" actionWidgetStyle="dropDown" sortExpression="">
129+
<columns/>
130+
</attributetableconfig>
131+
<editform></editform>
132+
<editforminit/>
133+
<editforminitcodesource>0</editforminitcodesource>
134+
<editforminitfilepath></editforminitfilepath>
135+
<editforminitcode><![CDATA[]]></editforminitcode>
136+
<featformsuppress>0</featformsuppress>
137+
<editorlayout>generatedlayout</editorlayout>
138+
<editable/>
139+
<labelOnTop/>
140+
<widgets/>
141+
<conditionalstyles>
142+
<rowstyles/>
143+
<fieldstyles/>
144+
</conditionalstyles>
145+
<expressionfields/>
146+
<previewExpression></previewExpression>
147+
<mapTip></mapTip>
148+
</maplayer>
149+
</maplayers>
150+
</qlr>

0 commit comments

Comments
 (0)