1 /***************************************************************************
2 qgsalgorithmexporttospreadsheet.cpp
3 ------------------
4 begin : December 2020
5 copyright : (C) 2020 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "qgsalgorithmexporttospreadsheet.h"
19 #include "qgsogrutils.h"
20 #include "qgsvectorfilewriter.h"
21 #include "qgsvectorlayer.h"
22 #include "qgsapplication.h"
23 #include "qgsfieldformatterregistry.h"
24 #include "qgsfieldformatter.h"
25
26 ///@cond PRIVATE
27
28 class FieldValueConverter : public QgsVectorFileWriter::FieldValueConverter
29 {
30 public:
FieldValueConverter(QgsVectorLayer * vl)31 FieldValueConverter( QgsVectorLayer *vl )
32 : mLayer( vl )
33 {
34 const QStringList formattersAllowList{ QStringLiteral( "KeyValue" ),
35 QStringLiteral( "List" ),
36 QStringLiteral( "ValueRelation" ),
37 QStringLiteral( "ValueMap" ) };
38
39 for ( int i = 0; i < mLayer->fields().count(); ++i )
40 {
41 const QgsEditorWidgetSetup setup = mLayer->fields().at( i ).editorWidgetSetup();
42 const QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
43 if ( formattersAllowList.contains( fieldFormatter->id() ) )
44 {
45 mFormatters[i] = fieldFormatter;
46 mConfig[i] = setup.config();
47 }
48 }
49 }
50
fieldDefinition(const QgsField & field)51 QgsField fieldDefinition( const QgsField &field ) override
52 {
53 if ( !mLayer )
54 return field;
55
56 const int idx = mLayer->fields().indexFromName( field.name() );
57 if ( mFormatters.contains( idx ) )
58 {
59 return QgsField( field.name(), QVariant::String );
60 }
61 return field;
62 }
63
convert(int i,const QVariant & value)64 QVariant convert( int i, const QVariant &value ) override
65 {
66 const QgsFieldFormatter *formatter = mFormatters.value( i );
67 if ( !formatter )
68 return value;
69
70 QVariant cache;
71 if ( mCaches.contains( i ) )
72 {
73 cache = mCaches.value( i );
74 }
75 else
76 {
77 cache = formatter->createCache( mLayer.data(), i, mConfig.value( i ) );
78 mCaches[ i ] = cache;
79 }
80
81 return formatter->representValue( mLayer.data(), i, mConfig.value( i ), cache, value );
82 }
83
clone() const84 FieldValueConverter *clone() const override
85 {
86 return new FieldValueConverter( *this );
87 }
88
89 private:
90 QPointer< QgsVectorLayer > mLayer;
91 QMap< int, const QgsFieldFormatter * > mFormatters;
92 QMap< int, QVariantMap > mConfig;
93 QMap< int, QVariant > mCaches;
94 };
95
name() const96 QString QgsExportToSpreadsheetAlgorithm::name() const
97 {
98 return QStringLiteral( "exporttospreadsheet" );
99 }
100
displayName() const101 QString QgsExportToSpreadsheetAlgorithm::displayName() const
102 {
103 return QObject::tr( "Export to spreadsheet" );
104 }
105
tags() const106 QStringList QgsExportToSpreadsheetAlgorithm::tags() const
107 {
108 return QObject::tr( "microsoft,excel,xls,xlsx,calc,open,office,libre,ods" ).split( ',' );
109 }
110
group() const111 QString QgsExportToSpreadsheetAlgorithm::group() const
112 {
113 return QObject::tr( "Layer tools" );
114 }
115
groupId() const116 QString QgsExportToSpreadsheetAlgorithm::groupId() const
117 {
118 return QStringLiteral( "layertools" );
119 }
120
initAlgorithm(const QVariantMap &)121 void QgsExportToSpreadsheetAlgorithm::initAlgorithm( const QVariantMap & )
122 {
123 addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), QgsProcessing::TypeVector ) );
124 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "USE_ALIAS" ), QObject::tr( "Use field aliases as column headings" ), false ) );
125 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "FORMATTED_VALUES" ), QObject::tr( "Export formatted values instead of raw values" ), false ) );
126 QgsProcessingParameterFileDestination *outputParameter = new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Destination spreadsheet" ), QObject::tr( "Microsoft Excel (*.xlsx);;Open Document Spreadsheet (*.ods)" ) );
127 outputParameter->setMetadata( QVariantMap( {{QStringLiteral( "widget_wrapper" ), QVariantMap( {{QStringLiteral( "dontconfirmoverwrite" ), true }} ) }} ) );
128 addParameter( outputParameter );
129 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "OVERWRITE" ), QObject::tr( "Overwrite existing spreadsheet" ), true ) );
130 addOutput( new QgsProcessingOutputMultipleLayers( QStringLiteral( "OUTPUT_LAYERS" ), QObject::tr( "Layers within spreadsheet" ) ) );
131 }
132
shortHelpString() const133 QString QgsExportToSpreadsheetAlgorithm::shortHelpString() const
134 {
135 return QObject::tr( "This algorithm collects a number of existing layers and exports them into a spreadsheet document.\n\n"
136 "Optionally the layers can be appended to an existing spreadsheet as additional sheets.\n\n" );
137 }
138
createInstance() const139 QgsExportToSpreadsheetAlgorithm *QgsExportToSpreadsheetAlgorithm::createInstance() const
140 {
141 return new QgsExportToSpreadsheetAlgorithm();
142 }
143
prepareAlgorithm(const QVariantMap & parameters,QgsProcessingContext & context,QgsProcessingFeedback * feedback)144 bool QgsExportToSpreadsheetAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
145 {
146 const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
147 for ( QgsMapLayer *layer : layers )
148 {
149 mLayers.emplace_back( layer->clone() );
150 }
151
152 if ( mLayers.empty() )
153 feedback->reportError( QObject::tr( "No layers selected, spreadsheet will be empty" ), false );
154
155 return true;
156 }
157
processAlgorithm(const QVariantMap & parameters,QgsProcessingContext & context,QgsProcessingFeedback * feedback)158 QVariantMap QgsExportToSpreadsheetAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
159 {
160 const bool overwrite = parameterAsBoolean( parameters, QStringLiteral( "OVERWRITE" ), context );
161 const QString outputPath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
162 if ( outputPath.isEmpty() )
163 throw QgsProcessingException( QObject::tr( "No output file specified." ) );
164
165 const bool useAlias = parameterAsBoolean( parameters, QStringLiteral( "USE_ALIAS" ), context );
166 const bool formattedValues = parameterAsBoolean( parameters, QStringLiteral( "FORMATTED_VALUES" ), context );
167 bool createNew = true;
168 // delete existing spreadsheet if it exists
169 if ( overwrite && QFile::exists( outputPath ) )
170 {
171 feedback->pushInfo( QObject::tr( "Removing existing file '%1'" ).arg( outputPath ) );
172 if ( !QFile( outputPath ).remove() )
173 {
174 throw QgsProcessingException( QObject::tr( "Could not remove existing file '%1'" ).arg( outputPath ) );
175 }
176 }
177 else if ( QFile::exists( outputPath ) )
178 {
179 createNew = false;
180 }
181
182 const QFileInfo fi( outputPath );
183 const QString driverName = QgsVectorFileWriter::driverForExtension( fi.suffix() );
184
185 OGRSFDriverH hDriver = OGRGetDriverByName( driverName.toLocal8Bit().constData() );
186 if ( !hDriver )
187 {
188 if ( driverName == QLatin1String( "ods" ) )
189 throw QgsProcessingException( QObject::tr( "Open Document Spreadsheet driver not found." ) );
190 else
191 throw QgsProcessingException( QObject::tr( "Microsoft Excel driver not found." ) );
192 }
193
194 const gdal::ogr_datasource_unique_ptr hDS;
195 #if 0
196 if ( !QFile::exists( outputPath ) )
197 {
198 hDS = gdal::ogr_datasource_unique_ptr( OGR_Dr_CreateDataSource( hDriver, outputPath.toUtf8().constData(), nullptr ) );
199 if ( !hDS )
200 throw QgsProcessingException( QObject::tr( "Creation of spreadsheet %1 failed (OGR error: %2)" ).arg( outputPath, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
201 }
202 #endif
203 bool errored = false;
204
205 QgsProcessingMultiStepFeedback multiStepFeedback( mLayers.size(), feedback );
206
207 QStringList outputLayers;
208 int i = 0;
209 for ( const auto &layer : mLayers )
210 {
211 if ( feedback->isCanceled() )
212 break;
213
214 multiStepFeedback.setCurrentStep( i );
215 i++;
216
217 if ( !layer )
218 {
219 // don't throw immediately - instead do what we can and error out later
220 feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
221 errored = true;
222 continue;
223 }
224
225 feedback->pushInfo( QObject::tr( "Exporting layer %1/%2: %3" ).arg( i ).arg( mLayers.size() ).arg( layer ? layer->name() : QString() ) );
226
227 FieldValueConverter converter( qobject_cast< QgsVectorLayer * >( layer.get() ) );
228
229 if ( !exportVectorLayer( qobject_cast< QgsVectorLayer * >( layer.get() ), outputPath,
230 context, &multiStepFeedback, driverName, createNew, useAlias, formattedValues ? &converter : nullptr ) )
231 errored = true;
232 else
233 {
234 outputLayers.append( QStringLiteral( "%1|layername=%2" ).arg( outputPath, layer->name() ) );
235 createNew = false;
236 }
237 }
238
239 if ( errored )
240 throw QgsProcessingException( QObject::tr( "Error obtained while exporting one or more layers." ) );
241
242 QVariantMap outputs;
243 outputs.insert( QStringLiteral( "OUTPUT" ), outputPath );
244 outputs.insert( QStringLiteral( "OUTPUT_LAYERS" ), outputLayers );
245 return outputs;
246 }
247
exportVectorLayer(QgsVectorLayer * layer,const QString & path,QgsProcessingContext & context,QgsProcessingFeedback * feedback,const QString & driverName,bool createNew,bool preferAlias,QgsVectorFileWriter::FieldValueConverter * converter)248 bool QgsExportToSpreadsheetAlgorithm::exportVectorLayer( QgsVectorLayer *layer, const QString &path, QgsProcessingContext &context,
249 QgsProcessingFeedback *feedback, const QString &driverName, bool createNew, bool preferAlias, QgsVectorFileWriter::FieldValueConverter *converter )
250 {
251 QgsVectorFileWriter::SaveVectorOptions options;
252 options.driverName = driverName;
253 options.layerName = layer->name();
254 options.actionOnExistingFile = createNew ? QgsVectorFileWriter::CreateOrOverwriteFile : QgsVectorFileWriter::CreateOrOverwriteLayer;
255 options.fileEncoding = context.defaultEncoding();
256 options.feedback = feedback;
257 options.fieldNameSource = preferAlias ? QgsVectorFileWriter::PreferAlias : QgsVectorFileWriter::Original;
258 options.fieldValueConverter = converter;
259
260
261 QString error;
262 QString newFilename;
263 QString newLayer;
264 if ( QgsVectorFileWriter::writeAsVectorFormatV3( layer, path, context.transformContext(), options, &error, &newFilename, &newLayer ) != QgsVectorFileWriter::NoError )
265 {
266 feedback->reportError( QObject::tr( "Exporting layer failed: %1" ).arg( error ) );
267 return false;
268 }
269 else
270 {
271 return true;
272 }
273 }
274
275 ///@endcond
276