From 48d85349674764afef9aa8592b0cdd548160ffdd Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Mon, 23 Nov 2020 16:16:06 +0330 Subject: [PATCH] Spreadsheet: Add support for importing from and exporting to CSV files Closes #4136. --- Applications/Spreadsheet/Spreadsheet.cpp | 46 +++++++++ Applications/Spreadsheet/Spreadsheet.h | 4 + Applications/Spreadsheet/Workbook.cpp | 118 ++++++++++++++--------- 3 files changed, 125 insertions(+), 43 deletions(-) diff --git a/Applications/Spreadsheet/Spreadsheet.cpp b/Applications/Spreadsheet/Spreadsheet.cpp index d31ab7eae4..5a7504bc5c 100644 --- a/Applications/Spreadsheet/Spreadsheet.cpp +++ b/Applications/Spreadsheet/Spreadsheet.cpp @@ -486,6 +486,52 @@ JsonObject Sheet::to_json() const return object; } +Vector> Sheet::to_xsv() const +{ + Vector> data; + + // First row = headers. + data.append(m_columns); + + for (size_t i = 0; i < m_rows; ++i) { + Vector row; + row.resize(m_columns.size()); + for (size_t j = 0; j < m_columns.size(); ++j) { + auto cell = at({ m_columns[j], i }); + if (cell) + row[j] = cell->typed_display(); + } + + data.append(move(row)); + } + + return data; +} + +RefPtr Sheet::from_xsv(const Reader::XSV& xsv, Workbook& workbook) +{ + auto cols = xsv.headers(); + auto rows = xsv.size(); + + auto sheet = adopt(*new Sheet(workbook)); + sheet->m_columns = cols; + for (size_t i = 0; i < rows; ++i) + sheet->add_row(); + + for (auto row : xsv) { + for (size_t i = 0; i < cols.size(); ++i) { + auto str = row[i]; + if (str.is_empty()) + continue; + Position position { cols[i], row.index() }; + auto cell = make(str, position, *sheet); + sheet->m_cells.set(position, move(cell)); + } + } + + return sheet; +} + JsonObject Sheet::gather_documentation() const { JsonObject object; diff --git a/Applications/Spreadsheet/Spreadsheet.h b/Applications/Spreadsheet/Spreadsheet.h index a02ed77740..f9a2c5af2b 100644 --- a/Applications/Spreadsheet/Spreadsheet.h +++ b/Applications/Spreadsheet/Spreadsheet.h @@ -28,6 +28,7 @@ #include "Cell.h" #include "Forward.h" +#include "Readers/XSV.h" #include #include #include @@ -60,6 +61,9 @@ public: JsonObject to_json() const; static RefPtr from_json(const JsonObject&, Workbook&); + Vector> to_xsv() const; + static RefPtr from_xsv(const Reader::XSV&, Workbook&); + const String& name() const { return m_name; } void set_name(const StringView& name) { m_name = name; } diff --git a/Applications/Spreadsheet/Workbook.cpp b/Applications/Spreadsheet/Workbook.cpp index a17b2eb5f3..ca0f932f76 100644 --- a/Applications/Spreadsheet/Workbook.cpp +++ b/Applications/Spreadsheet/Workbook.cpp @@ -26,12 +26,17 @@ #include "Workbook.h" #include "JSIntegration.h" +#include "Readers/CSV.h" +#include "Writers/CSV.h" #include #include #include #include #include +#include #include +#include +#include #include #include #include @@ -77,42 +82,57 @@ Result Workbook::load(const StringView& filename) return sb.to_string(); } - auto json_value_option = JsonParser(file_or_error.value()->read_all()).parse(); - if (!json_value_option.has_value()) { - StringBuilder sb; - sb.append("Failed to parse "); - sb.append(filename); + auto mime = Core::guess_mime_type_based_on_filename(filename); - return sb.to_string(); - } + if (mime == "text/csv") { + // FIXME: Prompt the user for settings. + NonnullRefPtrVector sheets; - auto& json_value = json_value_option.value(); - if (!json_value.is_array()) { - StringBuilder sb; - sb.append("Did not find a spreadsheet in "); - sb.append(filename); + auto sheet = Sheet::from_xsv(Reader::CSV(file_or_error.value()->read_all(), Reader::default_behaviours() | Reader::ParserBehaviour::ReadHeaders), *this); + if (sheet) + sheets.append(sheet.release_nonnull()); - return sb.to_string(); - } + m_sheets.clear(); + m_sheets = move(sheets); + } else { + // Assume JSON. + auto json_value_option = JsonParser(file_or_error.value()->read_all()).parse(); + if (!json_value_option.has_value()) { + StringBuilder sb; + sb.append("Failed to parse "); + sb.append(filename); - NonnullRefPtrVector sheets; + return sb.to_string(); + } + + auto& json_value = json_value_option.value(); + if (!json_value.is_array()) { + StringBuilder sb; + sb.append("Did not find a spreadsheet in "); + sb.append(filename); + + return sb.to_string(); + } + + NonnullRefPtrVector sheets; + + auto& json_array = json_value.as_array(); + json_array.for_each([&](auto& sheet_json) { + if (!sheet_json.is_object()) + return IterationDecision::Continue; + + auto sheet = Sheet::from_json(sheet_json.as_object(), *this); + if (!sheet) + return IterationDecision::Continue; + + sheets.append(sheet.release_nonnull()); - auto& json_array = json_value.as_array(); - json_array.for_each([&](auto& sheet_json) { - if (!sheet_json.is_object()) return IterationDecision::Continue; + }); - auto sheet = Sheet::from_json(sheet_json.as_object(), *this); - if (!sheet) - return IterationDecision::Continue; - - sheets.append(sheet.release_nonnull()); - - return IterationDecision::Continue; - }); - - m_sheets.clear(); - m_sheets = move(sheets); + m_sheets.clear(); + m_sheets = move(sheets); + } set_filename(filename); @@ -121,13 +141,7 @@ Result Workbook::load(const StringView& filename) Result Workbook::save(const StringView& filename) { - JsonArray array; - - for (auto& sheet : m_sheets) - array.append(sheet.to_json()); - - auto file_content = array.to_string(); - + auto mime = Core::guess_mime_type_based_on_filename(filename); auto file = Core::File::construct(filename); file->open(Core::IODevice::WriteOnly); if (!file->is_open()) { @@ -140,14 +154,32 @@ Result Workbook::save(const StringView& filename) return sb.to_string(); } - bool result = file->write(file_content); - if (!result) { - int error_number = errno; - StringBuilder sb; - sb.append("Unable to save file. Error: "); - sb.append(strerror(error_number)); + if (mime == "text/csv") { + // FIXME: Prompt the user for settings and which sheet to export. + Core::OutputFileStream stream { file }; + auto data = m_sheets[0].to_xsv(); + auto header_string = data.take_first(); + Vector headers; + for (auto& str : header_string) + headers.append(str); + Writer::CSV csv { stream, data, headers }; + if (csv.has_error()) + return String::formatted("Unable to save file, CSV writer error: {}", csv.error_string()); + } else { + JsonArray array; + for (auto& sheet : m_sheets) + array.append(sheet.to_json()); - return sb.to_string(); + auto file_content = array.to_string(); + bool result = file->write(file_content); + if (!result) { + int error_number = errno; + StringBuilder sb; + sb.append("Unable to save file. Error: "); + sb.append(strerror(error_number)); + + return sb.to_string(); + } } set_filename(filename);