QGIS API Documentation 3.41.0-Master (af5edcb665c)
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#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 return QgsField( field.name(), QMetaType::Type::QString );
57 }
58 return field;
59 }
60
61 QVariant convert( int i, const QVariant &value ) override
62 {
63 const QgsFieldFormatter *formatter = mFormatters.value( i );
64 if ( !formatter )
65 return value;
66
67 QVariant cache;
68 if ( mCaches.contains( i ) )
69 {
70 cache = mCaches.value( i );
71 }
72 else
73 {
74 cache = formatter->createCache( mLayer.data(), i, mConfig.value( i ) );
75 mCaches[i] = cache;
76 }
77
78 return formatter->representValue( mLayer.data(), i, mConfig.value( i ), cache, value );
79 }
80
81 FieldValueConverter *clone() const override
82 {
83 return new FieldValueConverter( *this );
84 }
85
86 private:
87 QPointer<QgsVectorLayer> mLayer;
88 QMap<int, const QgsFieldFormatter *> mFormatters;
89 QMap<int, QVariantMap> mConfig;
90 QMap<int, QVariant> mCaches;
91};
92
93QString QgsExportToSpreadsheetAlgorithm::name() const
94{
95 return QStringLiteral( "exporttospreadsheet" );
96}
97
98QString QgsExportToSpreadsheetAlgorithm::displayName() const
99{
100 return QObject::tr( "Export to spreadsheet" );
101}
102
103QStringList QgsExportToSpreadsheetAlgorithm::tags() const
104{
105 return QObject::tr( "microsoft,excel,xls,xlsx,calc,open,office,libre,ods" ).split( ',' );
106}
107
108QString QgsExportToSpreadsheetAlgorithm::group() const
109{
110 return QObject::tr( "Layer tools" );
111}
112
113QString QgsExportToSpreadsheetAlgorithm::groupId() const
114{
115 return QStringLiteral( "layertools" );
116}
117
118void QgsExportToSpreadsheetAlgorithm::initAlgorithm( const QVariantMap & )
119{
120 addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), Qgis::ProcessingSourceType::Vector ) );
121 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "USE_ALIAS" ), QObject::tr( "Use field aliases as column headings" ), false ) );
122 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "FORMATTED_VALUES" ), QObject::tr( "Export formatted values instead of raw values" ), false ) );
123 QgsProcessingParameterFileDestination *outputParameter = new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Destination spreadsheet" ), QObject::tr( "Microsoft Excel (*.xlsx);;Open Document Spreadsheet (*.ods)" ) );
124 outputParameter->setMetadata( QVariantMap( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "dontconfirmoverwrite" ), true } } ) } } ) );
125 addParameter( outputParameter );
126 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "OVERWRITE" ), QObject::tr( "Overwrite existing spreadsheet" ), true ) );
127 addOutput( new QgsProcessingOutputMultipleLayers( QStringLiteral( "OUTPUT_LAYERS" ), QObject::tr( "Layers within spreadsheet" ) ) );
128}
129
130QString QgsExportToSpreadsheetAlgorithm::shortHelpString() const
131{
132 return QObject::tr( "This algorithm collects a number of existing layers and exports them into a spreadsheet document.\n\n"
133 "Optionally the layers can be appended to an existing spreadsheet as additional sheets.\n\n" );
134}
135
136QgsExportToSpreadsheetAlgorithm *QgsExportToSpreadsheetAlgorithm::createInstance() const
137{
138 return new QgsExportToSpreadsheetAlgorithm();
139}
140
141bool QgsExportToSpreadsheetAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
142{
143 const QList<QgsMapLayer *> layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
144 for ( QgsMapLayer *layer : layers )
145 {
146 mLayers.emplace_back( layer->clone() );
147 }
148
149 if ( mLayers.empty() )
150 feedback->reportError( QObject::tr( "No layers selected, spreadsheet will be empty" ), false );
151
152 return true;
153}
154
155QVariantMap QgsExportToSpreadsheetAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
156{
157 const bool overwrite = parameterAsBoolean( parameters, QStringLiteral( "OVERWRITE" ), context );
158 const QString outputPath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
159 if ( outputPath.isEmpty() )
160 throw QgsProcessingException( QObject::tr( "No output file specified." ) );
161
162 const bool useAlias = parameterAsBoolean( parameters, QStringLiteral( "USE_ALIAS" ), context );
163 const bool formattedValues = parameterAsBoolean( parameters, QStringLiteral( "FORMATTED_VALUES" ), context );
164 bool createNew = true;
165 // delete existing spreadsheet if it exists
166 if ( overwrite && QFile::exists( outputPath ) )
167 {
168 feedback->pushInfo( QObject::tr( "Removing existing file '%1'" ).arg( outputPath ) );
169 if ( !QFile( outputPath ).remove() )
170 {
171 throw QgsProcessingException( QObject::tr( "Could not remove existing file '%1'" ).arg( outputPath ) );
172 }
173 }
174 else if ( QFile::exists( outputPath ) )
175 {
176 createNew = false;
177 }
178
179 const QFileInfo fi( outputPath );
180 const QString driverName = QgsVectorFileWriter::driverForExtension( fi.suffix() );
181
182 OGRSFDriverH hDriver = OGRGetDriverByName( driverName.toLocal8Bit().constData() );
183 if ( !hDriver )
184 {
185 if ( driverName == QLatin1String( "ods" ) )
186 throw QgsProcessingException( QObject::tr( "Open Document Spreadsheet driver not found." ) );
187 else
188 throw QgsProcessingException( QObject::tr( "Microsoft Excel driver not found." ) );
189 }
190
192#if 0
193 if ( !QFile::exists( outputPath ) )
194 {
195 hDS = gdal::ogr_datasource_unique_ptr( OGR_Dr_CreateDataSource( hDriver, outputPath.toUtf8().constData(), nullptr ) );
196 if ( !hDS )
197 throw QgsProcessingException( QObject::tr( "Creation of spreadsheet %1 failed (OGR error: %2)" ).arg( outputPath, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
198 }
199#endif
200 bool errored = false;
201
202 QgsProcessingMultiStepFeedback multiStepFeedback( mLayers.size(), feedback );
203
204 QStringList outputLayers;
205 int i = 0;
206 for ( const auto &layer : mLayers )
207 {
208 if ( feedback->isCanceled() )
209 break;
210
211 multiStepFeedback.setCurrentStep( i );
212 i++;
213
214 if ( !layer )
215 {
216 // don't throw immediately - instead do what we can and error out later
217 feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
218 errored = true;
219 continue;
220 }
221
222 feedback->pushInfo( QObject::tr( "Exporting layer %1/%2: %3" ).arg( i ).arg( mLayers.size() ).arg( layer ? layer->name() : QString() ) );
223
224 FieldValueConverter converter( qobject_cast<QgsVectorLayer *>( layer.get() ) );
225
226 if ( !exportVectorLayer( qobject_cast<QgsVectorLayer *>( layer.get() ), outputPath, context, &multiStepFeedback, driverName, createNew, useAlias, formattedValues ? &converter : nullptr ) )
227 errored = true;
228 else
229 {
230 outputLayers.append( QStringLiteral( "%1|layername=%2" ).arg( outputPath, layer->name() ) );
231 createNew = false;
232 }
233 }
234
235 if ( errored )
236 throw QgsProcessingException( QObject::tr( "Error obtained while exporting one or more layers." ) );
237
238 QVariantMap outputs;
239 outputs.insert( QStringLiteral( "OUTPUT" ), outputPath );
240 outputs.insert( QStringLiteral( "OUTPUT_LAYERS" ), outputLayers );
241 return outputs;
242}
243
244bool QgsExportToSpreadsheetAlgorithm::exportVectorLayer( QgsVectorLayer *layer, const QString &path, QgsProcessingContext &context, QgsProcessingFeedback *feedback, const QString &driverName, bool createNew, bool preferAlias, QgsVectorFileWriter::FieldValueConverter *converter )
245{
247 options.driverName = driverName;
248 options.layerName = layer->name();
250 options.fileEncoding = context.defaultEncoding();
251 options.feedback = feedback;
253 options.fieldValueConverter = converter;
254
255
256 QString error;
257 QString newFilename;
258 QString newLayer;
259 if ( QgsVectorFileWriter::writeAsVectorFormatV3( layer, path, context.transformContext(), options, &error, &newFilename, &newLayer ) != QgsVectorFileWriter::NoError )
260 {
261 feedback->reportError( QObject::tr( "Exporting layer failed: %1" ).arg( error ) );
262 return false;
263 }
264 else
265 {
266 return true;
267 }
268}
269
@ 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.
QVariantMap config() const
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
Base class for all map layer types.
Definition qgsmaplayer.h:76
QString name
Definition qgsmaplayer.h:80
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 value.
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 writeAsVectorFormat()
FieldNameSource fieldNameSource
Source for exported field names.
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.
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 data sets.
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.