Branch data Line data Source code
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 : 0 : class FieldValueConverter : public QgsVectorFileWriter::FieldValueConverter
29 : : {
30 : : public:
31 : 0 : FieldValueConverter( QgsVectorLayer *vl )
32 : 0 : : mLayer( vl )
33 : 0 : {
34 : 0 : QStringList formattersAllowList{ QStringLiteral( "KeyValue" ),
35 : 0 : QStringLiteral( "List" ),
36 : 0 : QStringLiteral( "ValueRelation" ),
37 : 0 : QStringLiteral( "ValueMap" ) };
38 : :
39 : 0 : for ( int i = 0; i < mLayer->fields().count(); ++i )
40 : : {
41 : 0 : const QgsEditorWidgetSetup setup = mLayer->fields().at( i ).editorWidgetSetup();
42 : 0 : const QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
43 : 0 : if ( formattersAllowList.contains( fieldFormatter->id() ) )
44 : : {
45 : 0 : mFormatters[i] = fieldFormatter;
46 : 0 : mConfig[i] = setup.config();
47 : 0 : }
48 : 0 : }
49 : 0 : }
50 : :
51 : 0 : QgsField fieldDefinition( const QgsField &field ) override
52 : : {
53 : 0 : if ( !mLayer )
54 : 0 : return field;
55 : :
56 : 0 : int idx = mLayer->fields().indexFromName( field.name() );
57 : 0 : if ( mFormatters.contains( idx ) )
58 : : {
59 : 0 : return QgsField( field.name(), QVariant::String );
60 : : }
61 : 0 : return field;
62 : 0 : }
63 : :
64 : 0 : QVariant convert( int i, const QVariant &value ) override
65 : : {
66 : 0 : const QgsFieldFormatter *formatter = mFormatters.value( i );
67 : 0 : if ( !formatter )
68 : 0 : return value;
69 : :
70 : 0 : QVariant cache;
71 : 0 : if ( mCaches.contains( i ) )
72 : : {
73 : 0 : cache = mCaches.value( i );
74 : 0 : }
75 : : else
76 : : {
77 : 0 : cache = formatter->createCache( mLayer.data(), i, mConfig.value( i ) );
78 : 0 : mCaches[ i ] = cache;
79 : : }
80 : :
81 : 0 : return formatter->representValue( mLayer.data(), i, mConfig.value( i ), cache, value );
82 : 0 : }
83 : :
84 : 0 : FieldValueConverter *clone() const override
85 : : {
86 : 0 : return new FieldValueConverter( *this );
87 : 0 : }
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 : :
96 : 0 : QString QgsExportToSpreadsheetAlgorithm::name() const
97 : : {
98 : 0 : return QStringLiteral( "exporttospreadsheet" );
99 : : }
100 : :
101 : 0 : QString QgsExportToSpreadsheetAlgorithm::displayName() const
102 : : {
103 : 0 : return QObject::tr( "Export to spreadsheet" );
104 : : }
105 : :
106 : 0 : QStringList QgsExportToSpreadsheetAlgorithm::tags() const
107 : : {
108 : 0 : return QObject::tr( "microsoft,excel,xls,xlsx,calc,open,office,libre,ods" ).split( ',' );
109 : 0 : }
110 : :
111 : 0 : QString QgsExportToSpreadsheetAlgorithm::group() const
112 : : {
113 : 0 : return QObject::tr( "Layer tools" );
114 : : }
115 : :
116 : 0 : QString QgsExportToSpreadsheetAlgorithm::groupId() const
117 : : {
118 : 0 : return QStringLiteral( "layertools" );
119 : : }
120 : :
121 : 0 : void QgsExportToSpreadsheetAlgorithm::initAlgorithm( const QVariantMap & )
122 : : {
123 : 0 : addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), QgsProcessing::TypeVector ) );
124 : 0 : addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "USE_ALIAS" ), QObject::tr( "Use field aliases as column headings" ), false ) );
125 : 0 : addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "FORMATTED_VALUES" ), QObject::tr( "Export formatted values instead of raw values" ), false ) );
126 : 0 : QgsProcessingParameterFileDestination *outputParameter = new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Destination spreadsheet" ), QObject::tr( "Microsoft Excel (*.xlsx);;Open Document Spreadsheet (*.ods)" ) );
127 : 0 : outputParameter->setMetadata( QVariantMap( {{QStringLiteral( "widget_wrapper" ), QVariantMap( {{QStringLiteral( "dontconfirmoverwrite" ), true }} ) }} ) );
128 : 0 : addParameter( outputParameter );
129 : 0 : addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "OVERWRITE" ), QObject::tr( "Overwrite existing spreadsheet" ), true ) );
130 : 0 : addOutput( new QgsProcessingOutputMultipleLayers( QStringLiteral( "OUTPUT_LAYERS" ), QObject::tr( "Layers within spreadsheet" ) ) );
131 : 0 : }
132 : :
133 : 0 : QString QgsExportToSpreadsheetAlgorithm::shortHelpString() const
134 : : {
135 : 0 : 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 : :
139 : 0 : QgsExportToSpreadsheetAlgorithm *QgsExportToSpreadsheetAlgorithm::createInstance() const
140 : : {
141 : 0 : return new QgsExportToSpreadsheetAlgorithm();
142 : : }
143 : :
144 : 0 : bool QgsExportToSpreadsheetAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
145 : : {
146 : 0 : const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
147 : 0 : for ( QgsMapLayer *layer : layers )
148 : : {
149 : 0 : mLayers.emplace_back( layer->clone() );
150 : : }
151 : :
152 : 0 : if ( mLayers.empty() )
153 : 0 : feedback->reportError( QObject::tr( "No layers selected, spreadsheet will be empty" ), false );
154 : :
155 : : return true;
156 : 0 : }
157 : :
158 : 0 : QVariantMap QgsExportToSpreadsheetAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
159 : : {
160 : 0 : const bool overwrite = parameterAsBoolean( parameters, QStringLiteral( "OVERWRITE" ), context );
161 : 0 : QString outputPath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
162 : 0 : if ( outputPath.isEmpty() )
163 : 0 : throw QgsProcessingException( QObject::tr( "No output file specified." ) );
164 : :
165 : 0 : const bool useAlias = parameterAsBoolean( parameters, QStringLiteral( "USE_ALIAS" ), context );
166 : 0 : const bool formattedValues = parameterAsBoolean( parameters, QStringLiteral( "FORMATTED_VALUES" ), context );
167 : 0 : bool createNew = true;
168 : : // delete existing spreadsheet if it exists
169 : 0 : if ( overwrite && QFile::exists( outputPath ) )
170 : : {
171 : 0 : feedback->pushInfo( QObject::tr( "Removing existing file '%1'" ).arg( outputPath ) );
172 : 0 : if ( !QFile( outputPath ).remove() )
173 : : {
174 : 0 : throw QgsProcessingException( QObject::tr( "Could not remove existing file '%1'" ).arg( outputPath ) );
175 : : }
176 : 0 : }
177 : 0 : else if ( QFile::exists( outputPath ) )
178 : : {
179 : 0 : createNew = false;
180 : 0 : }
181 : :
182 : 0 : QFileInfo fi( outputPath );
183 : 0 : const QString driverName = QgsVectorFileWriter::driverForExtension( fi.suffix() );
184 : :
185 : 0 : OGRSFDriverH hDriver = OGRGetDriverByName( driverName.toLocal8Bit().constData() );
186 : 0 : if ( !hDriver )
187 : : {
188 : 0 : if ( driverName == QLatin1String( "ods" ) )
189 : 0 : throw QgsProcessingException( QObject::tr( "Open Document Spreadsheet driver not found." ) );
190 : : else
191 : 0 : throw QgsProcessingException( QObject::tr( "Microsoft Excel driver not found." ) );
192 : : }
193 : :
194 : 0 : 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 : 0 : bool errored = false;
204 : :
205 : 0 : QgsProcessingMultiStepFeedback multiStepFeedback( mLayers.size(), feedback );
206 : :
207 : 0 : QStringList outputLayers;
208 : 0 : int i = 0;
209 : 0 : for ( const auto &layer : mLayers )
210 : : {
211 : 0 : if ( feedback->isCanceled() )
212 : 0 : break;
213 : :
214 : 0 : multiStepFeedback.setCurrentStep( i );
215 : 0 : i++;
216 : :
217 : 0 : if ( !layer )
218 : : {
219 : : // don't throw immediately - instead do what we can and error out later
220 : 0 : feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
221 : 0 : errored = true;
222 : 0 : continue;
223 : : }
224 : :
225 : 0 : feedback->pushInfo( QObject::tr( "Exporting layer %1/%2: %3" ).arg( i ).arg( mLayers.size() ).arg( layer ? layer->name() : QString() ) );
226 : :
227 : 0 : FieldValueConverter converter( qobject_cast< QgsVectorLayer * >( layer.get() ) );
228 : :
229 : 0 : if ( !exportVectorLayer( qobject_cast< QgsVectorLayer * >( layer.get() ), outputPath,
230 : 0 : context, &multiStepFeedback, driverName, createNew, useAlias, formattedValues ? &converter : nullptr ) )
231 : 0 : errored = true;
232 : : else
233 : : {
234 : 0 : outputLayers.append( QStringLiteral( "%1|layername=%2" ).arg( outputPath, layer->name() ) );
235 : 0 : createNew = false;
236 : : }
237 : 0 : }
238 : :
239 : 0 : if ( errored )
240 : 0 : throw QgsProcessingException( QObject::tr( "Error obtained while exporting one or more layers." ) );
241 : :
242 : 0 : QVariantMap outputs;
243 : 0 : outputs.insert( QStringLiteral( "OUTPUT" ), outputPath );
244 : 0 : outputs.insert( QStringLiteral( "OUTPUT_LAYERS" ), outputLayers );
245 : 0 : return outputs;
246 : 0 : }
247 : :
248 : 0 : 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 : 0 : QgsVectorFileWriter::SaveVectorOptions options;
252 : 0 : options.driverName = driverName;
253 : 0 : options.layerName = layer->name();
254 : 0 : options.actionOnExistingFile = createNew ? QgsVectorFileWriter::CreateOrOverwriteFile : QgsVectorFileWriter::CreateOrOverwriteLayer;
255 : 0 : options.fileEncoding = context.defaultEncoding();
256 : 0 : options.feedback = feedback;
257 : 0 : options.fieldNameSource = preferAlias ? QgsVectorFileWriter::PreferAlias : QgsVectorFileWriter::Original;
258 : 0 : options.fieldValueConverter = converter;
259 : :
260 : :
261 : 0 : QString error;
262 : 0 : QString newFilename;
263 : 0 : QString newLayer;
264 : 0 : if ( QgsVectorFileWriter::writeAsVectorFormatV2( layer, path, context.transformContext(), options, &newFilename, &newLayer, &error ) != QgsVectorFileWriter::NoError )
265 : : {
266 : 0 : feedback->reportError( QObject::tr( "Exporting layer failed: %1" ).arg( error ) );
267 : 0 : return false;
268 : : }
269 : : else
270 : : {
271 : 0 : return true;
272 : : }
273 : 0 : }
274 : :
275 : : ///@endcond
|