diff options
Diffstat (limited to 'src/python/cell.cpp')
-rw-r--r-- | src/python/cell.cpp | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/src/python/cell.cpp b/src/python/cell.cpp new file mode 100644 index 0000000..a4a46b0 --- /dev/null +++ b/src/python/cell.cpp @@ -0,0 +1,349 @@ +/* -*- 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 "cell.hpp" +#include "memory.hpp" +#include "global.hpp" +#include "formula_token.hpp" +#include "formula_tokens.hpp" +#include "orcus/spreadsheet/document.hpp" + +#include <ixion/cell.hpp> +#include <ixion/formula_result.hpp> +#include <ixion/formula.hpp> +#include <ixion/formula_name_resolver.hpp> +#include <ixion/model_context.hpp> + +#include <structmember.h> +#include <string> + +namespace ss = orcus::spreadsheet; + +namespace orcus { namespace python { + +namespace { + +/** non-python part of the object. */ +struct cell_data +{ + const ss::document* doc = nullptr; + const ixion::formula_cell* formula_cell = nullptr; + ixion::abs_address_t origin; +}; + +/** + * Python object for orcus.Cell. + */ +struct pyobj_cell +{ + PyObject_HEAD + + PyObject* type; + PyObject* value; + PyObject* formula; + + cell_data* data = nullptr; +}; + +void initialize_cell_members(pyobj_cell* self) +{ + Py_INCREF(Py_None); + self->value = Py_None; + + Py_INCREF(Py_None); + self->formula = Py_None; +} + +PyObject* create_and_init_cell_object(const char* type_name) +{ + PyTypeObject* cell_type = get_cell_type(); + if (!cell_type) + { + PyErr_SetString(PyExc_RuntimeError, "Failed to get the cell type object."); + return nullptr; + } + + PyObject* obj = cell_type->tp_new(cell_type, nullptr, nullptr); + if (!obj) + { + PyErr_SetString(PyExc_RuntimeError, "Failed to instantiate a cell object."); + return nullptr; + } + + pyobj_cell* self = reinterpret_cast<pyobj_cell*>(obj); + self->type = get_python_enum_value("CellType", type_name); + initialize_cell_members(self); + + return obj; +} + +void tp_dealloc(pyobj_cell* self) +{ + delete self->data; + self->data = nullptr; + + Py_CLEAR(self->type); + Py_CLEAR(self->value); + Py_CLEAR(self->formula); + + Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self)); +} + +PyObject* tp_new(PyTypeObject* type, PyObject* /*args*/, PyObject* /*kwargs*/) +{ + pyobj_cell* self = (pyobj_cell*)type->tp_alloc(type, 0); + self->data = new cell_data; + return reinterpret_cast<PyObject*>(self); +} + +int tp_init(pyobj_cell* self, PyObject* args, PyObject* kwargs) +{ + static const char* kwlist[] = { "type", nullptr }; + + self->type = nullptr; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", const_cast<char**>(kwlist), &self->type)) + return -1; + + if (!self->type) + self->type = get_python_enum_value("CellType", "UNKNOWN"); + + initialize_cell_members(self); + return 0; +} + +PyObject* cell_get_formula_tokens(PyObject* self, PyObject* /*args*/, PyObject* /*kwargs*/) +{ + pyobj_cell* obj = reinterpret_cast<pyobj_cell*>(self); + cell_data& data = *obj->data; + if (!data.formula_cell) + { + // This is not a formula cell. + Py_INCREF(Py_None); + return Py_None; + } + + const ixion::formula_tokens_t& tokens = data.formula_cell->get_tokens()->get(); + return create_formula_tokens_iterator_object(*data.doc, data.origin, tokens); +} + +PyMethodDef tp_methods[] = +{ + { "get_formula_tokens", (PyCFunction)cell_get_formula_tokens, METH_NOARGS, "Get a formula tokens iterator." }, + { nullptr } +}; + +PyMemberDef tp_members[] = +{ + { (char*)"type", T_OBJECT_EX, offsetof(pyobj_cell, type), READONLY, (char*)"cell type" }, + { (char*)"value", T_OBJECT_EX, offsetof(pyobj_cell, value), READONLY, (char*)"cell value" }, + { (char*)"formula", T_OBJECT_EX, offsetof(pyobj_cell, formula), READONLY, (char*)"formula string" }, + { nullptr } +}; + +PyTypeObject cell_type = +{ + PyVarObject_HEAD_INIT(nullptr, 0) + "orcus.Cell", // tp_name + sizeof(pyobj_cell), // tp_basicsize + 0, // tp_itemsize + (destructor)tp_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 + "orcus spreadsheet cell", // tp_doc + 0, // tp_traverse + 0, // tp_clear + 0, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + tp_methods, // tp_methods + tp_members, // tp_members + 0, // tp_getset + 0, // tp_base + 0, // tp_dict + 0, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + (initproc)tp_init, // tp_init + 0, // tp_alloc + tp_new, // tp_new +}; + +} // anonymous namespace + +PyObject* create_cell_object_empty() +{ + PyObject* obj = create_and_init_cell_object("EMPTY"); + if (!obj) + return nullptr; + + return obj; +} + +PyObject* create_cell_object_boolean(bool v) +{ + PyObject* obj = create_and_init_cell_object("BOOLEAN"); + if (!obj) + return nullptr; + + pyobj_cell* obj_data = reinterpret_cast<pyobj_cell*>(obj); + + if (v) + { + Py_INCREF(Py_True); + obj_data->value = Py_True; + } + else + { + Py_INCREF(Py_False); + obj_data->value = Py_False; + } + + return obj; +} + +PyObject* create_cell_object_string(const std::string* p) +{ + PyObject* obj = create_and_init_cell_object("STRING"); + if (!obj) + return nullptr; + + pyobj_cell* obj_data = reinterpret_cast<pyobj_cell*>(obj); + + if (p) + { + obj_data->value = PyUnicode_FromStringAndSize(p->data(), p->size()); + if (!obj_data->value) + { + // The string contains invalid utf-8 sequence, and the function has + // already set a python exception which needs to be cleared. + PyErr_Clear(); + Py_XDECREF(obj); + obj = create_and_init_cell_object("STRING_WITH_ERROR"); + } + } + else + { + Py_INCREF(Py_None); + obj_data->value = Py_None; + } + + return obj; +} + +PyObject* create_cell_object_numeric(double v) +{ + PyObject* obj = create_and_init_cell_object("NUMERIC"); + if (!obj) + return nullptr; + + pyobj_cell* obj_data = reinterpret_cast<pyobj_cell*>(obj); + obj_data->value = PyFloat_FromDouble(v); + + return obj; +} + +PyObject* create_cell_object_formula( + const spreadsheet::document& doc, const ixion::abs_address_t& origin, const ixion::formula_cell* fc) +{ + if (!fc) + { + PyErr_SetString(PyExc_RuntimeError, "failed to find class orcus.CellType."); + return nullptr; + } + + const ixion::formula_tokens_t& tokens = fc->get_tokens()->get(); + bool is_error = !tokens.empty() && tokens[0].opcode == ixion::fop_error; + + PyObject* obj = create_and_init_cell_object(is_error ? "FORMULA_WITH_ERROR": "FORMULA"); + if (!obj) + return nullptr; + + pyobj_cell* obj_data = reinterpret_cast<pyobj_cell*>(obj); + obj_data->data->doc = &doc; + obj_data->data->origin = origin; + obj_data->data->formula_cell = fc; + + // Create formula expression string. + auto* resolver = doc.get_formula_name_resolver(spreadsheet::formula_ref_context_t::global); + const ixion::model_context& cxt = doc.get_model_context(); + std::string formula_s = ixion::print_formula_tokens(cxt, origin, *resolver, tokens); + obj_data->formula = PyUnicode_FromStringAndSize(formula_s.data(), formula_s.size()); + + ixion::formula_result res; + + try + { + res = fc->get_result_cache( + ixion::formula_result_wait_policy_t::throw_exception); + } + catch (const std::exception&) + { + Py_INCREF(Py_None); + obj_data->value = Py_None; + return obj; + } + + switch (res.get_type()) + { + case ixion::formula_result::result_type::value: + { + obj_data->value = PyFloat_FromDouble(res.get_value()); + break; + } + case ixion::formula_result::result_type::string: + { + const std::string& s = res.get_string(); + obj_data->value = PyUnicode_FromStringAndSize(s.data(), s.size()); + break; + } + case ixion::formula_result::result_type::error: + { + ixion::formula_error_t fe = res.get_error(); + std::string_view fename = ixion::get_formula_error_name(fe); + if (!fename.empty()) + obj_data->value = PyUnicode_FromStringAndSize(fename.data(), fename.size()); + else + { + // This should not be hit, but just in case... + Py_INCREF(Py_None); + obj_data->value = Py_None; + } + break; + } + default: + { + // This should not be hit, but just in case... + Py_INCREF(Py_None); + obj_data->value = Py_None; + } + } + + return obj; +} + +PyTypeObject* get_cell_type() +{ + return &cell_type; +} + +}} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |