QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 
28 class FieldValueConverter : public QgsVectorFileWriter::FieldValueConverter
29 {
30  public:
32  : mLayer( vl )
33  {
34  const QStringList formattersAllowList{ QStringLiteral( "KeyValue" ),
35  QStringLiteral( "List" ),
36  QStringLiteral( "ValueRelation" ),
37  QStringLiteral( "ValueMap" ) };
38 
39  for ( int i = 0; i < mLayer->fields().count(); ++i )
40  {
41  const QgsEditorWidgetSetup setup = mLayer->fields().at( i ).editorWidgetSetup();
43  if ( formattersAllowList.contains( fieldFormatter->id() ) )
44  {
45  mFormatters[i] = fieldFormatter;
46  mConfig[i] = setup.config();
47  }
48  }
49  }
50 
51  QgsField fieldDefinition( const QgsField &field ) override
52  {
53  if ( !mLayer )
54  return field;
55 
56  const int idx = mLayer->fields().indexFromName( field.name() );
57  if ( mFormatters.contains( idx ) )
58  {
59  return QgsField( field.name(), QVariant::String );
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 
96 QString QgsExportToSpreadsheetAlgorithm::name() const
97 {
98  return QStringLiteral( "exporttospreadsheet" );
99 }
100 
101 QString QgsExportToSpreadsheetAlgorithm::displayName() const
102 {
103  return QObject::tr( "Export to spreadsheet" );
104 }
105 
106 QStringList QgsExportToSpreadsheetAlgorithm::tags() const
107 {
108  return QObject::tr( "microsoft,excel,xls,xlsx,calc,open,office,libre,ods" ).split( ',' );
109 }
110 
111 QString QgsExportToSpreadsheetAlgorithm::group() const
112 {
113  return QObject::tr( "Layer tools" );
114 }
115 
116 QString QgsExportToSpreadsheetAlgorithm::groupId() const
117 {
118  return QStringLiteral( "layertools" );
119 }
120 
121 void QgsExportToSpreadsheetAlgorithm::initAlgorithm( const QVariantMap & )
122 {
123  addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), QgsProcessing::TypeVector ) );
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 
133 QString QgsExportToSpreadsheetAlgorithm::shortHelpString() const
134 {
135  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 QgsExportToSpreadsheetAlgorithm *QgsExportToSpreadsheetAlgorithm::createInstance() const
140 {
141  return new QgsExportToSpreadsheetAlgorithm();
142 }
143 
144 bool QgsExportToSpreadsheetAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
145 {
146  const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
147  for ( QgsMapLayer *layer : layers )
148  {
149  mLayers.emplace_back( layer->clone() );
150  }
151 
152  if ( mLayers.empty() )
153  feedback->reportError( QObject::tr( "No layers selected, spreadsheet will be empty" ), false );
154 
155  return true;
156 }
157 
158 QVariantMap QgsExportToSpreadsheetAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
159 {
160  const bool overwrite = parameterAsBoolean( parameters, QStringLiteral( "OVERWRITE" ), context );
161  const QString outputPath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
162  if ( outputPath.isEmpty() )
163  throw QgsProcessingException( QObject::tr( "No output file specified." ) );
164 
165  const bool useAlias = parameterAsBoolean( parameters, QStringLiteral( "USE_ALIAS" ), context );
166  const bool formattedValues = parameterAsBoolean( parameters, QStringLiteral( "FORMATTED_VALUES" ), context );
167  bool createNew = true;
168  // delete existing spreadsheet if it exists
169  if ( overwrite && QFile::exists( outputPath ) )
170  {
171  feedback->pushInfo( QObject::tr( "Removing existing file '%1'" ).arg( outputPath ) );
172  if ( !QFile( outputPath ).remove() )
173  {
174  throw QgsProcessingException( QObject::tr( "Could not remove existing file '%1'" ).arg( outputPath ) );
175  }
176  }
177  else if ( QFile::exists( outputPath ) )
178  {
179  createNew = false;
180  }
181 
182  const QFileInfo fi( outputPath );
183  const QString driverName = QgsVectorFileWriter::driverForExtension( fi.suffix() );
184 
185  OGRSFDriverH hDriver = OGRGetDriverByName( driverName.toLocal8Bit().constData() );
186  if ( !hDriver )
187  {
188  if ( driverName == QLatin1String( "ods" ) )
189  throw QgsProcessingException( QObject::tr( "Open Document Spreadsheet driver not found." ) );
190  else
191  throw QgsProcessingException( QObject::tr( "Microsoft Excel driver not found." ) );
192  }
193 
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  bool errored = false;
204 
205  QgsProcessingMultiStepFeedback multiStepFeedback( mLayers.size(), feedback );
206 
207  QStringList outputLayers;
208  int i = 0;
209  for ( const auto &layer : mLayers )
210  {
211  if ( feedback->isCanceled() )
212  break;
213 
214  multiStepFeedback.setCurrentStep( i );
215  i++;
216 
217  if ( !layer )
218  {
219  // don't throw immediately - instead do what we can and error out later
220  feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
221  errored = true;
222  continue;
223  }
224 
225  feedback->pushInfo( QObject::tr( "Exporting layer %1/%2: %3" ).arg( i ).arg( mLayers.size() ).arg( layer ? layer->name() : QString() ) );
226 
227  FieldValueConverter converter( qobject_cast< QgsVectorLayer * >( layer.get() ) );
228 
229  if ( !exportVectorLayer( qobject_cast< QgsVectorLayer * >( layer.get() ), outputPath,
230  context, &multiStepFeedback, driverName, createNew, useAlias, formattedValues ? &converter : nullptr ) )
231  errored = true;
232  else
233  {
234  outputLayers.append( QStringLiteral( "%1|layername=%2" ).arg( outputPath, layer->name() ) );
235  createNew = false;
236  }
237  }
238 
239  if ( errored )
240  throw QgsProcessingException( QObject::tr( "Error obtained while exporting one or more layers." ) );
241 
242  QVariantMap outputs;
243  outputs.insert( QStringLiteral( "OUTPUT" ), outputPath );
244  outputs.insert( QStringLiteral( "OUTPUT_LAYERS" ), outputLayers );
245  return outputs;
246 }
247 
248 bool QgsExportToSpreadsheetAlgorithm::exportVectorLayer( QgsVectorLayer *layer, const QString &path, QgsProcessingContext &context,
249  QgsProcessingFeedback *feedback, const QString &driverName, bool createNew, bool preferAlias, QgsVectorFileWriter::FieldValueConverter *converter )
250 {
252  options.driverName = driverName;
253  options.layerName = layer->name();
255  options.fileEncoding = context.defaultEncoding();
256  options.feedback = feedback;
258  options.fieldValueConverter = converter;
259 
260 
261  QString error;
262  QString newFilename;
263  QString newLayer;
264  if ( QgsVectorFileWriter::writeAsVectorFormatV3( layer, path, context.transformContext(), options, &error, &newFilename, &newLayer ) != QgsVectorFileWriter::NoError )
265  {
266  feedback->reportError( QObject::tr( "Exporting layer failed: %1" ).arg( error ) );
267  return false;
268  }
269  else
270  {
271  return true;
272  }
273 }
274 
formatter
Definition: qgsbasicnumericformat.cpp:24
QgsVectorFileWriter::SaveVectorOptions
Options to pass to writeAsVectorFormat()
Definition: qgsvectorfilewriter.h:458
QgsVectorFileWriter::CreateOrOverwriteFile
@ CreateOrOverwriteFile
Create or overwrite file.
Definition: qgsvectorfilewriter.h:279
QgsEditorWidgetSetup
Holder for the widget type and its configuration for a field.
Definition: qgseditorwidgetsetup.h:28
QgsProcessingFeedback
Base class for providing feedback from a processing algorithm.
Definition: qgsprocessingfeedback.h:37
QgsProcessingFeedback::pushInfo
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
Definition: qgsprocessingfeedback.cpp:77
QgsProcessingFeedback::reportError
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
Definition: qgsprocessingfeedback.cpp:59
gdal::ogr_datasource_unique_ptr
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.
Definition: qgsogrutils.h:119
QgsEditorWidgetSetup::config
QVariantMap config() const
Definition: qgseditorwidgetsetup.h:64
QgsFieldFormatter::id
virtual QString id() const =0
Returns a unique id for this field formatter.
qgsfieldformatterregistry.h
QgsFeedback::isCanceled
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:67
QgsVectorFileWriter::FieldValueConverter::clone
virtual QgsVectorFileWriter::FieldValueConverter * clone() const
Creates a clone of the FieldValueConverter.
Definition: qgsvectorfilewriter.cpp:73
QgsVectorFileWriter::SaveVectorOptions::layerName
QString layerName
Layer name. If let empty, it will be derived from the filename.
Definition: qgsvectorfilewriter.h:470
qgsogrutils.h
QgsVectorFileWriter::PreferAlias
@ PreferAlias
Use the field alias as the exported field name, wherever one is set. Otherwise use the original field...
Definition: qgsvectorfilewriter.h:199
QgsVectorFileWriter::FieldValueConverter
Interface to convert raw field values to their user-friendly value.
Definition: qgsvectorfilewriter.h:218
QgsVectorFileWriter::CreateOrOverwriteLayer
@ CreateOrOverwriteLayer
Create or overwrite layer.
Definition: qgsvectorfilewriter.h:282
field
const QgsField & field
Definition: qgsfield.h:463
QgsProcessingParameterMultipleLayers
A parameter for processing algorithms which accepts multiple map layers.
Definition: qgsprocessingparameters.h:2097
QgsField::name
QString name
Definition: qgsfield.h:60
QgsVectorFileWriter::FieldValueConverter::FieldValueConverter
FieldValueConverter()=default
Constructor.
QgsVectorFileWriter::FieldValueConverter::fieldDefinition
virtual QgsField fieldDefinition(const QgsField &field)
Returns a possibly modified field definition.
Definition: qgsvectorfilewriter.cpp:63
qgsapplication.h
QgsVectorFileWriter::SaveVectorOptions::feedback
QgsFeedback * feedback
Optional feedback object allowing cancellation of layer save.
Definition: qgsvectorfilewriter.h:532
QgsEditorWidgetSetup::type
QString type() const
Definition: qgseditorwidgetsetup.h:59
QgsProcessing::TypeVector
@ TypeVector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:54
QgsProcessingMultiStepFeedback
Processing feedback object for multi-step operations.
Definition: qgsprocessingfeedback.h:166
QgsProcessingContext
Contains information about the context in which a processing algorithm is executed.
Definition: qgsprocessingcontext.h:46
QgsVectorFileWriter::SaveVectorOptions::fileEncoding
QString fileEncoding
Encoding to use.
Definition: qgsvectorfilewriter.h:476
QgsProcessingParameterFileDestination
A generic file based destination parameter, for specifying the destination path for a file (non-map l...
Definition: qgsprocessingparameters.h:3451
QgsProcessingContext::defaultEncoding
QString defaultEncoding() const
Returns the default encoding to use for newly created files.
Definition: qgsprocessingcontext.h:496
QgsProcessingParameterDefinition::setMetadata
void setMetadata(const QVariantMap &metadata)
Sets the parameter's freeform metadata.
Definition: qgsprocessingparameters.h:721
QgsVectorFileWriter::SaveVectorOptions::fieldValueConverter
QgsVectorFileWriter::FieldValueConverter * fieldValueConverter
Field value converter.
Definition: qgsvectorfilewriter.h:529
QgsVectorFileWriter::SaveVectorOptions::fieldNameSource
FieldNameSource fieldNameSource
Source for exported field names.
Definition: qgsvectorfilewriter.h:539
QgsProcessingContext::transformContext
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Definition: qgsprocessingcontext.h:165
QgsVectorFileWriter::driverForExtension
static QString driverForExtension(const QString &extension)
Returns the OGR driver name for a specified file extension.
Definition: qgsvectorfilewriter.cpp:3716
QgsVectorFileWriter::SaveVectorOptions::actionOnExistingFile
QgsVectorFileWriter::ActionOnExistingFile actionOnExistingFile
Action on existing file.
Definition: qgsvectorfilewriter.h:473
QgsApplication::fieldFormatterRegistry
static QgsFieldFormatterRegistry * fieldFormatterRegistry()
Gets the registry of available field formatters.
Definition: qgsapplication.cpp:2480
qgsvectorlayer.h
QgsProcessingFeedback::pushDebugInfo
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
Definition: qgsprocessingfeedback.cpp:95
QgsVectorFileWriter::writeAsVectorFormatV3
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.
Definition: qgsvectorfilewriter.cpp:3454
QgsVectorFileWriter::Original
@ Original
Use original field names.
Definition: qgsvectorfilewriter.h:198
QgsProcessingParameterBoolean
A boolean parameter for processing algorithms.
Definition: qgsprocessingparameters.h:1709
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:391
QgsMapLayer
Base class for all map layer types. This is the base class for all map layer types (vector,...
Definition: qgsmaplayer.h:72
QgsProcessingOutputMultipleLayers
A multi-layer output for processing algorithms which create map layers, when the number and nature of...
Definition: qgsprocessingoutputs.h:268
QgsFieldFormatterRegistry::fieldFormatter
QgsFieldFormatter * fieldFormatter(const QString &id) const
Gets a field formatter by its id.
Definition: qgsfieldformatterregistry.cpp:76
QgsVectorFileWriter::mLayer
OGRLayerH mLayer
Definition: qgsvectorfilewriter.h:924
QgsVectorFileWriter::FieldValueConverter::convert
virtual QVariant convert(int fieldIdxInLayer, const QVariant &value)
Convert the provided value, for field fieldIdxInLayer.
Definition: qgsvectorfilewriter.cpp:68
QgsMapLayer::name
QString name
Definition: qgsmaplayer.h:76
QgsVectorFileWriter::NoError
@ NoError
Definition: qgsvectorfilewriter.h:171
qgsvectorfilewriter.h
QgsVectorFileWriter::SaveVectorOptions::driverName
QString driverName
OGR driver to use.
Definition: qgsvectorfilewriter.h:467
QgsFieldFormatter
A field formatter helps to handle and display values for a field.
Definition: qgsfieldformatter.h:72
qgsalgorithmexporttospreadsheet.h
QgsProcessingException
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
qgsfieldformatter.h
QgsField
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:50