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