summaryrefslogtreecommitdiffstats
path: root/src/spreadsheet/document.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/spreadsheet/document.cpp')
-rw-r--r--src/spreadsheet/document.cpp526
1 files changed, 526 insertions, 0 deletions
diff --git a/src/spreadsheet/document.cpp b/src/spreadsheet/document.cpp
new file mode 100644
index 0000000..dc8daec
--- /dev/null
+++ b/src/spreadsheet/document.cpp
@@ -0,0 +1,526 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "document_impl.hpp"
+#include "debug_state_dumper.hpp"
+
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <map>
+#include <algorithm>
+
+using namespace std;
+
+namespace orcus { namespace spreadsheet {
+
+namespace {
+
+class find_sheet_by_name
+{
+ std::string_view m_name;
+public:
+ find_sheet_by_name(std::string_view name) : m_name(name) {}
+ bool operator() (const std::unique_ptr<detail::sheet_item>& v) const
+ {
+ return v->name == m_name;
+ }
+};
+
+}
+
+document::document(const range_size_t& sheet_size) : mp_impl(std::make_unique<detail::document_impl>(*this, sheet_size)) {}
+
+document::~document() {}
+
+shared_strings& document::get_shared_strings()
+{
+ return mp_impl->ss_store;
+}
+
+const shared_strings& document::get_shared_strings() const
+{
+ return mp_impl->ss_store;
+}
+
+styles& document::get_styles()
+{
+ return mp_impl->styles_store;
+}
+
+const styles& document::get_styles() const
+{
+ return mp_impl->styles_store;
+}
+
+pivot_collection& document::get_pivot_collection()
+{
+ return mp_impl->pivots;
+}
+
+const pivot_collection& document::get_pivot_collection() const
+{
+ return mp_impl->pivots;
+}
+
+ixion::model_context& document::get_model_context()
+{
+ return mp_impl->context;
+}
+
+const ixion::model_context& document::get_model_context() const
+{
+ return mp_impl->context;
+}
+
+const document_config& document::get_config() const
+{
+ return mp_impl->doc_config;
+}
+
+void document::set_config(const document_config& cfg)
+{
+ mp_impl->doc_config = cfg;
+ ixion::config ixion_cfg = mp_impl->context.get_config();
+ ixion_cfg.output_precision = cfg.output_precision;
+ mp_impl->context.set_config(ixion_cfg);
+}
+
+string_pool& document::get_string_pool()
+{
+ return mp_impl->string_pool_store;
+}
+
+const string_pool& document::get_string_pool() const
+{
+ return mp_impl->string_pool_store;
+}
+
+void document::insert_table(table_t* p)
+{
+ if (!p)
+ return;
+
+ std::string_view name = p->name;
+ mp_impl->tables.emplace(name, std::unique_ptr<table_t>(p));
+}
+
+const table_t* document::get_table(std::string_view name) const
+{
+ auto it = mp_impl->tables.find(name);
+ return it == mp_impl->tables.end() ? nullptr : it->second.get();
+}
+
+void document::finalize_import()
+{
+ std::for_each(mp_impl->sheets.begin(), mp_impl->sheets.end(),
+ [](std::unique_ptr<detail::sheet_item>& sh)
+ {
+ sh->data.finalize_import();
+ }
+ );
+
+ mp_impl->styles_store.finalize_import();
+}
+
+sheet* document::append_sheet(std::string_view sheet_name)
+{
+ std::string_view sheet_name_safe = mp_impl->string_pool_store.intern(sheet_name).first;
+ sheet_t sheet_index = static_cast<sheet_t>(mp_impl->sheets.size());
+
+ mp_impl->sheets.push_back(
+ std::make_unique<detail::sheet_item>(*this, sheet_name_safe, sheet_index));
+
+ mp_impl->context.append_sheet(std::string{sheet_name_safe});
+
+ return &mp_impl->sheets.back()->data;
+}
+
+sheet* document::get_sheet(std::string_view sheet_name)
+{
+ const sheet* sh = const_cast<const document*>(this)->get_sheet(sheet_name);
+ return const_cast<sheet*>(sh);
+}
+
+const sheet* document::get_sheet(std::string_view sheet_name) const
+{
+ auto it = std::find_if(
+ mp_impl->sheets.begin(), mp_impl->sheets.end(), find_sheet_by_name(sheet_name));
+
+ if (it == mp_impl->sheets.end())
+ return nullptr;
+
+ return &(*it)->data;
+}
+
+sheet* document::get_sheet(sheet_t sheet_pos)
+{
+ const sheet* sh = const_cast<const document*>(this)->get_sheet(sheet_pos);
+ return const_cast<sheet*>(sh);
+}
+
+const sheet* document::get_sheet(sheet_t sheet_pos) const
+{
+ if (static_cast<size_t>(sheet_pos) >= mp_impl->sheets.size())
+ return nullptr;
+
+ return &mp_impl->sheets[sheet_pos]->data;
+}
+
+void document::recalc_formula_cells()
+{
+ ixion::abs_range_set_t empty;
+
+ ixion::model_context& cxt = get_model_context();
+ std::vector<ixion::abs_range_t> sorted = ixion::query_and_sort_dirty_cells(
+ cxt, empty, &mp_impl->dirty_cells);
+ ixion::calculate_sorted_cells(cxt, sorted, 0);
+}
+
+void document::clear()
+{
+ mp_impl = std::make_unique<detail::document_impl>(*this, get_sheet_size());
+}
+
+void document::dump(dump_format_t format, const std::string& output) const
+{
+ if (format == dump_format_t::none)
+ return;
+
+ if (format == dump_format_t::check)
+ {
+ // For this output, we write to a single file.
+ std::ostream* ostrm = &std::cout;
+ std::unique_ptr<std::ofstream> fs;
+
+ if (!output.empty())
+ {
+ if (fs::is_directory(output))
+ {
+ std::ostringstream os;
+ os << "Output file path points to an existing directory.";
+ throw std::invalid_argument(os.str());
+ }
+
+ // Output to stdout when output path is not given.
+ fs = std::make_unique<std::ofstream>(output.data());
+ ostrm = fs.get();
+ }
+
+ dump_check(*ostrm);
+ return;
+ }
+
+ if (output.empty())
+ throw std::invalid_argument("No output directory.");
+
+ if (fs::exists(output))
+ {
+ if (!fs::is_directory(output))
+ {
+ std::ostringstream os;
+ os << "A file named '" << output << "' already exists, and is not a directory.";
+ throw std::invalid_argument(os.str());
+ }
+ }
+ else
+ fs::create_directory(output);
+
+ switch (format)
+ {
+ case dump_format_t::csv:
+ dump_csv(output);
+ break;
+ case dump_format_t::flat:
+ dump_flat(output);
+ break;
+ case dump_format_t::html:
+ dump_html(output);
+ break;
+ case dump_format_t::json:
+ dump_json(output);
+ break;
+ case dump_format_t::debug_state:
+ dump_debug_state(output);
+ break;
+ // coverity[dead_error_line] - following conditions exist to avoid compiler warning
+ case dump_format_t::none:
+ case dump_format_t::unknown:
+ break;
+ default:
+ ;
+ }
+}
+
+void document::dump_check(ostream& os) const
+{
+ for (const std::unique_ptr<detail::sheet_item>& sheet : mp_impl->sheets)
+ sheet->data.dump_check(os, sheet->name);
+}
+
+void document::dump_flat(const string& outdir) const
+{
+ cout << "----------------------------------------------------------------------" << endl;
+ cout << " Document content summary" << endl;
+ cout << "----------------------------------------------------------------------" << endl;
+ mp_impl->ss_store.dump(cout);
+
+ cout << "number of sheets: " << mp_impl->sheets.size() << endl;
+
+ for (const std::unique_ptr<detail::sheet_item>& sheet : mp_impl->sheets)
+ {
+ fs::path outpath{outdir};
+ outpath /= std::string{sheet->name};
+ outpath.replace_extension(".txt");
+
+ std::ofstream file(outpath.native());
+ if (!file)
+ {
+ cerr << "failed to create file: " << outpath << endl;
+ return;
+ }
+
+ file << "---" << endl;
+ file << "Sheet name: " << sheet->name << endl;
+ sheet->data.dump_flat(file);
+ }
+}
+
+void document::dump_html(const string& outdir) const
+{
+ for (const std::unique_ptr<detail::sheet_item>& sheet : mp_impl->sheets)
+ {
+ fs::path outpath{outdir};
+ outpath /= std::string{sheet->name};
+ outpath.replace_extension(".html");
+
+ std::ofstream file(outpath.native());
+ if (!file)
+ {
+ cerr << "failed to create file: " << outpath << endl;
+ return;
+ }
+
+ sheet->data.dump_html(file);
+ }
+}
+
+void document::dump_json(const string& outdir) const
+{
+ for (const std::unique_ptr<detail::sheet_item>& sheet : mp_impl->sheets)
+ {
+ fs::path outpath{outdir};
+ outpath /= std::string{sheet->name};
+ outpath.replace_extension(".json");
+
+ std::ofstream file(outpath.native());
+ if (!file)
+ {
+ cerr << "failed to create file: " << outpath << endl;
+ return;
+ }
+
+ sheet->data.dump_json(file);
+ }
+}
+
+void document::dump_csv(const std::string& outdir) const
+{
+ for (const std::unique_ptr<detail::sheet_item>& sheet : mp_impl->sheets)
+ {
+ fs::path outpath{outdir};
+ outpath /= std::string{sheet->name};
+ outpath.replace_extension(".csv");
+
+ ofstream file(outpath.c_str());
+ if (!file)
+ {
+ cerr << "failed to create file: " << outpath << endl;
+ return;
+ }
+
+ sheet->data.dump_csv(file);
+ }
+}
+
+void document::dump_debug_state(const std::string& outdir) const
+{
+ detail::doc_debug_state_dumper dumper{*mp_impl};
+ fs::path output_dir{outdir};
+ dumper.dump(output_dir);
+
+ for (const std::unique_ptr<detail::sheet_item>& sheet : mp_impl->sheets)
+ {
+ fs::path outpath = output_dir;
+ outpath /= std::string{sheet->name};
+ fs::create_directories(outpath);
+ sheet->data.dump_debug_state(outpath.string(), sheet->name);
+ }
+}
+
+sheet_t document::get_sheet_index(std::string_view name) const
+{
+ auto it = std::find_if(
+ mp_impl->sheets.begin(), mp_impl->sheets.end(), find_sheet_by_name(name));
+
+ if (it == mp_impl->sheets.end())
+ return ixion::invalid_sheet;
+
+ auto it_beg = mp_impl->sheets.begin();
+ size_t pos = std::distance(it_beg, it);
+ return static_cast<sheet_t>(pos);
+}
+
+std::string_view document::get_sheet_name(sheet_t sheet_pos) const
+{
+ if (sheet_pos < 0)
+ return std::string_view{};
+
+ size_t pos = static_cast<size_t>(sheet_pos);
+ if (pos >= mp_impl->sheets.size())
+ return std::string_view{};
+
+ return mp_impl->sheets[pos]->name;
+}
+
+void document::set_sheet_name(sheet_t sheet_pos, std::string name)
+{
+ assert(mp_impl->sheets.size() == mp_impl->context.get_sheet_count());
+
+ std::string_view name_interned = mp_impl->string_pool_store.intern(name).first;
+ mp_impl->context.set_sheet_name(sheet_pos, std::move(name)); // will throw on invalid name or position
+ mp_impl->sheets[sheet_pos]->name = name_interned;
+}
+
+range_size_t document::get_sheet_size() const
+{
+ ixion::rc_size_t ss = mp_impl->context.get_sheet_size();
+ range_size_t ret;
+ ret.rows = ss.row;
+ ret.columns = ss.column;
+ return ret;
+}
+
+void document::set_sheet_size(const range_size_t& sheet_size)
+{
+ mp_impl->context.set_sheet_size({sheet_size.rows, sheet_size.columns});
+}
+
+size_t document::get_sheet_count() const
+{
+ return mp_impl->sheets.size();
+}
+
+void document::set_origin_date(int year, int month, int day)
+{
+ mp_impl->origin_date.year = year;
+ mp_impl->origin_date.month = month;
+ mp_impl->origin_date.day = day;
+}
+
+date_time_t document::get_origin_date() const
+{
+ return mp_impl->origin_date;
+}
+
+void document::set_formula_grammar(formula_grammar_t grammar)
+{
+ if (mp_impl->grammar == grammar)
+ return;
+
+ mp_impl->grammar = grammar;
+
+ ixion::formula_name_resolver_t resolver_type_global = ixion::formula_name_resolver_t::unknown;
+ ixion::formula_name_resolver_t resolver_type_named_exp_base = ixion::formula_name_resolver_t::unknown;
+ ixion::formula_name_resolver_t resolver_type_named_range = ixion::formula_name_resolver_t::unknown;
+ char arg_sep = 0;
+
+ switch (mp_impl->grammar)
+ {
+ case formula_grammar_t::xls_xml:
+ resolver_type_global = ixion::formula_name_resolver_t::excel_r1c1;
+ arg_sep = ',';
+ break;
+ case formula_grammar_t::xlsx:
+ resolver_type_global = ixion::formula_name_resolver_t::excel_a1;
+ arg_sep = ',';
+ break;
+ case formula_grammar_t::ods:
+ resolver_type_global = ixion::formula_name_resolver_t::odff;
+ resolver_type_named_exp_base = ixion::formula_name_resolver_t::calc_a1;
+ resolver_type_named_range = ixion::formula_name_resolver_t::odf_cra;
+ arg_sep = ';';
+ break;
+ case formula_grammar_t::gnumeric:
+ // TODO : Use Excel A1 name resolver for now.
+ resolver_type_global = ixion::formula_name_resolver_t::excel_a1;
+ arg_sep = ',';
+ break;
+ default:
+ ;
+ }
+
+ mp_impl->name_resolver_global.reset();
+ mp_impl->name_resolver_named_exp_base.reset();
+
+ if (resolver_type_global != ixion::formula_name_resolver_t::unknown)
+ {
+ mp_impl->name_resolver_global =
+ ixion::formula_name_resolver::get(resolver_type_global, &mp_impl->context);
+
+ if (resolver_type_named_exp_base != ixion::formula_name_resolver_t::unknown)
+ {
+ mp_impl->name_resolver_named_exp_base =
+ ixion::formula_name_resolver::get(resolver_type_named_exp_base, &mp_impl->context);
+ }
+
+ if (resolver_type_named_range != ixion::formula_name_resolver_t::unknown)
+ {
+ mp_impl->name_resolver_named_range =
+ ixion::formula_name_resolver::get(resolver_type_named_range, &mp_impl->context);
+ }
+
+ ixion::config cfg = mp_impl->context.get_config();
+ cfg.sep_function_arg = arg_sep;
+ cfg.output_precision = mp_impl->doc_config.output_precision;
+ mp_impl->context.set_config(cfg);
+ }
+}
+
+formula_grammar_t document::get_formula_grammar() const
+{
+ return mp_impl->grammar;
+}
+
+const ixion::formula_name_resolver* document::get_formula_name_resolver(formula_ref_context_t cxt) const
+{
+ switch (cxt)
+ {
+ case formula_ref_context_t::global:
+ return mp_impl->name_resolver_global.get();
+ case formula_ref_context_t::named_expression_base:
+ if (mp_impl->name_resolver_named_exp_base)
+ return mp_impl->name_resolver_named_exp_base.get();
+ break;
+ case formula_ref_context_t::named_range:
+ if (mp_impl->name_resolver_named_range)
+ return mp_impl->name_resolver_named_range.get();
+ break;
+ default:
+ ;
+ }
+
+ return mp_impl->name_resolver_global.get();
+}
+
+void document::insert_dirty_cell(const ixion::abs_address_t& pos)
+{
+ mp_impl->dirty_cells.insert(pos);
+}
+
+}}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */