QGIS API Documentation  3.20.0-Odense (decaadbb31)
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  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  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  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  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 
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 SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
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 QString id() const =0
Returns a unique id for this field formatter.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString name
Definition: qgsfield.h:60
Base class for all map layer types.
Definition: qgsmaplayer.h:70
QString name
Definition: qgsmaplayer.h:73
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.
Definition: qgsexception.h:83
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.
@ TypeVector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:54
Interface to convert raw field values to their user-friendly value.
FieldValueConverter()=default
Constructor.
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.
Definition: qgsogrutils.h:116
const QgsField & field
Definition: qgsfield.h:463