diff options
author | AnotherTest <ali.mpfard@gmail.com> | 2020-11-23 16:16:06 +0330 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-11-24 21:38:13 +0100 |
commit | 48d85349674764afef9aa8592b0cdd548160ffdd (patch) | |
tree | 6c299836fe996debb14c7ac728c2210111822bb6 | |
parent | 8066a623d90f7e5a716a7f1bc435a80a66c122d2 (diff) | |
download | serenity-48d85349674764afef9aa8592b0cdd548160ffdd.zip |
Spreadsheet: Add support for importing from and exporting to CSV files
Closes #4136.
-rw-r--r-- | Applications/Spreadsheet/Spreadsheet.cpp | 46 | ||||
-rw-r--r-- | Applications/Spreadsheet/Spreadsheet.h | 4 | ||||
-rw-r--r-- | Applications/Spreadsheet/Workbook.cpp | 116 |
3 files changed, 124 insertions, 42 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<Vector<String>> Sheet::to_xsv() const +{ + Vector<Vector<String>> data; + + // First row = headers. + data.append(m_columns); + + for (size_t i = 0; i < m_rows; ++i) { + Vector<String> 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> 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<Cell>(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 <AK/HashMap.h> #include <AK/HashTable.h> #include <AK/String.h> @@ -60,6 +61,9 @@ public: JsonObject to_json() const; static RefPtr<Sheet> from_json(const JsonObject&, Workbook&); + Vector<Vector<String>> to_xsv() const; + static RefPtr<Sheet> 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 <AK/ByteBuffer.h> #include <AK/JsonArray.h> #include <AK/JsonObject.h> #include <AK/JsonObjectSerializer.h> #include <AK/JsonParser.h> +#include <AK/Stream.h> #include <LibCore/File.h> +#include <LibCore/FileStream.h> +#include <LibCore/MimeData.h> #include <LibJS/Parser.h> #include <LibJS/Runtime/GlobalObject.h> #include <string.h> @@ -77,42 +82,57 @@ Result<bool, String> 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<Sheet> 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<Sheet> sheets; + return sb.to_string(); + } - auto& json_array = json_value.as_array(); - json_array.for_each([&](auto& sheet_json) { - if (!sheet_json.is_object()) - return IterationDecision::Continue; + 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_json(sheet_json.as_object(), *this); - if (!sheet) - return IterationDecision::Continue; + return sb.to_string(); + } + + NonnullRefPtrVector<Sheet> sheets; - 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; - return IterationDecision::Continue; - }); + auto sheet = Sheet::from_json(sheet_json.as_object(), *this); + if (!sheet) + return IterationDecision::Continue; - m_sheets.clear(); - m_sheets = move(sheets); + sheets.append(sheet.release_nonnull()); + + return IterationDecision::Continue; + }); + + m_sheets.clear(); + m_sheets = move(sheets); + } set_filename(filename); @@ -121,13 +141,7 @@ Result<bool, String> Workbook::load(const StringView& filename) Result<bool, String> 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<bool, String> 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)); - - return sb.to_string(); + 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<StringView> 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()); + + 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); |