diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:48:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:48:59 +0000 |
commit | c484829272cd13a738e35412498e12f2c9a194ac (patch) | |
tree | a1f5ec09629ee895bd3963fa8820b45f2f4c574b /src/python/json.cpp | |
parent | Initial commit. (diff) | |
download | liborcus-upstream/0.19.2.tar.xz liborcus-upstream/0.19.2.zip |
Adding upstream version 0.19.2.upstream/0.19.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/python/json.cpp')
-rw-r--r-- | src/python/json.cpp | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/src/python/json.cpp b/src/python/json.cpp new file mode 100644 index 0000000..873523b --- /dev/null +++ b/src/python/json.cpp @@ -0,0 +1,290 @@ +/* -*- 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 "orcus/env.hpp" +#include "orcus/json_parser.hpp" +#include "orcus/json_document_tree.hpp" +#include "orcus/config.hpp" + +#include <algorithm> +#include <sstream> +#include <boost/current_function.hpp> + +#include <Python.h> + +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) + +using namespace std; + +namespace orcus { namespace python { + +namespace { + +class python_json_error : public general_error +{ +public: + python_json_error(const std::string& msg) : general_error("python_json_error", msg) {} +}; + +struct module_state +{ + PyObject* error; +}; + +int orcus_traverse(PyObject* m, visitproc visit, void* arg) +{ + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +int orcus_clear(PyObject* m) +{ + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +struct parser_stack +{ + PyObject* key; + PyObject* node; + json::node_t type; + + parser_stack(PyObject* _node, json::node_t _type) : key(nullptr), node(_node), type(_type) {} +}; + +class json_parser_handler +{ + PyObject* m_root; + std::vector<parser_stack> m_stack; + + PyObject* push_value(PyObject* value) + { + if (!value) + { + std::ostringstream os; + os << BOOST_CURRENT_FUNCTION << ": Empty value is passed."; + throw python_json_error(os.str()); + } + + if (m_stack.empty()) + { + std::ostringstream os; + os << BOOST_CURRENT_FUNCTION << ": Stack is unexpectedly empty."; + throw python_json_error(os.str()); + } + + parser_stack& cur = m_stack.back(); + + switch (cur.type) + { + case json::node_t::array: + { + PyList_Append(cur.node, value); + return value; + } + break; + case json::node_t::object: + { + assert(cur.key); + PyDict_SetItem(cur.node, cur.key, value); + cur.key = nullptr; + return value; + } + break; + default: + Py_DECREF(value); + } + + std::ostringstream os; + os << BOOST_CURRENT_FUNCTION << ": unstackable JSON value type."; + throw python_json_error(os.str()); + } + +public: + json_parser_handler() : m_root(nullptr) {} + + ~json_parser_handler() + { + if (m_root) + Py_XDECREF(m_root); + + std::for_each(m_stack.begin(), m_stack.end(), + [](parser_stack& ps) + { + if (ps.key) + { + Py_XDECREF(ps.key); + ps.key = nullptr; + } + } + ); + } + + void begin_parse() + { + if (m_root) + { + std::ostringstream os; + os << BOOST_CURRENT_FUNCTION << ": Root JSON value already exists."; + throw python_json_error(os.str()); + } + } + + void end_parse() {} + + void begin_array() + { + if (m_root) + { + PyObject* array = push_value(PyList_New(0)); + m_stack.push_back(parser_stack(array, json::node_t::array)); + } + else + { + m_root = PyList_New(0); + m_stack.push_back(parser_stack(m_root, json::node_t::array)); + } + } + + void end_array() + { + if (m_stack.empty()) + { + std::ostringstream os; + os << BOOST_CURRENT_FUNCTION << ": Stack is unexpectedly empty."; + throw python_json_error(os.str()); + } + + m_stack.pop_back(); + } + + void begin_object() + { + if (m_root) + { + PyObject* dict = push_value(PyDict_New()); + m_stack.push_back(parser_stack(dict, json::node_t::object)); + } + else + { + m_root = PyDict_New(); + m_stack.push_back(parser_stack(m_root, json::node_t::object)); + } + } + + void object_key(std::string_view key, bool /*transient*/) + { + parser_stack& cur = m_stack.back(); + cur.key = PyUnicode_FromStringAndSize(key.data(), key.size()); + } + + void end_object() + { + if (m_stack.empty()) + { + std::ostringstream os; + os << BOOST_CURRENT_FUNCTION << ": Stack is unexpectedly empty."; + throw python_json_error(os.str()); + } + + m_stack.pop_back(); + } + + void boolean_true() + { + Py_INCREF(Py_True); + push_value(Py_True); + } + + void boolean_false() + { + Py_INCREF(Py_False); + push_value(Py_False); + } + + void null() + { + Py_INCREF(Py_None); + push_value(Py_None); + } + + void string(std::string_view val, bool /*transient*/) + { + push_value(PyUnicode_FromStringAndSize(val.data(), val.size())); + } + + void number(double val) + { + push_value(PyFloat_FromDouble(val)); + } + + PyObject* get_root() + { + PyObject* o = m_root; + m_root = nullptr; + return o; + } +}; + +PyObject* json_loads(PyObject* /*module*/, PyObject* args, PyObject* kwargs) +{ + char* stream = nullptr; + static const char* kwlist[] = { "s", nullptr }; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", const_cast<char**>(kwlist), &stream)) + { + PyErr_SetString(PyExc_TypeError, "The method must be given a string."); + return nullptr; + } + + json_parser_handler hdl; + orcus::json_parser<json_parser_handler> parser(stream, hdl); + try + { + parser.parse(); + return hdl.get_root(); + } + catch (const orcus::parse_error& e) + { + PyErr_SetString(PyExc_TypeError, e.what()); + } + return nullptr; +} + +PyMethodDef orcus_methods[] = +{ + { "loads", (PyCFunction)json_loads, METH_VARARGS | METH_KEYWORDS, "Load JSON string into a Python object." }, + { nullptr, nullptr, 0, nullptr } +}; + +struct PyModuleDef moduledef = +{ + PyModuleDef_HEAD_INIT, + "_orcus_json", + nullptr, + sizeof(struct module_state), + orcus_methods, + nullptr, + orcus_traverse, + orcus_clear, + nullptr +}; + +} + +}} + +extern "C" { + +ORCUS_DLLPUBLIC PyObject* PyInit__orcus_json() +{ + PyObject* m = PyModule_Create(&orcus::python::moduledef); + return m; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |