.. _program_listing_file_mxml_serialize.hpp: Program Listing for File serialize.hpp ====================================== |exhale_lsh| :ref:`Return to documentation for file ` (``mxml/serialize.hpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp /*- * 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 "mxml/node.hpp" #include "mxml/detail/charconv.hpp" #include #include #include #include #include #include #include #if __has_include() # include # include #endif namespace mxml { // -------------------------------------------------------------------- template struct value_serializer; template <> struct value_serializer { static std::string type_name() { return "xsd:boolean"; } static constexpr std::string_view 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 { static std::string type_name() { return "xsd:string"; } static std::string to_string(std::string value) { return value; } static std::string from_string(std::string_view value) { return std::string{ value }; } }; template struct char_conv_serializer { using value_type = T; static constexpr std::string derived_type_name() { using value_serializer_type = value_serializer; 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 = detail::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 : char_conv_serializer { static std::string type_name() { return "xsd:byte"; } }; template <> struct value_serializer : char_conv_serializer { static std::string type_name() { return "xsd:unsignedByte"; } }; template <> struct value_serializer : char_conv_serializer { static std::string type_name() { return "xsd:short"; } }; template <> struct value_serializer : char_conv_serializer { static std::string type_name() { return "xsd:unsignedShort"; } }; template <> struct value_serializer : char_conv_serializer { static std::string type_name() { return "xsd:int"; } }; template <> struct value_serializer : char_conv_serializer { static std::string type_name() { return "xsd:unsignedInt"; } }; template <> struct value_serializer : char_conv_serializer { static std::string type_name() { return "xsd:long"; } }; template <> struct value_serializer : char_conv_serializer { static std::string type_name() { return "xsd:unsignedLong"; } }; template <> struct value_serializer : char_conv_serializer { static std::string type_name() { return "xsd:float"; } }; template <> struct value_serializer : char_conv_serializer { static std::string type_name() { return "xsd:double"; } }; template requires std::is_enum_v struct value_serializer { std::string m_type_name; using value_map_type = std::map; using value_map_value_type = typename value_map_type::value_type; value_map_type m_value_map; static void init(std::string_view name, std::initializer_list values) { instance(name).m_value_map = value_map_type(values); } static void init(std::initializer_list 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 // We're using Howard Hinands date functions here. If available... #if __has_include() template <> struct value_serializer { 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 date::format("%FT%TZ", 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::stringstream is; is << s; if (m[1].matched) { if (m[1] == "Z") date::from_stream(is, "%FT%TZ", result); else date::from_stream(is, "%FT%T%0z", result); } else date::from_stream(is, "%FT%T", result); if (is.bad() or is.fail()) throw std::runtime_error("invalid formatted date"); return result; } }; template <> struct value_serializer { static std::string type_name() { return "xsd:date"; } static std::string to_string(const date::sys_days &v) { std::ostringstream ss; date::to_stream(ss, "%F", v); return ss.str(); } static date::sys_days from_string(std::string_view s) { date::sys_days result; std::stringstream is; is << s; date::from_stream(is, "%F", result); if (is.bad() or is.fail()) throw std::runtime_error("invalid formatted date"); return result; } }; #endif template using serialize_value_t = decltype(std::declval &>().from_string(std::declval())); template using serialize_function = decltype(std::declval().serialize(std::declval(), std::declval())); template struct has_serialize : std::false_type { }; template struct has_serialize>> { static constexpr bool value = detail::is_detected_v; }; template inline constexpr bool has_serialize_v = has_serialize::value; template struct is_serializable_array_type : std::false_type { }; template using value_type_t = typename T::value_type; template using iterator_t = typename T::iterator; template using std_string_npos_t = decltype(T::npos); template struct is_serializable_type { using value_type = std::remove_cvref_t; static constexpr bool value = detail::is_detected_v or has_serialize_v; }; template inline constexpr bool is_serializable_type_v = is_serializable_type::value; template struct is_serializable_array_type and detail::is_detected_v and not detail::is_detected_v>> { static constexpr bool value = is_serializable_type_v; }; template inline constexpr bool is_serializable_array_type_v = is_serializable_array_type::value; // -------------------------------------------------------------------- struct serializer; struct deserializer; template 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; const std::string &name() const { return m_name; } // T &value() { return m_value; } T &value() const { return m_value; } private: std::string m_name; T &m_value; }; template class element_nvp : public name_value_pair { public: element_nvp(std::string name, T &value) : name_value_pair(std::move(name), value) { } }; template class attribute_nvp : public name_value_pair { public: attribute_nvp(std::string name, T &value) : name_value_pair(std::move(name), value) { } }; template constexpr attribute_nvp make_attribute_nvp(std::string name, T &value) { return attribute_nvp(std::move(name), value); } template constexpr element_nvp make_element_nvp(std::string name, T &value) { return element_nvp(std::move(name), value); } struct serializer { serializer(element_container &node) : m_node(node) { } template serializer &operator&(const element_nvp &rhs) { return serialize_element(rhs.name(), rhs.value()); } template serializer &operator&(const attribute_nvp &rhs) { return serialize_attribute(rhs.name(), rhs.value()); } template serializer &serialize_element(const T &data); template serializer &serialize_element(std::string_view name, const T &data); template serializer &serialize_attribute(std::string_view name, const T &data); element_container &m_node; }; struct deserializer { deserializer(const element_container &node) : m_node(node) { } template deserializer &operator&(const element_nvp &rhs) { return deserialize_element(rhs.name(), rhs.value()); } template deserializer &operator&(const attribute_nvp &rhs) { return deserialize_attribute(rhs.name(), rhs.value()); } template deserializer &deserialize_element(T &data); template deserializer &deserialize_element(std::string_view name, T &data); template deserializer &deserialize_attribute(std::string_view name, T &data); const element_container &m_node; }; // -------------------------------------------------------------------- template struct type_serializer; template struct type_serializer { using value_type = std::remove_cvref_t; using type_serializer_type = type_serializer; 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 requires std::is_enum_v struct type_serializer : public value_serializer { using value_type = T; using value_serializer_type = value_serializer; 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(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(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 requires has_serialize_v struct type_serializer { using value_type = std::remove_cvref_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).serialize(sr, 0Ul); } else { element *e = (element *)n.emplace_back(name); serializer sr(*e); const_cast(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 struct type_serializer> { using value_type = T; using container_type = std::optional; using type_serializer_type = type_serializer; 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 struct priority_tag : priority_tag { }; template <> struct priority_tag<0> { }; template requires is_serializable_array_type_v struct type_serializer { using container_type = std::remove_cvref_t; using value_type = value_type_t; using type_serializer_type = type_serializer; 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 static auto deserialize_array(const element_container &n, std::string_view name, std::array &value, priority_tag<2>) { 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 static auto deserialize_array(const element_container &n, std::string_view name, A &arr, priority_tag<1>) -> decltype(arr.reserve(std::declval()), 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, priority_tag<0>) { 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 struct type_serializer { using value_type = std::remove_cvref_t; using value_serializer_type = value_serializer; 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(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(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 serializer &serializer::serialize_element(const T &value) { using value_type = std::remove_cvref_t; using type_serializer = type_serializer; type_serializer::serialize_child(m_node, "", value); return *this; } template serializer &serializer::serialize_element(std::string_view name, const T &value) { using value_type = std::remove_cvref_t; using type_serializer = type_serializer; type_serializer::serialize_child(m_node, name, value); return *this; } template serializer &serializer::serialize_attribute(std::string_view name, const T &value) { using value_type = std::remove_cvref_t; using type_serializer = type_serializer; if (m_node.type() == node_type::element) static_cast(m_node).attributes().emplace(name, type_serializer::serialize_value(value)); return *this; } template deserializer &deserializer::deserialize_element(T &value) { using value_type = std::remove_cvref_t; using type_serializer = type_serializer; type_serializer::deserialize_child(m_node, "", value); return *this; } template deserializer &deserializer::deserialize_element(std::string_view name, T &value) { using value_type = std::remove_cvref_t; using type_serializer = type_serializer; type_serializer::deserialize_child(m_node, name, value); return *this; } template deserializer &deserializer::deserialize_attribute(std::string_view name, T &value) { using value_type = std::remove_cvref_t; using type_serializer = type_serializer; if (m_node.type() == node_type::element) { std::string attr = static_cast(m_node).get_attribute(name); if (not attr.empty()) value = type_serializer::deserialize_value(attr); } return *this; } // -------------------------------------------------------------------- // Convenience routines template void to_xml(mxml::element_container &e, const T &value) { serializer sr(e); sr.serialize_element(value); } template void to_xml(mxml::element_container &e, std::string_view name, const T &value) { serializer sr(e); sr.serialize_element(name, value); } template void from_xml(const mxml::element_container &e, T &value) { deserializer dsr(e); dsr.deserialize_element(value); } template void from_xml(const mxml::element_container &e, std::string_view name, T &value) { deserializer dsr(e); dsr.deserialize_element(name, value); } } // namespace mxml