From f91773b291cb32f6e425bbccd8168c8578abe0f3 Mon Sep 17 00:00:00 2001 From: Martin Kleusberg Date: Thu, 25 Aug 2016 01:01:46 +0200 Subject: [PATCH] Add basic JSON export feature This adds some basic functionality for exporting JSON files by extending the CSV export dialog. There is still a bit of work required for fine-tuning the JSON export feature though. But a standard export of a table s working well already. See issue #688. --- src/ExportCsvDialog.cpp | 166 +++++++++--- src/ExportCsvDialog.h | 12 +- src/ExportCsvDialog.ui | 516 ++++++++++++++++++++------------------ src/MainWindow.cpp | 19 +- src/MainWindow.h | 1 + src/MainWindow.ui | 43 +++- src/PreferencesDialog.cpp | 4 + src/SqlExecutionArea.cpp | 2 +- 8 files changed, 471 insertions(+), 292 deletions(-) diff --git a/src/ExportCsvDialog.cpp b/src/ExportCsvDialog.cpp index 97fd0f35..ee9b3fb6 100644 --- a/src/ExportCsvDialog.cpp +++ b/src/ExportCsvDialog.cpp @@ -9,27 +9,31 @@ #include #include #include +#if QT_VERSION_MAJOR >= 5 +#include +#include +#include +#endif -ExportCsvDialog::ExportCsvDialog(DBBrowserDB* db, QWidget* parent, const QString& query, const QString& selection) +ExportCsvDialog::ExportCsvDialog(DBBrowserDB* db, ExportFormats format, QWidget* parent, const QString& query, const QString& selection) : QDialog(parent), ui(new Ui::ExportCsvDialog), pdb(db), + m_format(format), m_sQuery(query) { // Create UI ui->setupUi(this); - // Retrieve the saved dialog preferences - bool firstRow = PreferencesDialog::getSettingsValue("exportcsv", "firstrowheader").toBool(); - int separatorChar = PreferencesDialog::getSettingsValue("exportcsv", "separator").toInt(); - int quoteChar = PreferencesDialog::getSettingsValue("exportcsv", "quotecharacter").toInt(); - QString newLineString = PreferencesDialog::getSettingsValue("exportcsv", "newlinecharacters").toString(); + // Show different option widgets depending on the export format + ui->stackFormat->setCurrentIndex(format); - // Set the widget values using the retrieved preferences - ui->checkHeader->setChecked(firstRow); - setSeparatorChar(separatorChar); - setQuoteChar(quoteChar); - setNewLineString(newLineString); + // Retrieve the saved dialog preferences + ui->checkHeader->setChecked(PreferencesDialog::getSettingsValue("exportcsv", "firstrowheader").toBool()); + setSeparatorChar(PreferencesDialog::getSettingsValue("exportcsv", "separator").toInt()); + setQuoteChar(PreferencesDialog::getSettingsValue("exportcsv", "quotecharacter").toInt()); + setNewLineString(PreferencesDialog::getSettingsValue("exportcsv", "newlinecharacters").toString()); + ui->checkPrettyPrint->setChecked(PreferencesDialog::getSettingsValue("exportjson", "prettyprint").toBool()); // Update the visible/hidden status of the "Other" line edit fields showCustomCharEdits(); @@ -39,21 +43,17 @@ ExportCsvDialog::ExportCsvDialog(DBBrowserDB* db, QWidget* parent, const QString { // Get list of tables to export objectMap objects = pdb->getBrowsableObjects(); - for(objectMap::ConstIterator i=objects.begin();i!=objects.end();++i) - { - ui->listTables->addItem(new QListWidgetItem(QIcon(QString(":icons/%1").arg(i.value().gettype())), i.value().getname())); - } + foreach(const DBBrowserObject& obj, objects) + ui->listTables->addItem(new QListWidgetItem(QIcon(QString(":icons/%1").arg(obj.gettype())), obj.getname())); // Sort list of tables and select the table specified in the selection parameter or alternatively the first one ui->listTables->model()->sort(0); if(selection.isEmpty()) { ui->listTables->setCurrentItem(ui->listTables->item(0)); - } - else - { + } else { QList items = ui->listTables->findItems(selection, Qt::MatchExactly); - if (!items.isEmpty()) + if(!items.isEmpty()) ui->listTables->setCurrentItem(items.first()); } } else { @@ -70,6 +70,19 @@ ExportCsvDialog::~ExportCsvDialog() } bool ExportCsvDialog::exportQuery(const QString& sQuery, const QString& sFilename) +{ + switch(m_format) + { + case ExportFormatCsv: + return exportQueryCsv(sQuery, sFilename); + case ExportFormatJson: + return exportQueryJson(sQuery, sFilename); + default: + return false; + } +} + +bool ExportCsvDialog::exportQueryCsv(const QString& sQuery, const QString& sFilename) { // Prepare the quote and separating characters QChar quoteChar = currentQuoteChar(); @@ -158,15 +171,97 @@ bool ExportCsvDialog::exportQuery(const QString& sQuery, const QString& sFilenam return true; } +bool ExportCsvDialog::exportQueryJson(const QString& sQuery, const QString& sFilename) +{ +#if QT_VERSION_MAJOR < 5 + return false; +#else + // Open file + QFile file(sFilename); + if(file.open(QIODevice::WriteOnly)) + { + QByteArray utf8Query = sQuery.toUtf8(); + sqlite3_stmt *stmt; + int status = sqlite3_prepare_v2(pdb->_db, utf8Query.data(), utf8Query.size(), &stmt, NULL); + + QJsonArray json_table; + + if(SQLITE_OK == status) + { + QApplication::setOverrideCursor(Qt::WaitCursor); + int columns = sqlite3_column_count(stmt); + size_t counter = 0; + QList column_names; + while(sqlite3_step(stmt) == SQLITE_ROW) + { + // Get column names if we didn't do so before + if(!column_names.size()) + { + for(int i=0;iprocessEvents(); + counter++; + } + } + + sqlite3_finalize(stmt); + + // Create JSON document + QJsonDocument json_doc; + json_doc.setArray(json_table); + file.write(json_doc.toJson(ui->checkPrettyPrint->isChecked() ? QJsonDocument::Indented : QJsonDocument::Compact)); + + QApplication::restoreOverrideCursor(); + qApp->processEvents(); + + // Done writing the file + file.close(); + } else { + QMessageBox::warning(this, QApplication::applicationName(), + tr("Could not open output file: %1").arg(sFilename)); + return false; + } + + return true; +#endif +} + void ExportCsvDialog::accept() { + QString file_dialog_filter; + QString default_file_extension; + switch(m_format) + { + case ExportFormatCsv: + file_dialog_filter = tr("Text files(*.csv *.txt)"); + default_file_extension = ".csv"; + break; + case ExportFormatJson: + file_dialog_filter = tr("Text files(*.json *.js *.txt)"); + default_file_extension = ".json"; + break; + } + if(!m_sQuery.isEmpty()) { // called from sqlexecute query tab QString sFilename = FileDialog::getSaveFileName( this, tr("Choose a filename to export data"), - tr("Text files(*.csv *.txt)")); + file_dialog_filter); if(sFilename.isEmpty()) { close(); @@ -174,9 +269,7 @@ void ExportCsvDialog::accept() } exportQuery(m_sQuery, sFilename); - } - else - { + } else { // called from the File export menu QList selectedItems = ui->listTables->selectedItems(); @@ -194,8 +287,8 @@ void ExportCsvDialog::accept() QString fileName = FileDialog::getSaveFileName( this, tr("Choose a filename to export data"), - tr("Text files(*.csv *.txt)"), - selectedItems.at(0)->text() + ".csv"); + file_dialog_filter, + selectedItems.at(0)->text() + default_file_extension); if(fileName.isEmpty()) { close(); @@ -203,25 +296,21 @@ void ExportCsvDialog::accept() } filenames << fileName; - } - else - { + } else { // ask for folder - QString csvfolder = FileDialog::getExistingDirectory( + QString exportfolder = FileDialog::getExistingDirectory( this, tr("Choose a directory"), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); - if(csvfolder.isEmpty()) + if(exportfolder.isEmpty()) { close(); return; } - for(QList::iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) - { - filenames << QDir(csvfolder).filePath((*it)->text() + ".csv"); - } + foreach(QListWidgetItem* item, selectedItems) + filenames << QDir(exportfolder).filePath(item->text() + default_file_extension); } // Only if the user hasn't clicked the cancel button @@ -236,10 +325,11 @@ void ExportCsvDialog::accept() } // Save the dialog preferences for future use - PreferencesDialog::setSettingsValue("exportcsv", "firstrowheader", ui->checkHeader->isChecked(), false); - PreferencesDialog::setSettingsValue("exportcsv", "separator", currentSeparatorChar(), false); - PreferencesDialog::setSettingsValue("exportcsv", "quotecharacter", currentQuoteChar(), false); - PreferencesDialog::setSettingsValue("exportcsv", "newlinecharacters", currentNewLineString(), false); + PreferencesDialog::setSettingsValue("exportcsv", "firstrowheader", ui->checkHeader->isChecked()); + PreferencesDialog::setSettingsValue("exportjson", "prettyprint", ui->checkPrettyPrint->isChecked()); + PreferencesDialog::setSettingsValue("exportcsv", "separator", currentSeparatorChar()); + PreferencesDialog::setSettingsValue("exportcsv", "quotecharacter", currentQuoteChar()); + PreferencesDialog::setSettingsValue("exportcsv", "newlinecharacters", currentNewLineString()); // Notify the user the export has completed QMessageBox::information(this, QApplication::applicationName(), tr("Export completed.")); diff --git a/src/ExportCsvDialog.h b/src/ExportCsvDialog.h index 2e1a4600..c9dd40ca 100644 --- a/src/ExportCsvDialog.h +++ b/src/ExportCsvDialog.h @@ -14,7 +14,13 @@ class ExportCsvDialog : public QDialog Q_OBJECT public: - explicit ExportCsvDialog(DBBrowserDB* db, QWidget* parent = 0, const QString& query = "", const QString& selection = ""); + enum ExportFormats + { + ExportFormatCsv, + ExportFormatJson, + }; + + explicit ExportCsvDialog(DBBrowserDB* db, ExportFormats format, QWidget* parent = 0, const QString& query = "", const QString& selection = ""); ~ExportCsvDialog(); private slots: @@ -32,11 +38,15 @@ private: QString currentNewLineString() const; bool exportQuery(const QString& sQuery, const QString& sFilename); + bool exportQueryCsv(const QString& sQuery, const QString& sFilename); + bool exportQueryJson(const QString& sQuery, const QString& sFilename); private: Ui::ExportCsvDialog* ui; DBBrowserDB* pdb; + ExportFormats m_format; + QString m_sQuery; }; diff --git a/src/ExportCsvDialog.ui b/src/ExportCsvDialog.ui index 8fd9f1a5..8b9be9d1 100644 --- a/src/ExportCsvDialog.ui +++ b/src/ExportCsvDialog.ui @@ -6,20 +6,20 @@ 0 0 - 579 - 403 + 527 + 381 Export data as CSV - - - + + + - &Table(s) + Tab&le(s) listTables @@ -42,236 +42,264 @@ - - - - &Column names in first line - - - checkHeader - - - - - - - - - - true - - - - - - - Field &separator - - - comboFieldSeparator - - - - - - - - - - 180 - 0 - - - - - 180 - 16777215 - - - - - , - - - - - ; - - - - - Tab - - - - - | - - - - - Other - - - - - - - - 1 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - &Quote character - - - comboQuoteCharacter - - - - - - - - - - 180 - 0 - - - - - 180 - 16777215 - - - - - " - - - - - ' - - - - - - - - - - Other - - - - - - - - 1 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - New line characters - - - - - - - - - - 180 - 0 - - - - - 180 - 16777215 - - - - - Windows: CR+LF (\r\n) - - - - - Unix: LF (\n) - - - - - Other - - - - - - - - 5 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - + + + + 0 + + + + + + + Colu&mn names in first line + + + checkHeader + + + + + + + + + + true + + + + + + + Fie&ld separator + + + comboFieldSeparator + + + + + + + + + + 180 + 0 + + + + + 180 + 16777215 + + + + + , + + + + + ; + + + + + Tab + + + + + | + + + + + Other + + + + + + + + 1 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + &Quote character + + + comboQuoteCharacter + + + + + + + + + + 180 + 0 + + + + + 180 + 16777215 + + + + + " + + + + + ' + + + + + + + + + + Other + + + + + + + + 1 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + New line characters + + + + + + + + + + 180 + 0 + + + + + 180 + 16777215 + + + + + Windows: CR+LF (\r\n) + + + + + Unix: LF (\n) + + + + + Other + + + + + + + + 5 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + Pretty print + + + checkPrettyPrint + + + + + + + + + + + Qt::Horizontal @@ -300,8 +328,8 @@ accept() - 252 - 136 + 263 + 612 157 @@ -316,8 +344,8 @@ reject() - 320 - 136 + 263 + 612 286 @@ -332,8 +360,8 @@ showCustomCharEdits() - 210 - 64 + 390 + 293 264 @@ -348,8 +376,8 @@ showCustomCharEdits() - 197 - 93 + 390 + 332 270 @@ -364,8 +392,8 @@ showCustomCharEdits() - 231 - 327 + 390 + 371 296 diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index bb66df87..bc83115e 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1053,7 +1053,24 @@ void MainWindow::exportTableToCSV() current_table = ui->comboBrowseTable->currentText(); // Open dialog - ExportCsvDialog dialog(&db, this, "", current_table); + ExportCsvDialog dialog(&db, ExportCsvDialog::ExportFormatCsv, this, "", current_table); + dialog.exec(); +} + +void MainWindow::exportTableToJson() +{ + // Get the current table name if we are in the Browse Data tab + QString current_table; + if(ui->mainTab->currentIndex() == StructureTab) { + QString type = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), 1)).toString(); + if(type == "table" || type == "view") + current_table = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), 0)).toString(); + } + else if(ui->mainTab->currentIndex() == BrowseTab) + current_table = ui->comboBrowseTable->currentText(); + + // Open dialog + ExportCsvDialog dialog(&db, ExportCsvDialog::ExportFormatJson, this, "", current_table); dialog.exec(); } diff --git a/src/MainWindow.h b/src/MainWindow.h index 34fafbb5..8c2def8e 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -195,6 +195,7 @@ private slots: void executeQuery(); void importTableFromCSV(); void exportTableToCSV(); + void exportTableToJson(); void fileSave(); void fileRevert(); void exportDatabaseToSQL(); diff --git a/src/MainWindow.ui b/src/MainWindow.ui index 651ef375..e4e79a37 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -337,8 +337,8 @@ 0 0 - 552 - 423 + 473 + 558 @@ -823,7 +823,7 @@ 0 0 1037 - 21 + 29 @@ -843,6 +843,7 @@ + @@ -919,7 +920,7 @@ - Edit Database Cell + Edit Database &Cell 2 @@ -1306,7 +1307,7 @@ - DB Schema + DB Sche&ma 2 @@ -1643,7 +1644,7 @@ :/icons/whatis:/icons/whatis - W&hat's This? + W&hat's This? Shift+F1 @@ -1940,12 +1941,23 @@ :/icons/browser_open:/icons/browser_open - SQLCipher FAQ... + SQLCipher &FAQ... Opens the SQLCipher FAQ in a browser window + + + Table(s) to JSON... + + + Export one or more table(s) to a JSON file + + + QAction::NoRole + + @@ -2976,6 +2988,22 @@ + + fileExportJsonAction + triggered() + MainWindow + exportTableToJson() + + + -1 + -1 + + + 518 + 314 + + + fileOpen() @@ -3038,5 +3066,6 @@ browseDataSetDefaultTableEncoding() browseDataFetchAllData() dittoRecord() + exportTableToJson() diff --git a/src/PreferencesDialog.cpp b/src/PreferencesDialog.cpp index 1a097aa2..1a9cfa9c 100644 --- a/src/PreferencesDialog.cpp +++ b/src/PreferencesDialog.cpp @@ -254,6 +254,10 @@ QVariant PreferencesDialog::getSettingsDefaultValue(const QString& group, const if(group == "exportcsv" && name == "quotecharacter") return '"'; + // exportjson/prettyprint? + if(group == "exportjson" && name == "prettyprint") + return true; + // MainWindow/geometry? if(group == "MainWindow" && name == "geometry") return ""; diff --git a/src/SqlExecutionArea.cpp b/src/SqlExecutionArea.cpp index 1000f7c5..9a0fbcaa 100644 --- a/src/SqlExecutionArea.cpp +++ b/src/SqlExecutionArea.cpp @@ -84,7 +84,7 @@ void SqlExecutionArea::enableSaveButton(bool enable) void SqlExecutionArea::saveAsCsv() { - ExportCsvDialog dialog(db, this, model->query()); + ExportCsvDialog dialog(db, ExportCsvDialog::ExportFormatCsv, this, model->query()); dialog.exec(); }