/* -*- 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 #include #include #include #include #include #include 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(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(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(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(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(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(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(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(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(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: */