Prechádzať zdrojové kódy

Spreadsheet: Add support for importing from and exporting to CSV files

Closes #4136.
AnotherTest 4 rokov pred
rodič
commit
48d8534967

+ 46 - 0
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;

+ 4 - 0
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; }
 

+ 74 - 42
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);