QGIS API Documentation 3.43.0-Master (e01d6d7c4c0)
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#include "qgsogrutils.h"
20#include "qgsvectorfilewriter.h"
21#include "qgsvectorlayer.h"
22#include "qgsapplication.h"
24#include "qgsfieldformatter.h"
25
27
28class FieldValueConverter : public QgsVectorFileWriter::FieldValueConverter
29{
30 public:
32 : mLayer( vl )
33 {
34 const QStringList formattersAllowList { QStringLiteral( "KeyValue" ), QStringLiteral( "List" ), QStringLiteral( "ValueRelation" ), QStringLiteral( "ValueMap" ) };
35
36 for ( int i = 0; i < mLayer->fields().count(); ++i )
37 {
38 const QgsEditorWidgetSetup setup = mLayer->fields().at( i ).editorWidgetSetup();
40 if ( formattersAllowList.contains( fieldFormatter->id() ) )
41 {
42 mFormatters[i] = fieldFormatter;
43 mConfig[i] = setup.config();
44 }
45 }
46 }
47
48 QgsField fieldDefinition( const QgsField &field ) override
49 {
50 if ( !mLayer )
51 return field;
52
53 const int idx = mLayer->fields().indexFromName( field.name() );
54 if ( mFormatters.contains( idx ) )
55 {
56 QgsField newField( field.name(), QMetaType::Type::QString );
57 newField.setAlias( field.alias() );
58 return newField;
59 }
60 return field;
61 }
62
63 QVariant convert( int i, const QVariant &value ) override
64 {
65 const QgsFieldFormatter *formatter = mFormatters.value( i );
66 if ( !formatter )
67 return value;
68
69 QVariant cache;
70 if ( mCaches.contains( i ) )
71 {
72 cache = mCaches.value( i );
73 }
74 else
75 {
76 cache = formatter->createCache( mLayer.data(), i, mConfig.value( i ) );
77 mCaches[i] = cache;
78 }
79
80 return formatter->representValue( mLayer.data(), i, mConfig.value( i ), cache, value );
81 }
82
83 FieldValueConverter *clone() const override
84 {
85 return new FieldValueConverter( *this );
86 }
87
88 private:
89 QPointer<QgsVectorLayer> mLayer;
90 QMap<int, const QgsFieldFormatter *> mFormatters;
91 QMap<int, QVariantMap> mConfig;
92 QMap<int, QVariant> mCaches;
93};
94
95QString QgsExportToSpreadsheetAlgorithm::name() const
96{
97 return QStringLiteral( "exporttospreadsheet" );
98}
99
100QString QgsExportToSpreadsheetAlgorithm::displayName() const
101{
102 return QObject::tr( "Export to spreadsheet" );
103}
104
105QStringList QgsExportToSpreadsheetAlgorithm::tags() const
106{
107 return QObject::tr( "microsoft,excel,xls,xlsx,calc,open,office,libre,ods" ).split( ',' );
108}
109
110QString QgsExportToSpreadsheetAlgorithm::group() const
111{
112 return QObject::tr( "Layer tools" );
113}
114
115QString QgsExportToSpreadsheetAlgorithm::groupId() const
116{
117 return QStringLiteral( "layertools" );
118}
119
120void QgsExportToSpreadsheetAlgorithm::initAlgorithm( const QVariantMap & )
121{
122 addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), Qgis::ProcessingSourceType::Vector ) );
123 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "USE_ALIAS" ), QObject::tr( "Use field aliases as column headings" ), false ) );
124 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "FORMATTED_VALUES" ), QObject::tr( "Export formatted values instead of raw values" ), false ) );
125 QgsProcessingParameterFileDestination *outputParameter = new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Destination spreadsheet" ), QObject::tr( "Microsoft Excel (*.xlsx);;Open Document Spreadsheet (*.ods)" ) );
126 outputParameter->setMetadata( QVariantMap( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "dontconfirmoverwrite" ), true } } ) } } ) );
127 addParameter( outputParameter );
128 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "OVERWRITE" ), QObject::tr( "Overwrite existing spreadsheet" ), true ) );
129 addOutput( new QgsProcessingOutputMultipleLayers( QStringLiteral( "OUTPUT_LAYERS" ), QObject::tr( "Layers within spreadsheet" ) ) );
130}
131
132QString QgsExportToSpreadsheetAlgorithm::shortHelpString() const
133{
134 return QObject::tr( "This algorithm collects a number of existing layers and exports them to a spreadsheet document.\n\n"
135 "Optionally the layers can be appended to an existing spreadsheet as additional sheets.\n\n" );
136}
137
138QString QgsExportToSpreadsheetAlgorithm::shortDescription() const
139{
140 return QObject::tr( "Collects a number of existing layers and exports them to a spreadsheet document." );
141}
142
143QgsExportToSpreadsheetAlgorithm *QgsExportToSpreadsheetAlgorithm::createInstance() const
144{
145 return new QgsExportToSpreadsheetAlgorithm();
146}
147
148bool QgsExportToSpreadsheetAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
149{
150 const QList<QgsMapLayer *> layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
151 for ( QgsMapLayer *layer : layers )
152 {
153 mLayers.emplace_back( layer->clone() );
154 }
155
156 if ( mLayers.empty() )
157 feedback->reportError( QObject::tr( "No layers selected, spreadsheet will be empty" ), false );
158
159 return true;
160}
161
162QVariantMap QgsExportToSpreadsheetAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
163{
164 const bool overwrite = parameterAsBoolean( parameters, QStringLiteral( "OVERWRITE" ), context );
165 const QString outputPath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
166 if ( outputPath.isEmpty() )
167 throw QgsProcessingException( QObject::tr( "No output file specified." ) );
168
169 const bool useAlias = parameterAsBoolean( parameters, QStringLiteral( "USE_ALIAS" ), context );
170 const bool formattedValues = parameterAsBoolean( parameters, QStringLiteral( "FORMATTED_VALUES" ), context );
171 bool createNew = true;
172 // delete existing spreadsheet if it exists
173 if ( overwrite && QFile::exists( outputPath ) )
174 {
175 feedback->pushInfo( QObject::tr( "Removing existing file '%1'" ).arg( outputPath ) );
176 if ( !QFile( outputPath ).remove() )
177 {
178 throw QgsProcessingException( QObject::tr( "Could not remove existing file '%1'" ).arg( outputPath ) );
179 }
180 }
181 else if ( QFile::exists( outputPath ) )
182 {
183 createNew = false;
184 }
185
186 const QFileInfo fi( outputPath );
187 const QString driverName = QgsVectorFileWriter::driverForExtension( fi.suffix() );
188
189 OGRSFDriverH hDriver = OGRGetDriverByName( driverName.toLocal8Bit().constData() );
190 if ( !hDriver )
191 {
192 if ( driverName == QLatin1String( "ods" ) )
193 throw QgsProcessingException( QObject::tr( "Open Document Spreadsheet driver not found." ) );
194 else
195 throw QgsProcessingException( QObject::tr( "Microsoft Excel driver not found." ) );
196 }
197
199#if 0
200 if ( !QFile::exists( outputPath ) )
201 {
202 hDS = gdal::ogr_datasource_unique_ptr( OGR_Dr_CreateDataSource( hDriver, outputPath.toUtf8().constData(), nullptr ) );
203 if ( !hDS )
204 throw QgsProcessingException( QObject::tr( "Creation of spreadsheet %1 failed (OGR error: %2)" ).arg( outputPath, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
205 }
206#endif
207 bool errored = false;
208
209 QgsProcessingMultiStepFeedback multiStepFeedback( mLayers.size(), feedback );
210
211 QStringList outputLayers;
212 int i = 0;
213 for ( const auto &layer : mLayers )
214 {
215 if ( feedback->isCanceled() )
216 break;
217
218 multiStepFeedback.setCurrentStep( i );
219 i++;
220
221 if ( !layer )
222 {
223 // don't throw immediately - instead do what we can and error out later
224 feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
225 errored = true;
226 continue;
227 }
228
229 feedback->pushInfo( QObject::tr( "Exporting layer %1/%2: %3" ).arg( i ).arg( mLayers.size() ).arg( layer ? layer->name() : QString() ) );
230
231 FieldValueConverter converter( qobject_cast<QgsVectorLayer *>( layer.get() ) );
232
233 if ( !exportVectorLayer( qobject_cast<QgsVectorLayer *>( layer.get() ), outputPath, context, &multiStepFeedback, driverName, createNew, useAlias, formattedValues ? &converter : nullptr ) )
234 errored = true;
235 else
236 {
237 outputLayers.append( QStringLiteral( "%1|layername=%2" ).arg( outputPath, layer->name() ) );
238 createNew = false;
239 }
240 }
241
242 if ( errored )
243 throw QgsProcessingException( QObject::tr( "Error obtained while exporting one or more layers." ) );
244
245 QVariantMap outputs;
246 outputs.insert( QStringLiteral( "OUTPUT" ), outputPath );
247 outputs.insert( QStringLiteral( "OUTPUT_LAYERS" ), outputLayers );
248 return outputs;
249}
250
251bool QgsExportToSpreadsheetAlgorithm::exportVectorLayer( QgsVectorLayer *layer, const QString &path, QgsProcessingContext &context, QgsProcessingFeedback *feedback, const QString &driverName, bool createNew, bool preferAlias, QgsVectorFileWriter::FieldValueConverter *converter )
252{
254 options.driverName = driverName;
255 options.layerName = layer->name();
257 options.fileEncoding = context.defaultEncoding();
258 options.feedback = feedback;
260 options.fieldValueConverter = converter;
261
262
263 QString error;
264 QString newFilename;
265 QString newLayer;
266 if ( QgsVectorFileWriter::writeAsVectorFormatV3( layer, path, context.transformContext(), options, &error, &newFilename, &newLayer ) != QgsVectorFileWriter::NoError )
267 {
268 feedback->reportError( QObject::tr( "Exporting layer failed: %1" ).arg( error ) );
269 return false;
270 }
271 else
272 {
273 return true;
274 }
275}
276
@ Vector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
static QgsFieldFormatterRegistry * fieldFormatterRegistry()
Gets the registry of available field formatters.
Holder for the widget type and its configuration for a field.
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:53
QgsFieldFormatter * fieldFormatter(const QString &id) const
Gets a field formatter by its id.
A field formatter helps to handle and display values for a field.
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.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:53
QString name
Definition qgsfield.h:62
QString alias
Definition qgsfield.h:63
Base class for all map layer types.
Definition qgsmaplayer.h:77
QString name
Definition qgsmaplayer.h:81
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.