summaryrefslogtreecommitdiffstats
path: root/src/spreadsheet/sheet.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/spreadsheet/sheet.cpp')
-rw-r--r--src/spreadsheet/sheet.cpp555
1 files changed, 555 insertions, 0 deletions
diff --git a/src/spreadsheet/sheet.cpp b/src/spreadsheet/sheet.cpp
new file mode 100644
index 0000000..4ffa8df
--- /dev/null
+++ b/src/spreadsheet/sheet.cpp
@@ -0,0 +1,555 @@
+/* -*- 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 "orcus/spreadsheet/sheet.hpp"
+#include "orcus/spreadsheet/document.hpp"
+#include "orcus/exception.hpp"
+
+#include "json_dumper.hpp"
+#include "check_dumper.hpp"
+#include "csv_dumper.hpp"
+#include "flat_dumper.hpp"
+#include "html_dumper.hpp"
+#include "sheet_impl.hpp"
+#include "debug_state_dumper.hpp"
+
+#include <iostream>
+#include <algorithm>
+#include <vector>
+#include <cassert>
+#include <cstdlib>
+
+#include <ixion/exceptions.hpp>
+#include <ixion/formula.hpp>
+#include <ixion/model_context.hpp>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/date_time/gregorian/greg_date.hpp>
+
+#include "filesystem_env.hpp"
+
+#define ORCUS_DEBUG_SHEET 0
+
+using namespace std;
+namespace gregorian = boost::gregorian;
+namespace posix_time = boost::posix_time;
+
+namespace orcus { namespace spreadsheet {
+
+namespace {
+
+ixion::abs_range_t to_ixion_range(sheet_t sheet, const range_t& range)
+{
+ ixion::abs_range_t pos;
+
+ pos.first.sheet = sheet;
+ pos.first.row = range.first.row;
+ pos.first.column = range.first.column;
+ pos.last.sheet = sheet;
+ pos.last.row = range.last.row;
+ pos.last.column = range.last.column;
+
+ return pos;
+}
+
+}
+
+const row_t sheet::max_row_limit = 1048575;
+const col_t sheet::max_col_limit = 1023;
+
+sheet::sheet(document& doc, sheet_t sheet_index) :
+ mp_impl(std::make_unique<detail::sheet_impl>(doc, *this, sheet_index)) {}
+
+sheet::~sheet() noexcept
+{
+}
+
+void sheet::set_auto(row_t row, col_t col, std::string_view s)
+{
+ if (s.empty())
+ return;
+
+ ixion::model_context& cxt = mp_impl->doc.get_model_context();
+
+ // First, see if this can be parsed as a number.
+ char* endptr = nullptr;
+ double val = strtod(s.data(), &endptr);
+ const char* endptr_check = s.data() + s.size();
+ if (endptr == endptr_check)
+ // Treat this as a numeric value.
+ cxt.set_numeric_cell(ixion::abs_address_t(mp_impl->sheet_id,row,col), val);
+ else
+ // Treat this as a string value.
+ cxt.set_string_cell(ixion::abs_address_t(mp_impl->sheet_id,row,col), s);
+}
+
+void sheet::set_string(row_t row, col_t col, string_id_t sindex)
+{
+ ixion::model_context& cxt = mp_impl->doc.get_model_context();
+ cxt.set_string_cell(ixion::abs_address_t(mp_impl->sheet_id,row,col), sindex);
+
+#if ORCUS_DEBUG_SHEET
+ cout << "sheet::set_string: sheet=" << mp_impl->sheet_id << "; row=" << row << "; col=" << col << "; si=" << sindex << endl;
+#endif
+}
+
+void sheet::set_value(row_t row, col_t col, double value)
+{
+ ixion::model_context& cxt = mp_impl->doc.get_model_context();
+ cxt.set_numeric_cell(ixion::abs_address_t(mp_impl->sheet_id,row,col), value);
+}
+
+void sheet::set_bool(row_t row, col_t col, bool value)
+{
+ ixion::model_context& cxt = mp_impl->doc.get_model_context();
+ cxt.set_boolean_cell(ixion::abs_address_t(mp_impl->sheet_id,row,col), value);
+}
+
+void sheet::set_date_time(row_t row, col_t col, int year, int month, int day, int hour, int minute, double second)
+{
+ // Convert this to a double value representing days since epoch.
+
+ date_time_t dt_origin = mp_impl->doc.get_origin_date();
+
+ gregorian::date origin(dt_origin.year, dt_origin.month, dt_origin.day);
+ gregorian::date d(year, month, day);
+
+ double days_since_epoch = (d - origin).days();
+
+ long ms = second * 1000000.0;
+
+ posix_time::time_duration t(
+ posix_time::hours(hour) +
+ posix_time::minutes(minute) +
+ posix_time::microseconds(ms)
+ );
+
+ double time_as_day = t.total_microseconds();
+ time_as_day /= 1000000.0; // microseconds to seconds
+ time_as_day /= 60.0 * 60.0 * 24.0; // seconds to day
+
+ set_value(row, col, days_since_epoch + time_as_day);
+}
+
+void sheet::set_format(row_t row, col_t col, size_t index)
+{
+ set_format(row, col, row, col, index);
+}
+
+void sheet::set_format(row_t row_start, col_t col_start, row_t row_end, col_t col_end, size_t index)
+{
+ for (col_t col = col_start; col <= col_end; ++col)
+ {
+ auto itr = mp_impl->cell_formats.find(col);
+ if (itr == mp_impl->cell_formats.end())
+ {
+ auto p = std::make_unique<detail::segment_row_index_type>(0, mp_impl->doc.get_sheet_size().rows, 0);
+ auto r = mp_impl->cell_formats.emplace(col, std::move(p));
+
+ if (!r.second)
+ {
+ cerr << "insertion of new cell format container failed!" << endl;
+ return;
+ }
+
+ itr = r.first;
+ }
+
+ detail::segment_row_index_type& con = *itr->second;
+ con.insert_back(row_start, row_end+1, index);
+ }
+}
+
+void sheet::set_column_format(col_t col, col_t col_span, std::size_t index)
+{
+ if (col_span > 0)
+ mp_impl->column_formats.insert_back(col, col + col_span, index);
+}
+
+void sheet::set_row_format(row_t row, std::size_t index)
+{
+ mp_impl->row_formats.insert_back(row, row+1, index);
+}
+
+void sheet::set_formula(row_t row, col_t col, const ixion::formula_tokens_store_ptr_t& tokens)
+{
+ ixion::model_context& cxt = mp_impl->doc.get_model_context();
+ ixion::abs_address_t pos(mp_impl->sheet_id, row, col);
+
+ cxt.set_formula_cell(pos, tokens);
+ try
+ {
+ ixion::register_formula_cell(cxt, pos);
+ mp_impl->doc.insert_dirty_cell(pos);
+ }
+ catch ([[maybe_unused]] const ixion::formula_registration_error& e)
+ {
+#if ORCUS_DEBUG_SHEET
+ cout << "sheet::set_formula: sheet=" << mp_impl->sheet_id << "; row=" << row << "; col=" << col << "; e=" << e.what() << endl;
+#endif
+ }
+}
+
+void sheet::set_formula(
+ row_t row, col_t col, const ixion::formula_tokens_store_ptr_t& tokens,
+ ixion::formula_result result)
+{
+ ixion::model_context& cxt = mp_impl->doc.get_model_context();
+ ixion::abs_address_t pos(mp_impl->sheet_id, row, col);
+
+ cxt.set_formula_cell(pos, tokens, result);
+
+ try
+ {
+ ixion::register_formula_cell(cxt, pos);
+ mp_impl->doc.insert_dirty_cell(pos);
+ }
+ catch ([[maybe_unused]] const ixion::formula_registration_error& e)
+ {
+#if ORCUS_DEBUG_SHEET
+ cout << "sheet::set_formula: sheet=" << mp_impl->sheet_id << "; row=" << row << "; col=" << col << "; e=" << e.what() << endl;
+#endif
+ }
+}
+
+void sheet::set_grouped_formula(const range_t& range, ixion::formula_tokens_t tokens)
+{
+ ixion::abs_range_t pos = to_ixion_range(mp_impl->sheet_id, range);
+ ixion::model_context& cxt = mp_impl->doc.get_model_context();
+
+ cxt.set_grouped_formula_cells(pos, std::move(tokens));
+ try
+ {
+ ixion::register_formula_cell(cxt, pos.first);
+ mp_impl->doc.insert_dirty_cell(pos.first);
+ }
+ catch ([[maybe_unused]] const ixion::formula_registration_error& e)
+ {
+#if ORCUS_DEBUG_SHEET
+ cout << "sheet::set_formula: sheet=" << mp_impl->sheet_id << "; range=" << range << "; e=" << e.what() << endl;
+#endif
+ }
+}
+
+void sheet::set_grouped_formula(const range_t& range, ixion::formula_tokens_t tokens, ixion::formula_result result)
+{
+ ixion::abs_range_t pos = to_ixion_range(mp_impl->sheet_id, range);
+ ixion::model_context& cxt = mp_impl->doc.get_model_context();
+
+ cxt.set_grouped_formula_cells(pos, std::move(tokens), std::move(result));
+ try
+ {
+ ixion::register_formula_cell(cxt, pos.first);
+ mp_impl->doc.insert_dirty_cell(pos.first);
+ }
+ catch ([[maybe_unused]] const ixion::formula_registration_error& e)
+ {
+#if ORCUS_DEBUG_SHEET
+ cout << "sheet::set_formula: sheet=" << mp_impl->sheet_id << "; range=" << range << "; e=" << e.what() << endl;
+#endif
+ }
+}
+
+void sheet::set_col_width(col_t col, col_t col_span, col_width_t width)
+{
+ mp_impl->col_width_pos =
+ mp_impl->col_widths.insert(mp_impl->col_width_pos, col, col+col_span, width).first;
+}
+
+col_width_t sheet::get_col_width(col_t col, col_t* col_start, col_t* col_end) const
+{
+ detail::col_widths_store_type& col_widths = mp_impl->col_widths;
+ if (!col_widths.is_tree_valid())
+ col_widths.build_tree();
+
+ col_width_t ret = 0;
+ if (!col_widths.search_tree(col, ret, col_start, col_end).second)
+ throw orcus::general_error("sheet::get_col_width: failed to search tree.");
+
+ return ret;
+}
+
+void sheet::set_col_hidden(col_t col, col_t col_span, bool hidden)
+{
+ mp_impl->col_hidden_pos =
+ mp_impl->col_hidden.insert(mp_impl->col_hidden_pos, col, col+col_span, hidden).first;
+}
+
+bool sheet::is_col_hidden(col_t col, col_t* col_start, col_t* col_end) const
+{
+ detail::col_hidden_store_type& col_hidden = mp_impl->col_hidden;
+ if (!col_hidden.is_tree_valid())
+ col_hidden.build_tree();
+
+ bool hidden = false;
+ if (!col_hidden.search_tree(col, hidden, col_start, col_end).second)
+ throw orcus::general_error("sheet::is_col_hidden: failed to search tree.");
+
+ return hidden;
+}
+
+void sheet::set_row_height(row_t row, row_height_t height)
+{
+ mp_impl->row_height_pos =
+ mp_impl->row_heights.insert(mp_impl->row_height_pos, row, row+1, height).first;
+}
+
+row_height_t sheet::get_row_height(row_t row, row_t* row_start, row_t* row_end) const
+{
+ detail::row_heights_store_type& row_heights = mp_impl->row_heights;
+ if (!row_heights.is_tree_valid())
+ row_heights.build_tree();
+
+ row_height_t ret = 0;
+ if (!row_heights.search_tree(row, ret, row_start, row_end).second)
+ throw orcus::general_error("sheet::get_row_height: failed to search tree.");
+
+ return ret;
+}
+
+void sheet::set_row_hidden(row_t row, bool hidden)
+{
+ mp_impl->row_hidden_pos =
+ mp_impl->row_hidden.insert(mp_impl->row_hidden_pos, row, row+1, hidden).first;
+}
+
+bool sheet::is_row_hidden(row_t row, row_t* row_start, row_t* row_end) const
+{
+ detail::row_hidden_store_type& row_hidden = mp_impl->row_hidden;
+ if (!row_hidden.is_tree_valid())
+ row_hidden.build_tree();
+
+ bool hidden = false;
+ if (!row_hidden.search_tree(row, hidden, row_start, row_end).second)
+ throw orcus::general_error("sheet::is_row_hidden: failed to search tree.");
+
+ return hidden;
+}
+
+void sheet::set_merge_cell_range(const range_t& range)
+{
+ detail::col_merge_size_type::iterator it_col = mp_impl->merge_ranges.find(range.first.column);
+ if (it_col == mp_impl->merge_ranges.end())
+ {
+ auto p = std::make_unique<detail::merge_size_type>();
+ pair<detail::col_merge_size_type::iterator, bool> r =
+ mp_impl->merge_ranges.insert(
+ detail::col_merge_size_type::value_type(range.first.column, std::move(p)));
+
+ if (!r.second)
+ // Insertion failed.
+ return;
+
+ it_col = r.first;
+ }
+
+ detail::merge_size_type& col_data = *it_col->second;
+ detail::merge_size sz(range.last.column-range.first.column+1, range.last.row-range.first.row+1);
+ col_data.insert(
+ detail::merge_size_type::value_type(range.first.row, sz));
+}
+
+void sheet::fill_down_cells(row_t src_row, col_t src_col, row_t range_size)
+{
+ ixion::model_context& cxt = mp_impl->doc.get_model_context();
+ ixion::abs_address_t src_pos(mp_impl->sheet_id, src_row, src_col);
+ cxt.fill_down_cells(src_pos, range_size);
+}
+
+range_t sheet::get_merge_cell_range(row_t row, col_t col) const
+{
+ range_t ret;
+ ret.first.column = col;
+ ret.first.row = row;
+ ret.last.column = col;
+ ret.last.row = row;
+
+ detail::col_merge_size_type::const_iterator it_col = mp_impl->merge_ranges.find(col);
+ if (it_col == mp_impl->merge_ranges.end())
+ return ret; // not a merged cell
+
+ const detail::merge_size_type& col_data = *it_col->second;
+ detail::merge_size_type::const_iterator it = col_data.find(row);
+ if (it == col_data.end())
+ return ret; // not a merged cell
+
+ const detail::merge_size& ms = it->second;
+ ret.last.column += ms.width - 1;
+ ret.last.row += ms.height - 1;
+
+ return ret;
+}
+
+size_t sheet::get_string_identifier(row_t row, col_t col) const
+{
+ const ixion::model_context& cxt = mp_impl->doc.get_model_context();
+ return cxt.get_string_identifier(ixion::abs_address_t(mp_impl->sheet_id, row, col));
+}
+
+auto_filter_t* sheet::get_auto_filter_data()
+{
+ return mp_impl->auto_filter_data.get();
+}
+
+const auto_filter_t* sheet::get_auto_filter_data() const
+{
+ return mp_impl->auto_filter_data.get();
+}
+
+void sheet::set_auto_filter_data(auto_filter_t* p)
+{
+ mp_impl->auto_filter_data.reset(p);
+}
+
+ixion::abs_range_t sheet::get_data_range() const
+{
+ return mp_impl->get_data_range();
+}
+
+sheet_t sheet::get_index() const
+{
+ return mp_impl->sheet_id;
+}
+
+date_time_t sheet::get_date_time(row_t row, col_t col) const
+{
+ const ixion::model_context& cxt = mp_impl->doc.get_model_context();
+
+ // raw value as days since epoch.
+ double dt_raw = cxt.get_numeric_value(
+ ixion::abs_address_t(mp_impl->sheet_id, row, col));
+
+ double days_since_epoch = std::floor(dt_raw);
+ double time_fraction = dt_raw - days_since_epoch;
+
+ date_time_t dt_origin = mp_impl->doc.get_origin_date();
+
+ posix_time::ptime origin(
+ gregorian::date(
+ gregorian::greg_year(dt_origin.year),
+ gregorian::greg_month(dt_origin.month),
+ gregorian::greg_day(dt_origin.day)
+ )
+ );
+
+ posix_time::ptime date_part = origin + gregorian::days(days_since_epoch);
+
+ long hours = 0;
+ long minutes = 0;
+ double seconds = 0.0;
+
+ if (time_fraction)
+ {
+ // Convert a fraction day to microseconds.
+ long long ms = time_fraction * 24.0 * 60.0 * 60.0 * 1000000.0;
+ posix_time::time_duration td = posix_time::microsec(ms);
+
+ hours = td.hours();
+ minutes = td.minutes();
+ seconds = td.seconds(); // long to double
+
+ td -= posix_time::hours(hours);
+ td -= posix_time::minutes(minutes);
+ td -= posix_time::seconds((long)seconds);
+
+ ms = td.total_microseconds(); // remaining microseconds.
+
+ seconds += ms / 1000000.0;
+ }
+
+ gregorian::date d = date_part.date();
+
+ return date_time_t(d.year(), d.month(), d.day(), hours, minutes, seconds);
+}
+
+void sheet::finalize_import()
+{
+ mp_impl->col_widths.build_tree();
+ mp_impl->row_heights.build_tree();
+}
+
+void sheet::dump_flat(std::ostream& os) const
+{
+ detail::flat_dumper dumper(mp_impl->doc);
+ dumper.dump(os, mp_impl->sheet_id);
+}
+
+void sheet::dump_check(ostream& os, std::string_view sheet_name) const
+{
+ detail::check_dumper dumper(*mp_impl, sheet_name);
+ dumper.dump(os);
+}
+
+void sheet::dump_html(std::ostream& os) const
+{
+ if (!mp_impl->col_widths.is_tree_valid())
+ mp_impl->col_widths.build_tree();
+
+ if (!mp_impl->row_heights.is_tree_valid())
+ mp_impl->row_heights.build_tree();
+
+ detail::html_dumper dumper(mp_impl->doc, mp_impl->merge_ranges, mp_impl->sheet_id);
+ dumper.dump(os);
+}
+
+void sheet::dump_json(std::ostream& os) const
+{
+ detail::json_dumper dumper(mp_impl->doc);
+ dumper.dump(os, mp_impl->sheet_id);
+}
+
+void sheet::dump_csv(std::ostream& os) const
+{
+ detail::csv_dumper dumper(mp_impl->doc);
+ dumper.dump(os, mp_impl->sheet_id);
+}
+
+void sheet::dump_debug_state(const std::string& output_dir, std::string_view sheet_name) const
+{
+ fs::path outdir{output_dir};
+ detail::sheet_debug_state_dumper dumper(*mp_impl, sheet_name);
+ dumper.dump(outdir);
+}
+
+size_t sheet::get_cell_format(row_t row, col_t col) const
+{
+ // Check the cell format store first
+ auto it = mp_impl->cell_formats.find(col);
+ if (it != mp_impl->cell_formats.end())
+ {
+ detail::segment_row_index_type& con = *it->second;
+ if (!con.is_tree_valid())
+ con.build_tree();
+
+ // Return only if the index is not a default index
+ std::size_t index;
+ if (con.search_tree(row, index).second && index)
+ return index;
+ }
+
+ // Not found in the cell format store. Check the row store.
+ if (!mp_impl->row_formats.is_tree_valid())
+ mp_impl->row_formats.build_tree();
+
+ std::size_t index;
+ if (mp_impl->row_formats.search_tree(row, index).second && index)
+ return index;
+
+ // Not found in the row store. Check the column store.
+ if (!mp_impl->column_formats.is_tree_valid())
+ mp_impl->column_formats.build_tree();
+
+ if (mp_impl->column_formats.search_tree(col, index).second && index)
+ return index;
+
+ // Not found. Return the default format index.
+ return 0;
+}
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */