diff options
Diffstat (limited to 'src/libixion/model_context_impl.cpp')
-rw-r--r-- | src/libixion/model_context_impl.cpp | 1126 |
1 files changed, 1126 insertions, 0 deletions
diff --git a/src/libixion/model_context_impl.cpp b/src/libixion/model_context_impl.cpp new file mode 100644 index 0000000..fdd6714 --- /dev/null +++ b/src/libixion/model_context_impl.cpp @@ -0,0 +1,1126 @@ +/* -*- 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 "model_context_impl.hpp" + +#include <ixion/address.hpp> +#include <ixion/cell.hpp> +#include <ixion/formula_result.hpp> +#include <ixion/matrix.hpp> +#include <ixion/interface/session_handler.hpp> +#include <ixion/model_iterator.hpp> +#include <ixion/exceptions.hpp> + +#include "calc_status.hpp" +#include "model_types.hpp" +#include "utils.hpp" +#include "debug.hpp" + +#include <sstream> +#include <iostream> +#include <cstring> + +using std::cout; +using std::endl; + +namespace ixion { namespace detail { + +string_id_t safe_string_pool::append_string_unsafe(std::string_view s) +{ + assert(!s.empty()); + + string_id_t str_id = m_strings.size(); + m_strings.push_back(std::string{s}); + s = m_strings.back(); + m_string_map.insert(string_map_type::value_type(s, str_id)); + return str_id; +} + +string_id_t safe_string_pool::append_string(std::string_view s) +{ + if (s.empty()) + // Never add an empty or invalid string. + return empty_string_id; + + std::unique_lock<std::mutex> lock(m_mtx); + return append_string_unsafe(s); +} + +string_id_t safe_string_pool::add_string(std::string_view s) +{ + if (s.empty()) + // Never add an empty or invalid string. + return empty_string_id; + + std::unique_lock<std::mutex> lock(m_mtx); + string_map_type::iterator itr = m_string_map.find(s); + if (itr != m_string_map.end()) + return itr->second; + + return append_string_unsafe(s); +} + +const std::string* safe_string_pool::get_string(string_id_t identifier) const +{ + if (identifier == empty_string_id) + return &m_empty_string; + + if (identifier >= m_strings.size()) + return nullptr; + + return &m_strings[identifier]; +} + +size_t safe_string_pool::size() const +{ + return m_strings.size(); +} + +void safe_string_pool::dump_strings() const +{ + { + cout << "string count: " << m_strings.size() << endl; + auto it = m_strings.begin(), ite = m_strings.end(); + for (string_id_t sid = 0; it != ite; ++it, ++sid) + { + const std::string& s = *it; + cout << "* " << sid << ": '" << s << "' (" << (void*)s.data() << ")" << endl; + } + } + + { + cout << "string map count: " << m_string_map.size() << endl; + auto it = m_string_map.begin(), ite = m_string_map.end(); + for (; it != ite; ++it) + { + std::string_view key = it->first; + cout << "* key: '" << key << "' (" << (void*)key.data() << "; " << key.size() << "), value: " << it->second << endl; + } + } +} + +string_id_t safe_string_pool::get_identifier_from_string(std::string_view s) const +{ + string_map_type::const_iterator it = m_string_map.find(s); + return it == m_string_map.end() ? empty_string_id : it->second; +} + +namespace { + +model_context::session_handler_factory dummy_session_handler_factory; + +rc_size_t to_group_size(const abs_range_t& group_range) +{ + rc_size_t group_size; + group_size.row = group_range.last.row - group_range.first.row + 1; + group_size.column = group_range.last.column - group_range.first.column + 1; + return group_size; +} + +void set_grouped_formula_cells_to_workbook( + workbook& wb, const abs_address_t& top_left, const rc_size_t& group_size, + const calc_status_ptr_t& cs, const formula_tokens_store_ptr_t& ts) +{ + worksheet& sheet = wb.at(top_left.sheet); + + for (col_t col_offset = 0; col_offset < group_size.column; ++col_offset) + { + col_t col = top_left.column + col_offset; + column_store_t& col_store = sheet.at(col); + column_store_t::iterator& pos_hint = sheet.get_pos_hint(col); + + for (row_t row_offset = 0; row_offset < group_size.row; ++row_offset) + { + row_t row = top_left.row + row_offset; + pos_hint = col_store.set(pos_hint, row, new formula_cell(row_offset, col_offset, cs, ts)); + } + } +} + +/** + * The name of a named expression can only contain letters, numbers or an + * underscore character. + */ +void check_named_exp_name_or_throw(const char* p, size_t n) +{ + const char* p_end = p + n; + + if (p == p_end) + throw model_context_error( + "empty name is not allowed", model_context_error::invalid_named_expression); + + if ('0' <= *p && *p <= '9') + throw model_context_error( + "name cannot start with a numeric character", model_context_error::invalid_named_expression); + + if (*p == '.') + throw model_context_error( + "name cannot start with a dot", model_context_error::invalid_named_expression); + + for (; p != p_end; ++p) + { + char c = *p; + if ('a' <= c && c <= 'z') + continue; + + if ('A' <= c && c <= 'Z') + continue; + + if ('0' <= c && c <= '9') + continue; + + if (c == '_' || c == '.') + continue; + + std::ostringstream os; + os << "name contains invalid character '" << c << "'"; + throw model_context_error(os.str(), model_context_error::invalid_named_expression); + } +} + +void clip_range(abs_range_t& range, const rc_size_t& sheet_size) +{ + if (range.first.row == row_unset) + range.first.row = 0; + if (range.last.row == row_unset) + range.last.row = sheet_size.row - 1; +} + +void throw_sheet_name_conflict(const std::string& name) +{ + // This sheet name is already taken. + std::ostringstream os; + os << "Sheet name '" << name << "' already exists."; + throw model_context_error(os.str(), model_context_error::sheet_name_conflict); +} + +} // anonymous namespace + +model_context_impl::model_context_impl(model_context& parent, const rc_size_t& sheet_size) : + m_parent(parent), + m_sheet_size(sheet_size), + m_tracker(), + mp_table_handler(nullptr), + mp_session_factory(&dummy_session_handler_factory), + m_formula_res_wait_policy(formula_result_wait_policy_t::throw_exception) +{ +} + +model_context_impl::~model_context_impl() {} + +void model_context_impl::notify(formula_event_t event) +{ + switch (event) + { + case formula_event_t::calculation_begins: + m_formula_res_wait_policy = formula_result_wait_policy_t::block_until_done; + break; + case formula_event_t::calculation_ends: + m_formula_res_wait_policy = formula_result_wait_policy_t::throw_exception; + break; + } +} + +void model_context_impl::set_named_expression( + std::string name, const abs_address_t& origin, formula_tokens_t&& expr) +{ + check_named_exp_name_or_throw(name.data(), name.size()); + + IXION_TRACE("named expression: name='" << name << "'"); + m_named_expressions.insert( + detail::named_expressions_t::value_type( + std::move(name), + named_expression_t(origin, std::move(expr)) + ) + ); +} + +void model_context_impl::set_named_expression( + sheet_t sheet, std::string name, const abs_address_t& origin, formula_tokens_t&& expr) +{ + check_named_exp_name_or_throw(name.data(), name.size()); + + detail::named_expressions_t& ns = m_sheets.at(sheet).get_named_expressions(); + IXION_TRACE("named expression: name='" << name << "'"); + ns.insert( + detail::named_expressions_t::value_type( + std::move(name), + named_expression_t(origin, std::move(expr)) + ) + ); +} + +const named_expression_t* model_context_impl::get_named_expression(std::string_view name) const +{ + named_expressions_t::const_iterator itr = m_named_expressions.find(std::string(name)); + return itr == m_named_expressions.end() ? nullptr : &itr->second; +} + +const named_expression_t* model_context_impl::get_named_expression(sheet_t sheet, std::string_view name) const +{ + const worksheet* ws = fetch_sheet(sheet); + + if (ws) + { + const named_expressions_t& ns = ws->get_named_expressions(); + auto it = ns.find(std::string(name)); + if (it != ns.end()) + return &it->second; + } + + // Search the global scope if not found in the sheet local scope. + return get_named_expression(name); +} + +sheet_t model_context_impl::get_sheet_index(std::string_view name) const +{ + strings_type::const_iterator itr_beg = m_sheet_names.begin(), itr_end = m_sheet_names.end(); + for (strings_type::const_iterator itr = itr_beg; itr != itr_end; ++itr) + { + const std::string& s = *itr; + if (s.empty()) + continue; + + if (s == name) + return static_cast<sheet_t>(std::distance(itr_beg, itr)); + } + return invalid_sheet; +} + +std::string model_context_impl::get_sheet_name(sheet_t sheet) const +{ + if (sheet < 0 || m_sheet_names.size() <= std::size_t(sheet)) + return std::string(); + + return m_sheet_names[sheet]; +} + +void model_context_impl::set_sheet_name(sheet_t sheet, std::string name) +{ + if (sheet < 0 || m_sheet_names.size() <= std::size_t(sheet)) + { + std::ostringstream os; + os << "invalid sheet index: " << sheet; + throw std::invalid_argument(os.str()); + } + + for (std::size_t i = 0; i < m_sheet_names.size(); ++i) + { + if (m_sheet_names[i] == name) + { + if (i == std::size_t(sheet)) + // Same sheet name is given. No point updating it. + return; + else + throw_sheet_name_conflict(name); + } + } + + m_sheet_names[sheet] = std::move(name); +} + +rc_size_t model_context_impl::get_sheet_size() const +{ + return m_sheet_size; +} + +size_t model_context_impl::get_sheet_count() const +{ + return m_sheets.size(); +} + +sheet_t model_context_impl::append_sheet(std::string&& name) +{ + IXION_TRACE("name='" << name << "'"); + + // Check if the new sheet name already exists. + strings_type::const_iterator it = + std::find(m_sheet_names.begin(), m_sheet_names.end(), name); + if (it != m_sheet_names.end()) + throw_sheet_name_conflict(name); + + // index of the new sheet. + sheet_t sheet_index = m_sheets.size(); + + m_sheet_names.push_back(std::move(name)); + m_sheets.push_back(m_sheet_size.row, m_sheet_size.column); + return sheet_index; +} + +void model_context_impl::set_cell_values(sheet_t sheet, std::initializer_list<model_context::input_row>&& rows) +{ + abs_address_t pos; + pos.sheet = sheet; + pos.row = 0; + pos.column = 0; + + // TODO : This function is not optimized for speed as it is mainly for + // convenience. Decide if we need to optimize this later. + + for (const model_context::input_row& row : rows) + { + pos.column = 0; + + for (const model_context::input_cell& c : row.cells()) + { + switch (c.type) + { + case celltype_t::numeric: + set_numeric_cell(pos, std::get<double>(c.value)); + break; + case celltype_t::string: + { + auto s = std::get<std::string_view>(c.value); + set_string_cell(pos, s); + break; + } + case celltype_t::boolean: + set_boolean_cell(pos, std::get<bool>(c.value)); + break; + default: + ; + } + + ++pos.column; + } + + ++pos.row; + } +} + +string_id_t model_context_impl::append_string(std::string_view s) +{ + return m_str_pool.append_string(s); +} + +string_id_t model_context_impl::add_string(std::string_view s) +{ + return m_str_pool.add_string(s); +} + +const std::string* model_context_impl::get_string(string_id_t identifier) const +{ + return m_str_pool.get_string(identifier); +} + +size_t model_context_impl::get_string_count() const +{ + return m_str_pool.size(); +} + +void model_context_impl::dump_strings() const +{ + m_str_pool.dump_strings(); +} + +const column_store_t* model_context_impl::get_column(sheet_t sheet, col_t col) const +{ + if (static_cast<size_t>(sheet) >= m_sheets.size()) + return nullptr; + + const worksheet& sh = m_sheets[sheet]; + + if (static_cast<size_t>(col) >= sh.size()) + return nullptr; + + return &sh[col]; +} + +const column_stores_t* model_context_impl::get_columns(sheet_t sheet) const +{ + if (static_cast<size_t>(sheet) >= m_sheets.size()) + return nullptr; + + const worksheet& sh = m_sheets[sheet]; + return &sh.get_columns(); +} + +namespace { + +double count_formula_block( + formula_result_wait_policy_t wait_policy, const column_store_t::const_iterator& itb, size_t offset, size_t len, const values_t& vt) +{ + double ret = 0.0; + + // Inspect each formula cell individually. + formula_cell** pp = &formula_element_block::at(*itb->data, offset); + formula_cell** pp_end = pp + len; + for (; pp != pp_end; ++pp) + { + const formula_cell& fc = **pp; + formula_result res = fc.get_result_cache(wait_policy); + + switch (res.get_type()) + { + case formula_result::result_type::boolean: + if (vt.is_boolean()) + ++ret; + break; + case formula_result::result_type::value: + if (vt.is_numeric()) + ++ret; + break; + case formula_result::result_type::string: + if (vt.is_string()) + ++ret; + break; + case formula_result::result_type::error: + // TODO : how do we handle error formula cells? + break; + case formula_result::result_type::matrix: + // TODO : ditto + break; + } + } + + return ret; +} + +column_block_t map_column_block_type(const mdds::mtv::element_t mtv_type) +{ + static const std::map<mdds::mtv::element_t, column_block_t> rules = { + { element_type_empty, column_block_t::empty }, // -1 + { element_type_boolean, column_block_t::boolean }, // 0 + { element_type_string, column_block_t::string }, // 6 + { element_type_numeric, column_block_t::numeric }, // 10 + { element_type_formula, column_block_t::formula }, // user-start (50) + }; + + auto it = rules.find(mtv_type); + return it == rules.end() ? column_block_t::unknown : it->second; +} + +} // anonymous namespace + +double model_context_impl::count_range(abs_range_t range, values_t values_type) const +{ + if (m_sheets.empty()) + return 0.0; + + clip_range(range, m_sheet_size); + + double ret = 0.0; + sheet_t last_sheet = range.last.sheet; + if (static_cast<size_t>(last_sheet) >= m_sheets.size()) + last_sheet = m_sheets.size() - 1; + + for (sheet_t sheet = range.first.sheet; sheet <= last_sheet; ++sheet) + { + const worksheet& ws = m_sheets.at(sheet); + for (col_t col = range.first.column; col <= range.last.column; ++col) + { + const column_store_t& cs = ws.at(col); + row_t cur_row = range.first.row; + column_store_t::const_position_type pos = cs.position(cur_row); + column_store_t::const_iterator itb = pos.first; // block iterator + column_store_t::const_iterator itb_end = cs.end(); + size_t offset = pos.second; + if (itb == itb_end) + continue; + + bool cont = true; + while (cont) + { + // remaining length of current block. + size_t len = itb->size - offset; + row_t last_row = cur_row + len - 1; + + if (last_row >= range.last.row) + { + last_row = range.last.row; + len = last_row - cur_row + 1; + cont = false; + } + + bool match = false; + + switch (itb->type) + { + case element_type_numeric: + match = values_type.is_numeric(); + break; + case element_type_boolean: + match = values_type.is_boolean(); + break; + case element_type_string: + match = values_type.is_string(); + break; + case element_type_empty: + match = values_type.is_empty(); + break; + case element_type_formula: + ret += count_formula_block(m_formula_res_wait_policy, itb, offset, len, values_type); + break; + default: + { + std::ostringstream os; + os << __FUNCTION__ << ": unhandled block type (" << itb->type << ")"; + throw general_error(os.str()); + } + } + + if (match) + ret += len; + + // Move to the next block. + cur_row = last_row + 1; + ++itb; + offset = 0; + if (itb == itb_end) + cont = false; + } + } + } + + return ret; +} + +void model_context_impl::walk(sheet_t sheet, const abs_rc_range_t& range, column_block_callback_t cb) const +{ + const worksheet& sh = m_sheets.at(sheet); + + for (col_t ic = range.first.column; ic <= range.last.column; ++ic) + { + row_t cur_row = range.first.row; + + while (cur_row <= range.last.row) + { + const column_store_t& col = sh.at(ic); + auto pos = col.position(cur_row); + auto blk = pos.first; + + column_block_shape_t shape; + shape.position = blk->position; + shape.size = blk->size; + shape.offset = pos.second; + shape.type = map_column_block_type(blk->type); + shape.data = blk->data; + + // last row specified by the caller, or row corresponding to the + // last element of the block, whichever comes first. + row_t last_row = std::min<row_t>(blk->size - pos.second - 1 + cur_row, range.last.row); + + if (!cb(ic, cur_row, last_row, shape)) + return; + + assert(blk->size > pos.second); + cur_row += blk->size - pos.second; + } + } +} + +bool model_context_impl::empty() const +{ + return m_sheets.empty(); +} + +const worksheet* model_context_impl::fetch_sheet(sheet_t sheet_index) const +{ + if (sheet_index < 0 || m_sheets.size() <= size_t(sheet_index)) + return nullptr; + + return &m_sheets[sheet_index]; +} + +column_store_t::const_position_type model_context_impl::get_cell_position(const abs_address_t& addr) const +{ + const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column); + return col_store.position(addr.row); +} + +const detail::named_expressions_t& model_context_impl::get_named_expressions() const +{ + return m_named_expressions; +} + +const detail::named_expressions_t& model_context_impl::get_named_expressions(sheet_t sheet) const +{ + const worksheet& sh = m_sheets.at(sheet); + return sh.get_named_expressions(); +} + +model_iterator model_context_impl::get_model_iterator( + sheet_t sheet, rc_direction_t dir, const abs_rc_range_t& range) const +{ + return model_iterator(*this, sheet, range, dir); +} + +void model_context_impl::set_sheet_size(const rc_size_t& sheet_size) +{ + if (!m_sheets.empty()) + throw model_context_error( + "You cannot change the sheet size if you already have at least one existing sheet.", + model_context_error::sheet_size_locked); + + m_sheet_size = sheet_size; +} + +std::unique_ptr<iface::session_handler> model_context_impl::create_session_handler() +{ + return mp_session_factory->create(); +} + +void model_context_impl::empty_cell(const abs_address_t& addr) +{ + worksheet& sheet = m_sheets.at(addr.sheet); + column_store_t& col_store = sheet.at(addr.column); + column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column); + pos_hint = col_store.set_empty(addr.row, addr.row); +} + +void model_context_impl::set_numeric_cell(const abs_address_t& addr, double val) +{ + worksheet& sheet = m_sheets.at(addr.sheet); + column_store_t& col_store = sheet.at(addr.column); + column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column); + pos_hint = col_store.set(pos_hint, addr.row, val); +} + +void model_context_impl::set_boolean_cell(const abs_address_t& addr, bool val) +{ + worksheet& sheet = m_sheets.at(addr.sheet); + column_store_t& col_store = sheet.at(addr.column); + column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column); + pos_hint = col_store.set(pos_hint, addr.row, val); +} + +void model_context_impl::set_string_cell(const abs_address_t& addr, std::string_view s) +{ + worksheet& sheet = m_sheets.at(addr.sheet); + string_id_t str_id = add_string(s); + column_store_t& col_store = sheet.at(addr.column); + column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column); + pos_hint = col_store.set(pos_hint, addr.row, str_id); +} + +void model_context_impl::fill_down_cells(const abs_address_t& src, size_t n_dst) +{ + if (!n_dst) + // Destination cell length is 0. Nothing to copy to. + return; + + worksheet& sheet = m_sheets.at(src.sheet); + column_store_t& col_store = sheet.at(src.column); + column_store_t::iterator& pos_hint = sheet.get_pos_hint(src.column); + + column_store_t::const_position_type pos = col_store.position(pos_hint, src.row); + auto it = pos.first; // block iterator + + switch (it->type) + { + case element_type_numeric: + { + double v = col_store.get<numeric_element_block>(pos); + std::vector<double> vs(n_dst, v); + pos_hint = col_store.set(pos_hint, src.row+1, vs.begin(), vs.end()); + break; + } + case element_type_boolean: + { + bool b = col_store.get<boolean_element_block>(pos); + std::deque<bool> vs(n_dst, b); + pos_hint = col_store.set(pos_hint, src.row+1, vs.begin(), vs.end()); + break; + } + case element_type_string: + { + string_id_t sid = col_store.get<string_element_block>(pos); + std::vector<string_id_t> vs(n_dst, sid); + pos_hint = col_store.set(pos_hint, src.row+1, vs.begin(), vs.end()); + break; + } + case element_type_empty: + { + size_t start_pos = src.row + 1; + size_t end_pos = start_pos + n_dst - 1; + pos_hint = col_store.set_empty(pos_hint, start_pos, end_pos); + break; + } + case element_type_formula: + // TODO : support this. + throw not_implemented_error("filling down of a formula cell is not yet supported."); + default: + { + std::ostringstream os; + os << __FUNCTION__ << ": unhandled block type (" << it->type << ")"; + throw general_error(os.str()); + } + } +} + +void model_context_impl::set_string_cell(const abs_address_t& addr, string_id_t identifier) +{ + worksheet& sheet = m_sheets.at(addr.sheet); + column_store_t& col_store = sheet.at(addr.column); + column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column); + pos_hint = col_store.set(pos_hint, addr.row, identifier); +} + +formula_cell* model_context_impl::set_formula_cell( + const abs_address_t& addr, const formula_tokens_store_ptr_t& tokens) +{ + std::unique_ptr<formula_cell> fcell = std::make_unique<formula_cell>(tokens); + + worksheet& sheet = m_sheets.at(addr.sheet); + column_store_t& col_store = sheet.at(addr.column); + column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column); + formula_cell* p = fcell.release(); + pos_hint = col_store.set(pos_hint, addr.row, p); + return p; +} + +formula_cell* model_context_impl::set_formula_cell( + const abs_address_t& addr, const formula_tokens_store_ptr_t& tokens, formula_result result) +{ + std::unique_ptr<formula_cell> fcell = std::make_unique<formula_cell>(tokens); + + worksheet& sheet = m_sheets.at(addr.sheet); + column_store_t& col_store = sheet.at(addr.column); + column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column); + formula_cell* p = fcell.release(); + p->set_result_cache(std::move(result)); + pos_hint = col_store.set(pos_hint, addr.row, p); + return p; +} + +void model_context_impl::set_grouped_formula_cells( + const abs_range_t& group_range, formula_tokens_t tokens) +{ + formula_tokens_store_ptr_t ts = formula_tokens_store::create(); + ts->get() = std::move(tokens); + + rc_size_t group_size = to_group_size(group_range); + calc_status_ptr_t cs(new calc_status(group_size)); + set_grouped_formula_cells_to_workbook(m_sheets, group_range.first, group_size, cs, ts); +} + +void model_context_impl::set_grouped_formula_cells( + const abs_range_t& group_range, formula_tokens_t tokens, formula_result result) +{ + formula_tokens_store_ptr_t ts = formula_tokens_store::create(); + ts->get() = std::move(tokens); + + rc_size_t group_size = to_group_size(group_range); + + if (result.get_type() != formula_result::result_type::matrix) + throw std::invalid_argument("cached result for grouped formula cells must be of matrix type."); + + if (row_t(result.get_matrix().row_size()) != group_size.row || col_t(result.get_matrix().col_size()) != group_size.column) + throw std::invalid_argument("dimension of the cached result differs from the size of the group."); + + calc_status_ptr_t cs(new calc_status(group_size)); + cs->result = std::make_unique<formula_result>(std::move(result)); + set_grouped_formula_cells_to_workbook(m_sheets, group_range.first, group_size, cs, ts); +} + +abs_range_t model_context_impl::get_data_range(sheet_t sheet) const +{ + const worksheet& cols = m_sheets.at(sheet); + size_t col_size = cols.size(); + if (!col_size) + return abs_range_t(abs_range_t::invalid); + + row_t row_size = cols[0].size(); + if (!row_size) + return abs_range_t(abs_range_t::invalid); + + abs_range_t range; + range.first.column = 0; + range.first.row = row_size-1; + range.first.sheet = sheet; + range.last.column = -1; // if this stays -1 all columns are empty. + range.last.row = 0; + range.last.sheet = sheet; + + for (size_t i = 0; i < col_size; ++i) + { + const column_store_t& col = cols[i]; + if (col.empty()) + { + if (range.last.column < 0) + ++range.first.column; + continue; + } + + if (range.first.row > 0) + { + // First non-empty row. + + column_store_t::const_iterator it = col.begin(), it_end = col.end(); + assert(it != it_end); + if (it->type == element_type_empty) + { + // First block is empty. + row_t offset = it->size; + ++it; + if (it == it_end) + { + // The whole column is empty. + if (range.last.column < 0) + ++range.first.column; + continue; + } + + assert(it->type != element_type_empty); + if (range.first.row > offset) + range.first.row = offset; + } + else + // Set the first row to 0, and lock it. + range.first.row = 0; + } + + if (range.last.row < (row_size-1)) + { + // Last non-empty row. + + column_store_t::const_reverse_iterator it = col.rbegin(), it_end = col.rend(); + assert(it != it_end); + if (it->type == element_type_empty) + { + // Last block is empty. + size_t size_last_block = it->size; + ++it; + if (it == it_end) + { + // The whole column is empty. + if (range.last.column < 0) + ++range.first.column; + continue; + } + + assert(it->type != element_type_empty); + row_t last_data_row = static_cast<row_t>(col.size() - size_last_block - 1); + if (range.last.row < last_data_row) + range.last.row = last_data_row; + } + else + // Last block is not empty. + range.last.row = row_size - 1; + } + + // Check if the column contains at least one non-empty cell. + if (col.block_size() > 1 || !col.is_empty(0)) + range.last.column = i; + } + + if (range.last.column < 0) + // No data column found. The whole sheet is empty. + return abs_range_t(abs_range_t::invalid); + + return range; +} + +bool model_context_impl::is_empty(const abs_address_t& addr) const +{ + return m_sheets.at(addr.sheet).at(addr.column).is_empty(addr.row); +} + +bool model_context_impl::is_empty(abs_range_t range) const +{ + range = shrink_to_workbook(range); + + for (sheet_t sh = range.first.sheet; sh <= range.last.sheet; ++sh) + { + for (col_t col = range.first.column; col <= range.last.column; ++col) + { + const column_store_t& col_store = m_sheets[sh][col]; + auto pos = col_store.position(range.first.row); + if (pos.first->type != element_type_empty) + // The top block is non-empty. + return false; + + // See if this block covers the entire row range. + row_t last_empty_row = range.first.row + pos.first->size - pos.second - 1; + if (last_empty_row < range.last.row) + return false; + } + } + + return true; +} + +celltype_t model_context_impl::get_celltype(const abs_address_t& addr) const +{ + mdds::mtv::element_t gmcell_type = + m_sheets.at(addr.sheet).at(addr.column).get_type(addr.row); + + return detail::to_celltype(gmcell_type); +} + +cell_value_t model_context_impl::get_cell_value_type(const abs_address_t& addr) const +{ + const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column); + auto pos = col_store.position(addr.row); + return detail::to_cell_value_type(pos, get_formula_result_wait_policy()); +} + +double model_context_impl::get_numeric_value(const abs_address_t& addr) const +{ + const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column); + auto pos = col_store.position(addr.row); + + switch (pos.first->type) + { + case element_type_numeric: + return numeric_element_block::at(*pos.first->data, pos.second); + case element_type_boolean: + { + auto it = boolean_element_block::cbegin(*pos.first->data); + std::advance(it, pos.second); + return *it ? 1.0 : 0.0; + } + case element_type_formula: + { + const formula_cell* p = formula_element_block::at(*pos.first->data, pos.second); + return p->get_value(m_formula_res_wait_policy); + } + default: + ; + } + return 0.0; +} + +bool model_context_impl::get_boolean_value(const abs_address_t& addr) const +{ + const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column); + auto pos = col_store.position(addr.row); + + switch (pos.first->type) + { + case element_type_numeric: + return numeric_element_block::at(*pos.first->data, pos.second) != 0.0 ? true : false; + case element_type_boolean: + { + auto it = boolean_element_block::cbegin(*pos.first->data); + std::advance(it, pos.second); + return *it; + } + case element_type_formula: + { + const formula_cell* p = formula_element_block::at(*pos.first->data, pos.second); + return p->get_value(m_formula_res_wait_policy) == 0.0 ? false : true; + } + default: + ; + } + return false; +} + +string_id_t model_context_impl::get_string_identifier(const abs_address_t& addr) const +{ + const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column); + auto pos = col_store.position(addr.row); + + switch (pos.first->type) + { + case element_type_string: + return string_element_block::at(*pos.first->data, pos.second); + default: + ; + } + return empty_string_id; +} + +std::string_view model_context_impl::get_string_value(const abs_address_t& addr) const +{ + const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column); + auto pos = col_store.position(addr.row); + + switch (pos.first->type) + { + case element_type_string: + { + string_id_t sid = string_element_block::at(*pos.first->data, pos.second); + const std::string* p = m_str_pool.get_string(sid); + return p ? *p : std::string_view{}; + } + case element_type_formula: + { + const formula_cell* p = formula_element_block::at(*pos.first->data, pos.second); + return p->get_string(m_formula_res_wait_policy); + } + case element_type_empty: + return empty_string; + default: + ; + } + + return std::string_view{}; +} + +string_id_t model_context_impl::get_identifier_from_string(std::string_view s) const +{ + return m_str_pool.get_identifier_from_string(s); +} + +const formula_cell* model_context_impl::get_formula_cell(const abs_address_t& addr) const +{ + const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column); + auto pos = col_store.position(addr.row); + + if (pos.first->type != element_type_formula) + return nullptr; + + return formula_element_block::at(*pos.first->data, pos.second); +} + +formula_cell* model_context_impl::get_formula_cell(const abs_address_t& addr) +{ + column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column); + auto pos = col_store.position(addr.row); + + if (pos.first->type != element_type_formula) + return nullptr; + + return formula_element_block::at(*pos.first->data, pos.second); +} + +formula_result model_context_impl::get_formula_result(const abs_address_t& addr) const +{ + const formula_cell* fc = get_formula_cell(addr); + if (!fc) + throw general_error("not a formula cell."); + + return fc->get_result_cache(m_formula_res_wait_policy); +} + +abs_range_t model_context_impl::shrink_to_workbook(abs_range_t range) const +{ + range.reorder(); + + if (m_sheets.empty()) + return range; + + if (range.first.sheet >= sheet_t(m_sheets.size())) + throw general_error("out-of-bound sheet ranges"); + + range.last.sheet = std::min<sheet_t>(range.last.sheet, m_sheets.size()-1); + const worksheet& ws = m_sheets[range.last.sheet]; + const column_stores_t& cols = ws.get_columns(); + + if (cols.empty()) + return range; + + if (range.first.column >= col_t(cols.size())) + throw general_error("out-of-bound column ranges"); + + range.last.column = std::min<col_t>(range.last.column, cols.size()-1); + + const column_store_t& col = cols[0]; + + if (range.first.row >= row_t(col.size())) + throw general_error("out-of-bound row ranges"); + + range.last.row = std::min<row_t>(range.last.row, col.size()-1); + + return range; +} + +}} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |