summaryrefslogtreecommitdiffstats
path: root/src/libixion/model_iterator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libixion/model_iterator.cpp')
-rw-r--r--src/libixion/model_iterator.cpp408
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: */