summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnotherTest <ali.mpfard@gmail.com>2020-11-23 16:16:06 +0330
committerAndreas Kling <kling@serenityos.org>2020-11-24 21:38:13 +0100
commit48d85349674764afef9aa8592b0cdd548160ffdd (patch)
tree6c299836fe996debb14c7ac728c2210111822bb6
parent8066a623d90f7e5a716a7f1bc435a80a66c122d2 (diff)
downloadserenity-48d85349674764afef9aa8592b0cdd548160ffdd.zip
Spreadsheet: Add support for importing from and exporting to CSV files
Closes #4136.
-rw-r--r--Applications/Spreadsheet/Spreadsheet.cpp46
-rw-r--r--Applications/Spreadsheet/Spreadsheet.h4
-rw-r--r--Applications/Spreadsheet/Workbook.cpp116
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);