QGIS API Documentation 4.3.0-Master (ffcfc20b9b4)
Loading...
Searching...
No Matches
qgsalgorithmexporttospreadsheet.cpp
Go to the documentation of this file.
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
19
20#include "qgsapplication.h"
21#include "qgsfieldformatter.h"
23#include "qgsogrutils.h"
24#include "qgsvectorfilewriter.h"
25#include "qgsvectorlayer.h"
26
27#include <QString>
28
29using namespace Qt::StringLiterals;
30
32
33class FieldValueConverter : public QgsVectorFileWriter::FieldValueConverter
34{
35 public:
36 FieldValueConverter( QgsVectorLayer *vl )
37 : mLayer( vl )
38 {
39 const QStringList formattersAllowList { u"KeyValue"_s, u"List"_s, u"ValueRelation"_s, u"ValueMap"_s };
40
41 for ( int i = 0; i < mLayer->fields().count(); ++i )
42 {
43 const QgsEditorWidgetSetup setup = mLayer->fields().at( i ).editorWidgetSetup();
44 const QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
45 if ( formattersAllowList.contains( fieldFormatter->id() ) )
46 {
47 mFormatters[i] = fieldFormatter;
48 mConfig[i] = setup.config();
49 }
50 }
51 }
52
53 QgsField fieldDefinition( const QgsField &field ) override
54 {
55 if ( !mLayer )
56 return field;
57
58 const int idx = mLayer->fields().indexFromName( field.name() );
59 if ( mFormatters.contains( idx ) )
60 {
61 QgsField newField( field.name(), QMetaType::Type::QString );
62 newField.setAlias( field.alias() );
63 return newField;
64 }
65 return field;
66 }
67
68 QVariant convert( int i, const QVariant &value ) override
69 {
70 const QgsFieldFormatter *formatter = mFormatters.value( i );
71 if ( !formatter )
72 return value;
73
74 QVariant cache;
75 if ( mCaches.contains( i ) )
76 {
77 cache = mCaches.value( i );
78 }
79 else
80 {
81 cache = formatter->createCache( mLayer.data(), i, mConfig.value( i ) );
82 mCaches[i] = cache;
83 }
84
85 return formatter->representValue( mLayer.data(), i, mConfig.value( i ), cache, value );
86 }
87
88 FieldValueConverter *clone() const override { return new FieldValueConverter( *this ); }
89
90 private:
91 QPointer<QgsVectorLayer> mLayer;
92 QMap<int, const QgsFieldFormatter *> mFormatters;
93 QMap<int, QVariantMap> mConfig;
94 QMap<int, QVariant> mCaches;
95};
96
97QString QgsExportToSpreadsheetAlgorithm::name() const
98{
99 return u"exporttospreadsheet"_s;
100}
101
102QString QgsExportToSpreadsheetAlgorithm::displayName() const
103{
104 return QObject::tr( "Export to spreadsheet" );
105}
106
107QStringList QgsExportToSpreadsheetAlgorithm::tags() const
108{
109 return QObject::tr( "microsoft,excel,xls,xlsx,calc,open,office,libre,ods" ).split( ',' );
110}
111
112QString QgsExportToSpreadsheetAlgorithm::group() const
113{
114 return QObject::tr( "Layer tools" );
115}
116
117QString QgsExportToSpreadsheetAlgorithm::groupId() const
118{
119 return u"layertools"_s;
120}
121
122void QgsExportToSpreadsheetAlgorithm::initAlgorithm( const QVariantMap & )
123{
124 addParameter( new QgsProcessingParameterMultipleLayers( u"LAYERS"_s, QObject::tr( "Input layers" ), Qgis::ProcessingSourceType::Vector ) );
125 addParameter( new QgsProcessingParameterBoolean( u"USE_ALIAS"_s, QObject::tr( "Use field aliases as column headings" ), false ) );
126 addParameter( new QgsProcessingParameterBoolean( u"FORMATTED_VALUES"_s, QObject::tr( "Export formatted values instead of raw values" ), false ) );
128 = new QgsProcessingParameterFileDestination( u"OUTPUT"_s, QObject::tr( "Destination spreadsheet" ), QObject::tr( "Microsoft Excel (*.xlsx);;Open Document Spreadsheet (*.ods)" ) );
129 outputParameter->setMetadata( QVariantMap( { { u"widget_wrapper"_s, QVariantMap( { { u"dontconfirmoverwrite"_s, true } } ) } } ) );
130 addParameter( outputParameter );
131 addParameter( new QgsProcessingParameterBoolean( u"OVERWRITE"_s, QObject::tr( "Overwrite existing spreadsheet" ), true ) );
132 addOutput( new QgsProcessingOutputMultipleLayers( u"OUTPUT_LAYERS"_s, QObject::tr( "Layers within spreadsheet" ) ) );
133}
134
135QString QgsExportToSpreadsheetAlgorithm::shortHelpString() const
136{
137 return QObject::tr(
138 "This algorithm collects a number of existing layers and exports them to a spreadsheet document.\n\n"
139 "Optionally the layers can be appended to an existing spreadsheet as additional sheets.\n\n"
140 );
141}
142
143QString QgsExportToSpreadsheetAlgorithm::shortDescription() const
144{
145 return QObject::tr( "Collects a number of existing layers and exports them to a spreadsheet document." );
146}
147
148QgsExportToSpreadsheetAlgorithm *QgsExportToSpreadsheetAlgorithm::createInstance() const
149{
150 return new QgsExportToSpreadsheetAlgorithm();
151}
152
153bool QgsExportToSpreadsheetAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
154{
155 const QList<QgsMapLayer *> layers = parameterAsLayerList( parameters, u"LAYERS"_s, context );
156 for ( QgsMapLayer *layer : layers )
157 {
158 mLayers.emplace_back( layer->clone() );
159 }
160
161 if ( mLayers.empty() )
162 feedback->reportError( QObject::tr( "No layers selected, spreadsheet will be empty" ), false );
163
164 return true;
165}
166
167QVariantMap QgsExportToSpreadsheetAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
168{
169 QGS_MARK_ALGORITHM_SOURCE
170
171 const bool overwrite = parameterAsBoolean( parameters, u"OVERWRITE"_s, context );
172 const QString outputPath = parameterAsString( parameters, u"OUTPUT"_s, context );
173 if ( outputPath.isEmpty() )
174 throw QgsProcessingException( QObject::tr( "No output file specified." ) );
175
176 const bool useAlias = parameterAsBoolean( parameters, u"USE_ALIAS"_s, context );
177 const bool formattedValues = parameterAsBoolean( parameters, u"FORMATTED_VALUES"_s, context );
178 bool createNew = true;
179 // delete existing spreadsheet if it exists
180 if ( overwrite && QFile::exists( outputPath ) )
181 {
182 feedback->pushInfo( QObject::tr( "Removing existing file '%1'" ).arg( outputPath ) );
183 if ( !QFile( outputPath ).remove() )
184 {
185 throw QgsProcessingException( QObject::tr( "Could not remove existing file '%1'" ).arg( outputPath ) );
186 }
187 }
188 else if ( QFile::exists( outputPath ) )
189 {
190 createNew = false;
191 }
192
193 const QFileInfo fi( outputPath );
194 const QString driverName = QgsVectorFileWriter::driverForExtension( fi.suffix() );
195
196 OGRSFDriverH hDriver = OGRGetDriverByName( driverName.toLocal8Bit().constData() );
197 if ( !hDriver )
198 {
199 if ( driverName == "ods"_L1 )
200 throw QgsProcessingException( QObject::tr( "Open Document Spreadsheet driver not found." ) );
201 else
202 throw QgsProcessingException( QObject::tr( "Microsoft Excel driver not found." ) );
203 }
204
206#if 0
207 if ( !QFile::exists( outputPath ) )
208 {
209 hDS = gdal::ogr_datasource_unique_ptr( OGR_Dr_CreateDataSource( hDriver, outputPath.toUtf8().constData(), nullptr ) );
210 if ( !hDS )
211 throw QgsProcessingException( QObject::tr( "Creation of spreadsheet %1 failed (OGR error: %2)" ).arg( outputPath, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
212 }
213#endif
214 bool errored = false;
215
216 QgsProcessingMultiStepFeedback multiStepFeedback( mLayers.size(), feedback );
217
218 QStringList outputLayers;
219 int i = 0;
220 for ( const auto &layer : mLayers )
221 {
222 if ( feedback->isCanceled() )
223 break;
224
225 multiStepFeedback.setCurrentStep( i );
226 i++;
227
228 if ( !layer )
229 {
230 // don't throw immediately - instead do what we can and error out later
231 feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
232 errored = true;
233 continue;
234 }
235
236 feedback->pushInfo( QObject::tr( "Exporting layer %1/%2: %3" ).arg( i ).arg( mLayers.size() ).arg( layer ? layer->name() : QString() ) );
237
238 FieldValueConverter converter( qobject_cast<QgsVectorLayer *>( layer.get() ) );
239
240 if ( !exportVectorLayer( qobject_cast<QgsVectorLayer *>( layer.get() ), outputPath, context, &multiStepFeedback, driverName, createNew, useAlias, formattedValues ? &converter : nullptr ) )
241 errored = true;
242 else
243 {
244 outputLayers.append( u"%1|layername=%2"_s.arg( outputPath, layer->name() ) );
245 createNew = false;
246 }
247 }
248
249 if ( errored )
250 throw QgsProcessingException( QObject::tr( "Error obtained while exporting one or more layers." ) );
251
252 QVariantMap outputs;
253 outputs.insert( u"OUTPUT"_s, outputPath );
254 outputs.insert( u"OUTPUT_LAYERS"_s, outputLayers );
255 return outputs;
256}
257
258bool QgsExportToSpreadsheetAlgorithm::exportVectorLayer(
259 QgsVectorLayer *layer,
260 const QString &path,
261 QgsProcessingContext &context,
262 QgsProcessingFeedback *feedback,
263 const QString &driverName,
264 bool createNew,
265 bool preferAlias,
267)
268{
270 options.driverName = driverName;
271 options.layerName = layer->name();
273 options.fileEncoding = context.defaultEncoding();
274 options.feedback = feedback;
276 options.fieldValueConverter = converter;
277
278
279 QString error;
280 QString newFilename;
281 QString newLayer;
282 if ( QgsVectorFileWriter::writeAsVectorFormatV3( layer, path, context.transformContext(), options, &error, &newFilename, &newLayer ) != QgsVectorFileWriter::NoError )
283 {
284 feedback->reportError( QObject::tr( "Exporting layer failed: %1" ).arg( error ) );
285 return false;
286 }
287 else
288 {
289 return true;
290 }
291}
292
@ Vector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition qgis.h:3755
static QgsFieldFormatterRegistry * fieldFormatterRegistry()
Gets the registry of available field formatters.
QString type() const
Returns the widget type to use.
QVariantMap config() const
Returns the widget configuration.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
QgsFieldFormatter * fieldFormatter(const QString &id) const
Gets a field formatter by its id.
virtual QVariant createCache(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config) const
Create a cache for a given field.
virtual QString id() const =0
Returns a unique id for this field formatter.
virtual QString representValue(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value) const
Create a pretty String representation of the value.
QString name
Definition qgsfield.h:65
QString alias
Definition qgsfield.h:66
Base class for all map layer types.
Definition qgsmaplayer.h:83
QString name
Definition qgsmaplayer.h:87
Contains information about the context in which a processing algorithm is executed.
QString defaultEncoding() const
Returns the default encoding to use for newly created files.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
Processing feedback object for multi-step operations.
A multi-layer output for processing algorithms which create map layers, when the number and nature of...
A boolean parameter for processing algorithms.
void setMetadata(const QVariantMap &metadata)
Sets the parameter's freeform metadata.
A generic file based destination parameter, for specifying the destination path for a file (non-map l...
A parameter for processing algorithms which accepts multiple map layers.
Interface to convert raw field values to their user-friendly values.
virtual QVariant convert(int fieldIdxInLayer, const QVariant &value)
Convert the provided value, for field fieldIdxInLayer.
virtual QgsVectorFileWriter::FieldValueConverter * clone() const
Creates a clone of the FieldValueConverter.
virtual QgsField fieldDefinition(const QgsField &field)
Returns a possibly modified field definition.
Options to pass to QgsVectorFileWriter::writeAsVectorFormat().
QString layerName
Layer name. If let empty, it will be derived from the filename.
QgsVectorFileWriter::FieldValueConverter * fieldValueConverter
Field value converter.
QgsVectorFileWriter::ActionOnExistingFile actionOnExistingFile
Action on existing file.
QgsVectorFileWriter::FieldNameSource fieldNameSource
Source for exported field names.
QgsFeedback * feedback
Optional feedback object allowing cancellation of layer save.
static QgsVectorFileWriter::WriterError writeAsVectorFormatV3(QgsVectorLayer *layer, const QString &fileName, const QgsCoordinateTransformContext &transformContext, const QgsVectorFileWriter::SaveVectorOptions &options, QString *errorMessage=nullptr, QString *newFilename=nullptr, QString *newLayer=nullptr)
Writes a layer out to a vector file.
static QString driverForExtension(const QString &extension)
Returns the OGR driver name for a specified file extension.
@ PreferAlias
Use the field alias as the exported field name, wherever one is set. Otherwise use the original field...
@ Original
Use original field names.
@ CreateOrOverwriteLayer
Create or overwrite layer.
@ CreateOrOverwriteFile
Create or overwrite file.
Represents a vector layer which manages a vector based dataset.
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.