diff options
Diffstat (limited to 'src/libixion/model_iterator.cpp')
-rw-r--r-- | src/libixion/model_iterator.cpp | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/src/libixion/model_iterator.cpp b/src/libixion/model_iterator.cpp new file mode 100644 index 0000000..fb54b70 --- /dev/null +++ b/src/libixion/model_iterator.cpp @@ -0,0 +1,408 @@ +/* -*- 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 "ixion/model_iterator.hpp" +#include "ixion/global.hpp" +#include "ixion/exceptions.hpp" +#include "model_context_impl.hpp" + +#include <mdds/multi_type_vector/collection.hpp> +#include <sstream> +#include <ostream> + +namespace ixion { + +model_iterator::cell::cell() : row(0), col(0), type(celltype_t::empty), value(false) {} + +model_iterator::cell::cell(row_t _row, col_t _col) : + row(_row), col(_col), type(celltype_t::empty), value(false) {} + +model_iterator::cell::cell(row_t _row, col_t _col, bool _b) : + row(_row), col(_col), type(celltype_t::boolean), value(_b) {} + +model_iterator::cell::cell(row_t _row, col_t _col, string_id_t _s) : + row(_row), col(_col), type(celltype_t::string), value(_s) {} + +model_iterator::cell::cell(row_t _row, col_t _col, double _v) : + row(_row), col(_col), type(celltype_t::numeric), value(_v) {} + +model_iterator::cell::cell(row_t _row, col_t _col, const formula_cell* _f) : + row(_row), col(_col), type(celltype_t::formula), value(_f) {} + +bool model_iterator::cell::operator== (const cell& other) const +{ + if (type != other.type || row != other.row || col != other.col) + return false; + + return value == other.value; +} + +bool model_iterator::cell::operator!= (const cell& other) const +{ + return !operator==(other); +} + +class model_iterator::impl +{ +public: + virtual bool has() const = 0; + virtual void next() = 0; + virtual const model_iterator::cell& get() const = 0; + virtual ~impl() {} +}; + +namespace { + +class iterator_core_empty : public model_iterator::impl +{ + model_iterator::cell m_cell; +public: + bool has() const override { return false; } + + void next() override {} + + const model_iterator::cell& get() const override + { + return m_cell; + } +}; + + +class iterator_core_horizontal : public model_iterator::impl +{ + using collection_type = mdds::mtv::collection<column_store_t>; + + collection_type m_collection; + mutable model_iterator::cell m_current_cell; + mutable bool m_update_current_cell; + collection_type::const_iterator m_current_pos; + collection_type::const_iterator m_end; + + void update_current() const + { + m_current_cell.col = m_current_pos->index; + m_current_cell.row = m_current_pos->position; + + switch (m_current_pos->type) + { + case element_type_boolean: + m_current_cell.type = celltype_t::boolean; + m_current_cell.value = m_current_pos->get<boolean_element_block>(); + break; + case element_type_numeric: + m_current_cell.type = celltype_t::numeric; + m_current_cell.value = m_current_pos->get<numeric_element_block>(); + break; + case element_type_string: + m_current_cell.type = celltype_t::string; + m_current_cell.value = m_current_pos->get<string_element_block>(); + break; + case element_type_formula: + m_current_cell.type = celltype_t::formula; + m_current_cell.value = m_current_pos->get<formula_element_block>(); + break; + case element_type_empty: + m_current_cell.type = celltype_t::empty; + m_current_cell.value = false; + default: + ; + } + + m_update_current_cell = false; + } +public: + iterator_core_horizontal(const detail::model_context_impl& cxt, sheet_t sheet, const abs_rc_range_t& range) : + m_update_current_cell(true) + { + const column_stores_t* cols = cxt.get_columns(sheet); + if (cols && !cols->empty()) + { + collection_type c = mdds::mtv::collection<column_store_t>(cols->begin(), cols->end()); + + if (range.valid()) + { + if (!range.all_columns()) + { + col_t c1 = range.first.column == column_unset ? 0 : range.first.column; + col_t c2 = range.last.column == column_unset ? (cols->size() - 1) : range.last.column; + assert(c1 >= 0); + assert(c1 <= c2); + size_t start = c1; + size_t size = c2 - c1 + 1; + c.set_collection_range(start, size); + } + + if (!range.all_rows()) + { + const column_store_t& col = (*cols)[0]; + row_t r1 = range.first.row == row_unset ? 0 : range.first.row; + row_t r2 = range.last.row == row_unset ? (col.size() - 1) : range.last.row; + assert(r1 >= 0); + assert(r1 <= r2); + size_t start = r1; + size_t size = r2 - r1 + 1; + c.set_element_range(start, size); + } + } + + m_collection.swap(c); + } + + m_current_pos = m_collection.begin(); + m_end = m_collection.end(); + } + + virtual bool has() const override + { + return m_current_pos != m_end; + } + + virtual void next() override + { + ++m_current_pos; + m_update_current_cell = true; + } + + virtual const model_iterator::cell& get() const override + { + if (m_update_current_cell) + update_current(); + return m_current_cell; + } +}; + +class iterator_core_vertical : public model_iterator::impl +{ + const column_stores_t* m_cols; + mutable model_iterator::cell m_current_cell; + mutable bool m_update_current_cell; + + column_stores_t::const_iterator m_it_cols; + column_stores_t::const_iterator m_it_cols_begin; + column_stores_t::const_iterator m_it_cols_end; + + column_store_t::const_position_type m_current_pos; + column_store_t::const_position_type m_end_pos; + + row_t m_row_first; + row_t m_row_last; + + void update_current() const + { + column_store_t::const_iterator blk_pos = m_current_pos.first; + + switch (blk_pos->type) + { + case element_type_empty: + m_current_cell.type = celltype_t::empty; + m_current_cell.value = false; + break; + case element_type_boolean: + m_current_cell.type = celltype_t::boolean; + m_current_cell.value = column_store_t::get<boolean_element_block>(m_current_pos); + break; + case element_type_numeric: + m_current_cell.type = celltype_t::numeric; + m_current_cell.value = column_store_t::get<numeric_element_block>(m_current_pos); + break; + case element_type_string: + m_current_cell.type = celltype_t::string; + m_current_cell.value = column_store_t::get<string_element_block>(m_current_pos); + break; + case element_type_formula: + m_current_cell.type = celltype_t::formula; + m_current_cell.value = column_store_t::get<formula_element_block>(m_current_pos); + break; + default: + throw std::logic_error("unhandled element type."); + } + + m_current_cell.row = column_store_t::logical_position(m_current_pos); + m_current_cell.col = std::distance(m_it_cols_begin, m_it_cols); + m_update_current_cell = false; + } + +public: + iterator_core_vertical(const detail::model_context_impl& cxt, sheet_t sheet, const abs_rc_range_t& range) : + m_update_current_cell(true), + m_row_first(0), + m_row_last(row_unset) + { + m_cols = cxt.get_columns(sheet); + if (!m_cols) + return; + + m_it_cols_begin = m_cols->begin(); + m_it_cols = m_it_cols_begin; + m_it_cols_end = m_cols->end(); + if (m_it_cols_begin == m_it_cols_end) + return; + + m_row_last = (*m_cols)[0].size() - 1; + + if (range.valid()) + { + col_t last_col = m_cols->size() - 1; + + if (range.last.column != column_unset && range.last.column < last_col) + { + // Shrink the tail end. + col_t diff = range.last.column - last_col; + assert(diff < 0); + std::advance(m_it_cols_end, diff); + + last_col += diff; + } + + if (range.first.column != column_unset) + { + if (range.first.column <= last_col) + std::advance(m_it_cols, range.first.column); + else + { + // First column is past the last column. Nothing to parse. + m_it_cols_begin = m_it_cols_end; + return; + } + } + + if (range.last.row != row_unset && range.last.row < m_row_last) + { + // Shrink the tail end. + m_row_last = range.last.row; + } + + if (range.first.row != row_unset) + { + if (range.first.row <= m_row_last) + m_row_first = range.first.row; + else + { + // First row is past the last row. Set it to an empty + // range and bail out. + m_it_cols_begin = m_it_cols_end; + return; + } + } + } + + const column_store_t& col = *m_it_cols; + m_current_pos = col.position(m_row_first); + m_end_pos = col.position(m_row_last+1); + } + + bool has() const override + { + if (!m_cols) + return false; + + return m_it_cols != m_it_cols_end; + } + + void next() override + { + m_update_current_cell = true; + m_current_pos = column_store_t::next_position(m_current_pos); + + const column_store_t* col = &*m_it_cols; + if (m_current_pos != m_end_pos) + // It hasn't reached the end of the current column yet. + return; + + ++m_it_cols; // Move to the next column. + if (m_it_cols == m_it_cols_end) + return; + + // Reset the position to the first cell in the new column. + col = &*m_it_cols; + m_current_pos = col->position(m_row_first); + m_end_pos = col->position(m_row_last+1); + } + + const model_iterator::cell& get() const override + { + if (m_update_current_cell) + update_current(); + return m_current_cell; + } +}; + +} // anonymous namespace + +model_iterator::model_iterator() : mp_impl(std::make_unique<iterator_core_empty>()) {} +model_iterator::model_iterator(const detail::model_context_impl& cxt, sheet_t sheet, const abs_rc_range_t& range, rc_direction_t dir) +{ + switch (dir) + { + case rc_direction_t::horizontal: + mp_impl = std::make_unique<iterator_core_horizontal>(cxt, sheet, range); + break; + case rc_direction_t::vertical: + mp_impl = std::make_unique<iterator_core_vertical>(cxt, sheet, range); + break; + } +} + +model_iterator::model_iterator(model_iterator&& other) : mp_impl(std::move(other.mp_impl)) {} + +model_iterator::~model_iterator() {} + +model_iterator& model_iterator::operator= (model_iterator&& other) +{ + mp_impl = std::move(other.mp_impl); + other.mp_impl = std::make_unique<iterator_core_empty>(); + return *this; +} + +bool model_iterator::has() const +{ + return mp_impl->has(); +} + +void model_iterator::next() +{ + mp_impl->next(); +} + +const model_iterator::cell& model_iterator::get() const +{ + return mp_impl->get(); +} + +std::ostream& operator<< (std::ostream& os, const model_iterator::cell& c) +{ + os << "(row=" << c.row << "; col=" << c.col << "; type=" << short(c.type); + + switch (c.type) + { + case celltype_t::boolean: + os << "; boolean=" << std::get<bool>(c.value); + break; + case celltype_t::formula: + os << "; formula=" << std::get<const formula_cell*>(c.value); + break; + case celltype_t::numeric: + os << "; numeric=" << std::get<double>(c.value); + break; + case celltype_t::string: + os << "; string=" << std::get<string_id_t>(c.value); + break; + case celltype_t::empty: + os << "; empty"; + break; + case celltype_t::unknown: + default: + ; + } + + os << ')'; + return os; +} + +} // namespace ixion + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |