summaryrefslogtreecommitdiffstats
path: root/src/python/sheet.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/python/sheet.cpp376
1 files changed, 376 insertions, 0 deletions
diff --git a/src/python/sheet.cpp b/src/python/sheet.cpp
new file mode 100644
index 0000000..873004a
--- /dev/null
+++ b/src/python/sheet.cpp
@@ -0,0 +1,376 @@
+/* -*- 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 "sheet.hpp"
+#include "global.hpp"
+
+#include <ixion/model_context.hpp>
+#include <ixion/formula_name_resolver.hpp>
+#include <ixion/formula.hpp>
+#include <ixion/cell.hpp>
+#include <ixion/exceptions.hpp>
+
+#include <structmember.h>
+
+#include <iostream>
+
+using namespace std;
+
+namespace ixion { namespace python {
+
+sheet_data::sheet_data() : m_global(nullptr), m_sheet_index(-1) {}
+
+namespace {
+
+struct sheet
+{
+ PyObject_HEAD
+ PyObject* name; // sheet name
+
+ sheet_data* m_data;
+};
+
+void sheet_dealloc(sheet* self)
+{
+ delete self->m_data;
+
+ Py_XDECREF(self->name);
+ Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
+}
+
+PyObject* sheet_new(PyTypeObject* type, PyObject* /*args*/, PyObject* /*kwargs*/)
+{
+ sheet* self = (sheet*)type->tp_alloc(type, 0);
+ if (!self)
+ return nullptr;
+
+ self->m_data = new sheet_data;
+
+ self->name = PyUnicode_FromString("");
+ if (!self->name)
+ {
+ Py_DECREF(self);
+ return nullptr;
+ }
+ return reinterpret_cast<PyObject*>(self);
+}
+
+int sheet_init(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ PyObject* name = nullptr;
+ static const char* kwlist[] = { "name", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", (char**)kwlist, &name))
+ return -1;
+
+ if (name)
+ {
+ PyObject* tmp = self->name;
+ Py_INCREF(name);
+ self->name = name;
+ Py_XDECREF(tmp);
+ }
+
+ return 0;
+}
+
+PyObject* sheet_set_numeric_cell(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+ double val = 0.0;
+
+ static const char* kwlist[] = { "row", "column", "value", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iid", const_cast<char**>(kwlist), &row, &col, &val))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+ ixion::abs_address_t pos(sd->m_sheet_index, row, col);
+ sd->m_global->m_modified_cells.insert(pos);
+ cxt.set_numeric_cell(pos, val);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject* sheet_set_string_cell(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+ char* val = nullptr;
+
+ static const char* kwlist[] = { "row", "column", "value", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iis", const_cast<char**>(kwlist), &row, &col, &val))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+ ixion::abs_address_t pos(sd->m_sheet_index, row, col);
+ sd->m_global->m_modified_cells.insert(pos);
+ cxt.set_string_cell(pos, val);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject* sheet_set_formula_cell(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+ char* formula = nullptr;
+
+ static const char* kwlist[] = { "row", "column", "value", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iis", const_cast<char**>(kwlist), &row, &col, &formula))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+
+ ixion::abs_address_t pos(sd->m_sheet_index, row, col);
+ sd->m_global->m_dirty_formula_cells.insert(pos);
+
+ ixion::formula_tokens_t tokens =
+ ixion::parse_formula_string(cxt, pos, *sd->m_global->m_resolver, formula);
+
+ auto ts = formula_tokens_store::create();
+ ts->get() = std::move(tokens);
+ cxt.set_formula_cell(pos, ts);
+
+ // Put this formula cell in a dependency chain.
+ ixion::register_formula_cell(cxt, pos);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject* sheet_get_numeric_value(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+
+ static const char* kwlist[] = { "row", "column", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii", const_cast<char**>(kwlist), &row, &col))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+ double val = 0.0;
+ try
+ {
+ val = cxt.get_numeric_value(ixion::abs_address_t(sd->m_sheet_index, row, col));
+ }
+ catch (const formula_error&)
+ {
+ PyErr_SetString(PyExc_TypeError, "The formula cell has yet to be calculated");
+ return nullptr;
+ }
+
+ return PyFloat_FromDouble(val);
+}
+
+PyObject* sheet_get_string_value(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+
+ static const char* kwlist[] = { "row", "column", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii", const_cast<char**>(kwlist), &row, &col))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+ std::string_view s = cxt.get_string_value(ixion::abs_address_t(sd->m_sheet_index, row, col));
+ if (s.empty())
+ return PyUnicode_FromStringAndSize(nullptr, 0);
+
+ return PyUnicode_FromStringAndSize(s.data(), s.size());
+}
+
+PyObject* sheet_get_formula_expression(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+
+ static const char* kwlist[] = { "row", "column", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii", const_cast<char**>(kwlist), &row, &col))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+ ixion::abs_address_t pos(sd->m_sheet_index, row, col);
+ const ixion::formula_cell* fc = cxt.get_formula_cell(pos);
+
+ if (!fc)
+ {
+ PyErr_SetString(get_python_sheet_error(), "No formula cell at specified position.");
+ return nullptr;
+ }
+
+ const ixion::formula_tokens_t& ft = fc->get_tokens()->get();
+
+ string str = ixion::print_formula_tokens(cxt, pos, *sd->m_global->m_resolver, ft);
+ if (str.empty())
+ return PyUnicode_FromString("");
+
+ return PyUnicode_FromStringAndSize(str.data(), str.size());
+}
+
+PyObject* sheet_erase_cell(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ PyErr_SetString(PyExc_RuntimeError, "erase_cell() method has been deprecated. Please use empty_cell() instead.");
+ return nullptr;
+}
+
+PyObject* sheet_empty_cell(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+
+ static const char* kwlist[] = { "row", "column", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii", const_cast<char**>(kwlist), &row, &col))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+ abs_address_t pos(sd->m_sheet_index, row, col);
+ sd->m_global->m_modified_cells.insert(pos);
+ cxt.empty_cell(pos);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyMethodDef sheet_methods[] =
+{
+ { "set_numeric_cell", (PyCFunction)sheet_set_numeric_cell, METH_VARARGS | METH_KEYWORDS, "set numeric value to specified cell" },
+ { "set_formula_cell", (PyCFunction)sheet_set_formula_cell, METH_VARARGS | METH_KEYWORDS, "set formula to specified cell" },
+ { "set_string_cell", (PyCFunction)sheet_set_string_cell, METH_VARARGS | METH_KEYWORDS, "set string to specified cell" },
+ { "get_numeric_value", (PyCFunction)sheet_get_numeric_value, METH_VARARGS | METH_KEYWORDS, "get numeric value from specified cell" },
+ { "get_string_value", (PyCFunction)sheet_get_string_value, METH_VARARGS | METH_KEYWORDS, "get string value from specified cell" },
+ { "get_formula_expression", (PyCFunction)sheet_get_formula_expression, METH_VARARGS | METH_KEYWORDS, "get formula expression string from specified cell position" },
+ { "empty_cell", (PyCFunction)sheet_empty_cell, METH_VARARGS | METH_KEYWORDS, "empty cell at specified position" },
+ { "erase_cell", (PyCFunction)sheet_erase_cell, METH_VARARGS | METH_KEYWORDS, "erase cell at specified position" },
+ { nullptr }
+};
+
+PyMemberDef sheet_members[] =
+{
+ { (char*)"name", T_OBJECT_EX, offsetof(sheet, name), READONLY, (char*)"sheet name" },
+ { nullptr }
+};
+
+PyTypeObject sheet_type =
+{
+ PyVarObject_HEAD_INIT(nullptr, 0)
+ "ixion.Sheet", // tp_name
+ sizeof(sheet), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)sheet_dealloc, // tp_dealloc
+ 0, // tp_print
+ 0, // tp_getattr
+ 0, // tp_setattr
+ 0, // tp_compare
+ 0, // tp_repr
+ 0, // tp_as_number
+ 0, // tp_as_sequence
+ 0, // tp_as_mapping
+ 0, // tp_hash
+ 0, // tp_call
+ 0, // tp_str
+ 0, // tp_getattro
+ 0, // tp_setattro
+ 0, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags
+ "ixion sheet object", // tp_doc
+ 0, // tp_traverse
+ 0, // tp_clear
+ 0, // tp_richcompare
+ 0, // tp_weaklistoffset
+ 0, // tp_iter
+ 0, // tp_iternext
+ sheet_methods, // tp_methods
+ sheet_members, // tp_members
+ 0, // tp_getset
+ 0, // tp_base
+ 0, // tp_dict
+ 0, // tp_descr_get
+ 0, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)sheet_init, // tp_init
+ 0, // tp_alloc
+ sheet_new, // tp_new
+};
+
+}
+
+PyTypeObject* get_sheet_type()
+{
+ return &sheet_type;
+}
+
+sheet_data* get_sheet_data(PyObject* obj)
+{
+ return reinterpret_cast<sheet*>(obj)->m_data;
+}
+
+PyObject* get_sheet_name(PyObject* obj)
+{
+ return reinterpret_cast<sheet*>(obj)->name;
+}
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */