# Program Listing for File serialize.hpp

↰ Return to documentation for file (`zeem/serialize.hpp`)

    /*-
     * SPDX-License-Identifier: BSD-2-Clause
     *
     * Copyright (c) 2024 Maarten L. Hekkelman
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer
     * 2. Redistributions in binary form must reproduce the above copyright notice,
     *    this list of conditions and the following disclaimer in the documentation
     *    and/or other materials provided with the distribution.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
     * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */

    #pragma once

    #include "zeem/config.hpp"
    #include "zeem/detail/charconv.hpp"
    #include "zeem/node.hpp"

    #if ZEEM_USE_DATE_H
    # include <date/date.h>
    # include <date/tz.h>
    #endif

    #include <algorithm>
    #include <charconv>
    #include <chrono>
    #include <map>
    #include <optional>
    #include <regex>
    #include <source_location>
    #include <string>
    #include <system_error>

    namespace zeem
    {

    // --------------------------------------------------------------------

    template <typename T>
    struct value_serializer;

    template <>
    struct value_serializer<bool>
    {
        static constexpr std::string type_name() { return "xsd:boolean"; }
        static constexpr std::string to_string(bool value) { return value ? "true" : "false"; }
        static constexpr bool from_string(std::string_view value) { return value == "true" or value == "1" or value == "yes"; }
    };

    template <>
    struct value_serializer<std::string>
    {
        static constexpr std::string type_name() { return "xsd:string"; }
        static constexpr std::string to_string(std::string value) { return value; }
        static constexpr std::string from_string(std::string_view value) { return std::string{ value }; }
    };

    template <typename T>
    struct char_conv_serializer
    {
        using value_type = T;

        static constexpr std::string derived_type_name()
        {
            using value_serializer_type = value_serializer<value_type>;
            return value_serializer_type::type_name();
        }

        static std::string to_string(value_type value)
        {
            char b[32];
            if (auto r = std::to_chars(b, b + sizeof(b), value); r.ec == std::errc{})
                return { b, r.ptr };
            else
                throw std::system_error(std::make_error_code(r.ec), "Error converting value to string for type " + derived_type_name());
        }

        static value_type from_string(std::string_view value)
        {
            value_type result{};

            auto r = from_chars(value.data(), value.data() + value.length(), result);
            if (r.ec != std::errc{} or r.ptr != value.data() + value.length())
                throw std::system_error(std::make_error_code(r.ec), "Error converting value '" + std::string{ value } + "' to type " + derived_type_name());

            return result;
        }
    };

    template <>
    struct value_serializer<int8_t> : char_conv_serializer<int8_t>
    {
        static std::string type_name() { return "xsd:byte"; }
    };

    template <>
    struct value_serializer<uint8_t> : char_conv_serializer<uint8_t>
    {
        static std::string type_name() { return "xsd:unsignedByte"; }
    };

    template <>
    struct value_serializer<int16_t> : char_conv_serializer<int16_t>
    {
        static std::string type_name() { return "xsd:short"; }
    };

    template <>
    struct value_serializer<uint16_t> : char_conv_serializer<uint16_t>
    {
        static std::string type_name() { return "xsd:unsignedShort"; }
    };

    template <>
    struct value_serializer<int32_t> : char_conv_serializer<int32_t>
    {
        static std::string type_name() { return "xsd:int"; }
    };

    template <>
    struct value_serializer<uint32_t> : char_conv_serializer<uint32_t>
    {
        static std::string type_name() { return "xsd:unsignedInt"; }
    };

    template <>
    struct value_serializer<int64_t> : char_conv_serializer<int64_t>
    {
        static std::string type_name() { return "xsd:long"; }
    };

    template <>
    struct value_serializer<uint64_t> : char_conv_serializer<uint64_t>
    {
        static std::string type_name() { return "xsd:unsignedLong"; }
    };

    template <>
    struct value_serializer<float> : char_conv_serializer<float>
    {
        static std::string type_name() { return "xsd:float"; }
    };

    template <>
    struct value_serializer<double> : char_conv_serializer<double>
    {
        static std::string type_name() { return "xsd:double"; }
    };

    template <typename T>
        requires std::is_enum_v<T>
    struct value_serializer<T>
    {
        using value_map_type = std::map<T, std::string>;
        using value_map_value_type = typename value_map_type::value_type;

      private:
        std::string m_type_name;
        value_map_type m_value_map;

      public:
        static void init(std::string_view name, std::initializer_list<value_map_value_type> values)
        {
            instance(std::string{ name }).m_value_map = value_map_type(values);
        }

        static void init(std::initializer_list<value_map_value_type> values)
        {
            instance().m_value_map = value_map_type(values);
        }

        static value_serializer &instance(std::string name = {})
        {
            static value_serializer s_instance;
            if (not name.empty() and s_instance.m_type_name.empty())
                s_instance.m_type_name = std::move(name);
            return s_instance;
        }

        value_serializer &operator()(T v, std::string_view name)
        {
            m_value_map[v] = name;
            return *this;
        }

        value_serializer &operator()(std::string name, T v)
        {
            m_value_map[v] = std::move(name);
            return *this;
        }

        static std::string type_name()
        {
            return instance().m_type_name;
        }

        static std::string to_string(T value)
        {
            return instance().m_value_map[value];
        }

        static T from_string(std::string_view value)
        {
            T result = {};
            for (auto &t : instance().m_value_map)
                if (t.second == value)
                {
                    result = t.first;
                    break;
                }
            return result;
        }

        static bool empty()
        {
            return instance().m_value_map.empty();
        }
    };

    // --------------------------------------------------------------------
    // date/time support

    template <>
    struct value_serializer<std::chrono::system_clock::time_point>
    {
        using time_type = std::chrono::system_clock::time_point;

        static std::string type_name() { return "xsd:dateTime"; }

        static std::string to_string(const time_type &v)
        {
            return std::format("{0:%F}T{0:%T}Z", v);
        }

        static time_type from_string(std::string_view s)
        {
            time_type result;

            std::regex kRX(R"(^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?)(Z|([-+]\d{2})(?::(\d{2}))?)?)");
            std::cmatch m;

            if (not std::regex_match(s.data(), s.data() + s.length(), m, kRX))
                throw std::runtime_error("Invalid date format");

            std::istringstream is{ m[1] };

            // Unfortunately, from_stream never seems to return the correct time zone and offset info
            // so we add it ourselves

    #if ZEEM_USE_DATE_H
            date::from_stream(is, "%FT%T", result);
    #else
            std::chrono::from_stream(is, "%FT%T", result);
    #endif

            if (m[2].matched)
            {
                if (m[3].matched)
                    result += std::chrono::hours{ stoi(m[3]) };
                if (m[4].matched)
                    result += std::chrono::minutes{ stoi(m[4]) };
            }
            else
    #if ZEEM_USE_DATE_H
                result = date::zoned_time(date::current_zone(), result).get_sys_time();
    #else
                result = std::chrono::zoned_time{ std::chrono::current_zone(), result };
    #endif

            if (is.bad() or is.fail())
                throw std::runtime_error("invalid formatted date");

            return result;
        }
    };

    template <>
    struct value_serializer<std::chrono::sys_days>
    {
        static std::string type_name() { return "xsd:date"; }

        static std::string to_string(const std::chrono::sys_days &v)
        {
            return std::format("{:%F}", v);
        }

        static std::chrono::sys_days from_string(std::string_view s)
        {
            std::chrono::sys_days result;

            std::stringstream is;
            is << s;

    #if ZEEM_USE_DATE_H
            date::from_stream(is, "%F", result);
    #else
            std::chrono::from_stream(is, "%F", result);
    #endif

            if (is.bad() or is.fail())
                throw std::runtime_error("invalid formatted date");

            return result;
        }
    };

    template <typename T>
    using serialize_value_t = decltype(std::declval<value_serializer<T> &>().from_string(std::declval<std::string_view>()));

    template <typename T, typename Archive>
    using serialize_function = decltype(std::declval<T &>().serialize(std::declval<Archive &>(), std::declval<uint64_t>()));

    template <typename T, typename Archive, typename = void>
    struct has_serialize : std::false_type
    {
    };

    template <typename T, typename Archive>
        requires(std::is_class_v<T>)
    struct has_serialize<T, Archive>
    {
        static constexpr bool value = detail::is_detected_v<serialize_function, T, Archive>;
    };

    template <typename T, typename S>
    inline constexpr bool has_serialize_v = has_serialize<T, S>::value;

    template <typename T, typename S, typename = void>
    struct is_serializable_array_type : std::false_type
    {
    };

    template <typename T>
    using value_type_t = typename T::value_type;

    template <typename T>
    using iterator_t = typename T::iterator;

    template <typename T>
    using std_string_npos_t = decltype(T::npos);

    template <typename T, typename S>
    struct is_serializable_type
    {
        using value_type = std::remove_cvref_t<T>;
        static constexpr bool value =
            detail::is_detected_v<serialize_value_t, value_type> or
            has_serialize_v<value_type, S>;
    };

    template <typename T, typename S>
    inline constexpr bool is_serializable_type_v = is_serializable_type<T, S>::value;

    template <typename T, typename S>
        requires(
            detail::is_detected_v<value_type_t, T> and
            detail::is_detected_v<iterator_t, T> and
            not detail::is_detected_v<std_string_npos_t, T>)
    struct is_serializable_array_type<T, S>
    {
        static constexpr bool value = is_serializable_type_v<typename T::value_type, S>;
    };

    template <typename T, typename S>
    inline constexpr bool is_serializable_array_type_v = is_serializable_array_type<T, S>::value;

    // --------------------------------------------------------------------

    struct serializer;
    struct deserializer;

    template <typename T>
    class name_value_pair
    {
      public:
        name_value_pair(std::string name, T &value)
            : m_name(std::move(name))
            , m_value(value)
        {
        }

        name_value_pair(const name_value_pair &) = default;
        name_value_pair(name_value_pair &&) = default;
        name_value_pair &operator=(const name_value_pair &) = default;
        name_value_pair &operator=(name_value_pair &&) = default;

        [[nodiscard]] const std::string &name() const { return m_name; }
        [[nodiscard]] T &value() const { return m_value; }

      private:
        std::string m_name;
        T &m_value;
    };

    template <typename T>
    class element_nvp : public name_value_pair<T>
    {
      public:
        element_nvp(std::string name, T &value)
            : name_value_pair<T>(std::move(name), value)
        {
        }
    };

    template <typename T>
    class attribute_nvp : public name_value_pair<T>
    {
      public:
        attribute_nvp(std::string name, T &value)
            : name_value_pair<T>(std::move(name), value)
        {
        }
    };

    template <typename T>
    constexpr attribute_nvp<T> make_attribute_nvp(std::string name, T &value)
    {
        return attribute_nvp(std::move(name), value);
    }

    template <typename T>
    constexpr element_nvp<T> make_element_nvp(std::string name, T &value)
    {
        return element_nvp(std::move(name), value);
    }

    struct serializer
    {
        explicit serializer(element_container &node)
            : m_node(node)
        {
        }

        template <typename T>
        serializer &operator&(const element_nvp<T> &rhs)
        {
            return serialize_element(rhs.name(), rhs.value());
        }

        template <typename T>
        serializer &operator&(const attribute_nvp<T> &rhs)
        {
            return serialize_attribute(rhs.name(), rhs.value());
        }

        template <typename T>
        serializer &serialize_element(const T &data);

        template <typename T>
        serializer &serialize_element(std::string_view name, const T &data);

        template <typename T>
        serializer &serialize_attribute(std::string_view name, const T &data);

      private:
        element_container &m_node;

    };

    struct deserializer
    {
        explicit deserializer(const element_container &node)
            : m_node(node)
        {
        }

        template <typename T>
        deserializer &operator&(const element_nvp<T> &rhs)
        {
            return deserialize_element(rhs.name(), rhs.value());
        }

        template <typename T>
        deserializer &operator&(const attribute_nvp<T> &rhs)
        {
            return deserialize_attribute(rhs.name(), rhs.value());
        }

        template <typename T>
        deserializer &deserialize_element(T &data);

        template <typename T>
        deserializer &deserialize_element(std::string_view name, T &data);

        template <typename T>
        deserializer &deserialize_attribute(std::string_view name, T &data);

      private:
        const element_container &m_node;

    };

    // --------------------------------------------------------------------

    template <typename T>
    struct type_serializer;

    template <typename T, size_t N>
    struct type_serializer<T[N]>
    {
        using value_type = std::remove_cvref_t<T>;
        using type_serializer_type = type_serializer<value_type>;

        static std::string type_name() { return type_serializer_type::type_name(); }

        static void serialize_child(element_container &n, std::string_view name, const value_type (&value)[N])
        {
            for (const value_type &v : value)
                type_serializer_type::serialize_child(n, name, v);
        }

        static void deserialize_child(const element_container &n, std::string_view name, value_type (&value)[N])
        {
            size_t ix = 0;
            for (auto &e : n)
            {
                if (e.name() != name)
                    continue;

                value_type v = {};
                type_serializer_type::deserialize_child(e, ".", v);

                value[ix] = std::move(v);
                ++ix;

                if (ix >= N)
                    break;
            }
        }
    };

    template <typename T>
        requires std::is_enum_v<T>
    struct type_serializer<T>
        : public value_serializer<T>
    {
        using value_type = T;
        using value_serializer_type = value_serializer<T>;
        using value_serializer_type::type_name;

        static std::string serialize_value(const T &value)
        {
            return value_serializer_type::to_string(value);
        }

        static T deserialize_value(std::string_view value)
        {
            return value_serializer_type::from_string(value);
        }

        static void serialize_child(element_container &n, std::string_view name, const value_type &value)
        {
            if (name.empty() or name == ".")
            {
                if (n.type() == node_type::element)
                    static_cast<element &>(n).set_content(value_serializer_type::to_string(value));
            }
            else
                n.emplace_back(name)->set_content(value_serializer_type::to_string(value));
        }

        static void deserialize_child(const element_container &n, std::string_view name, value_type &value)
        {
            value = value_type();

            if (name.empty() or name == ".")
            {
                if (n.type() == node_type::element)
                    value = value_serializer_type::from_string(static_cast<const element &>(n).get_content());
            }
            else
            {
                auto e = std::find_if(n.begin(), n.end(), [name](auto &e)
                    { return e.name() == name; });
                if (e != n.end())
                    value = value_serializer_type::from_string(e->get_content());
            }
        }
    };

    template <typename T>
        requires has_serialize_v<T, serializer>
    struct type_serializer<T>
    {
        using value_type = std::remove_cvref_t<T>;

        // the name of this type
        std::string m_type_name;

        static std::string type_name() { return instance().m_type_name.c_str(); }
        void type_name(std::string_view name) { m_type_name = name; }

        static type_serializer &instance()
        {
            static type_serializer s_instance{ std::source_location::current().function_name() };
            return s_instance;
        }

        static void serialize_child(element_container &n, std::string_view name, const value_type &value)
        {
            if (name.empty() or name == ".")
            {
                serializer sr(n);
                const_cast<value_type &>(value).serialize(sr, 0UL);
            }
            else
            {
                element *e = static_cast<element *>(n.emplace_back(name));
                serializer sr(*e);
                const_cast<value_type &>(value).serialize(sr, 0UL);
            }
        }

        static void deserialize_child(const element_container &n, std::string_view name, value_type &value)
        {
            value = value_type();

            if (name.empty() or name == ".")
            {
                deserializer sr(n);
                value.serialize(sr, 0UL);
            }
            else
            {
                auto e = std::find_if(n.begin(), n.end(), [name](auto &e)
                    { return e.name() == name; });
                if (e != n.end())
                {
                    deserializer sr(*e);
                    value.serialize(sr, 0UL);
                }
            }
        }
    };

    template <typename T>
    struct type_serializer<std::optional<T>>
    {
        using value_type = T;
        using container_type = std::optional<value_type>;
        using type_serializer_type = type_serializer<value_type>;

        static std::string type_name() { return type_serializer_type::type_name(); }

        static void serialize_child(element_container &n, std::string_view name, const container_type &value)
        {
            if (value.has_value())
                type_serializer_type::serialize_child(n, name, *value);
        }

        static void deserialize_child(const element_container &n, std::string_view name, container_type &value)
        {
            for (auto &e : n)
            {
                if (e.name() != name)
                    continue;

                value_type v = {};
                type_serializer_type::deserialize_child(e, ".", v);
                value.emplace(std::move(v));
            }
        }
    };

    // nice trick to enforce order in template selection
    template <unsigned N>
    struct priority_tag  : priority_tag<N - 1>
    {
    };

    template <>
    struct priority_tag<0>
    {
    };

    template <typename T>
        requires is_serializable_array_type_v<T, serializer>
    struct type_serializer<T>
    {
        using container_type = std::remove_cvref_t<T>;
        using value_type = value_type_t<container_type>;
        using type_serializer_type = type_serializer<value_type>;

        static std::string type_name() { return type_serializer_type::type_name(); }

        static void serialize_child(element_container &n, std::string_view name, const container_type &value)
        {
            for (const value_type &v : value)
                type_serializer_type::serialize_child(n, name, v);
        }

        template <size_t N>
        static auto deserialize_array(const element_container &n, std::string_view name,
            std::array<value_type, N> &value, [[maybe_unused]] priority_tag<2> pt)
        {
            size_t ix = 0;
            for (auto &e : n)
            {
                if (e.name() != name)
                    continue;

                value_type v = {};
                type_serializer_type::deserialize_child(e, ".", v);

                value[ix] = std::move(v);
                ++ix;

                if (ix >= N)
                    break;
            }
        }

        template <typename A>
        static auto deserialize_array(const element_container &n, std::string_view name, A &arr, [[maybe_unused]] priority_tag<1> pt)
            -> decltype(arr.reserve(std::declval<typename container_type::size_type>()),
                void())
        {
            arr.reserve(n.size());

            for (auto &e : n)
            {
                if (e.name() != name)
                    continue;

                value_type v = {};
                type_serializer_type::deserialize_child(e, ".", v);

                arr.emplace_back(std::move(v));
            }
        }

        static void deserialize_array(const element_container &n, std::string_view name, container_type &arr, [[maybe_unused]] priority_tag<0> pt)
        {
            for (auto &e : n)
            {
                if (e.name() != name)
                    continue;

                value_type v = {};
                type_serializer_type::deserialize_child(e, ".", v);

                arr.emplace_back(std::move(v));
            }
        }

        static void deserialize_child(const element_container &n, std::string_view name, container_type &value)
        {
            type_serializer::deserialize_array(n, name, value, priority_tag<2>{});
        }
    };

    template <typename T>
    struct type_serializer
    {
        using value_type = std::remove_cvref_t<T>;
        using value_serializer_type = value_serializer<value_type>;

        static std::string type_name() { return value_serializer_type::type_name(); }

        static std::string serialize_value(const T &value)
        {
            return value_serializer_type::to_string(value);
        }

        static T deserialize_value(std::string_view value)
        {
            return value_serializer_type::from_string(value);
        }

        static void serialize_child(element_container &n, std::string_view name, const value_type &value)
        {
            if (name.empty() or name == ".")
            {
                if (n.type() == node_type::element)
                    static_cast<element &>(n).set_content(value_serializer_type::to_string(value));
            }
            else
                n.emplace_back(name)->set_content(value_serializer_type::to_string(value));
        }

        static void deserialize_child(const element_container &n, std::string_view name, value_type &value)
        {
            value = {};

            if (name.empty() or name == ".")
            {
                if (n.type() == node_type::element)
                    value = value_serializer_type::from_string(static_cast<const element &>(n).get_content());
            }
            else
            {
                auto e = std::find_if(n.begin(), n.end(), [name](auto &e)
                    { return e.name() == name; });
                if (e != n.end())
                    value = value_serializer_type::from_string(e->get_content());
            }
        }
    };

    // And finally, the implementation of serializer, deserializer and schema_creator.

    template <typename T>
    serializer &serializer::serialize_element(const T &value)
    {
        using value_type = std::remove_cvref_t<T>;
        using type_serializer = type_serializer<value_type>;

        type_serializer::serialize_child(m_node, "", value);

        return *this;
    }

    template <typename T>
    serializer &serializer::serialize_element(std::string_view name, const T &value)
    {
        using value_type = std::remove_cvref_t<T>;
        using type_serializer = type_serializer<value_type>;

        type_serializer::serialize_child(m_node, name, value);

        return *this;
    }

    template <typename T>
    serializer &serializer::serialize_attribute(std::string_view name, const T &value)
    {
        using value_type = std::remove_cvref_t<T>;
        using type_serializer = type_serializer<value_type>;

        if (m_node.type() == node_type::element)
            static_cast<element &>(m_node).attributes().emplace(name, type_serializer::serialize_value(value));

        return *this;
    }

    template <typename T>
    deserializer &deserializer::deserialize_element(T &value)
    {
        using value_type = std::remove_cvref_t<T>;
        using type_serializer = type_serializer<value_type>;

        type_serializer::deserialize_child(m_node, "", value);

        return *this;
    }

    template <typename T>
    deserializer &deserializer::deserialize_element(std::string_view name, T &value)
    {
        using value_type = std::remove_cvref_t<T>;
        using type_serializer = type_serializer<value_type>;

        type_serializer::deserialize_child(m_node, name, value);

        return *this;
    }

    template <typename T>
    deserializer &deserializer::deserialize_attribute(std::string_view name, T &value)
    {
        using value_type = std::remove_cvref_t<T>;
        using type_serializer = type_serializer<value_type>;

        if (m_node.type() == node_type::element)
        {
            std::string attr = static_cast<const element &>(m_node).get_attribute(name);
            if (not attr.empty())
                value = type_serializer::deserialize_value(attr);
        }
        return *this;
    }

    // --------------------------------------------------------------------
    // Convenience routines

    template <typename T>
    void to_xml(zeem::element_container &e, const T &value)
    {
        serializer sr(e);
        sr.serialize_element(value);
    }

    template <typename T>
    void to_xml(zeem::element_container &e, std::string_view name, const T &value)
    {
        serializer sr(e);
        sr.serialize_element(name, value);
    }

    template <typename T>
    void from_xml(const zeem::element_container &e, T &value)
    {
        deserializer dsr(e);
        dsr.deserialize_element(value);
    }

    template <typename T>
    void from_xml(const zeem::element_container &e, std::string_view name, T &value)
    {
        deserializer dsr(e);
        dsr.deserialize_element(name, value);
    }

    } // namespace zeem