Program Listing for File serialize.hpp
↰ Return to documentation for file (mxml/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 "mxml/node.hpp"
#include "mxml/detail/charconv.hpp"
#include <algorithm>
#include <charconv>
#include <map>
#include <optional>
#include <source_location>
#include <string>
#include <system_error>
#if __has_include(<date/date.h>)
# include <date/date.h>
# include <regex>
#endif
namespace mxml
{
// --------------------------------------------------------------------
template <typename T>
struct value_serializer;
template <>
struct value_serializer<bool>
{
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<std::string>
{
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 <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 = 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<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>
{
std::string m_type_name;
using value_map_type = std::map<T, std::string>;
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<value_map_value_type> values)
{
instance(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
// We're using Howard Hinands date functions here. If available...
#if __has_include(<date/date.h>)
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 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<date::sys_days>
{
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 <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<unsigned long>()));
template <typename T, typename Archive, typename = void>
struct has_serialize : std::false_type
{
};
template <typename T, typename Archive>
struct has_serialize<T, Archive, typename std::enable_if_t<std::is_class_v<T>>>
{
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>
struct is_serializable_array_type<T, S,
std::enable_if_t<
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>>>
{
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;
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 <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
{
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);
element_container &m_node;
};
struct deserializer
{
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);
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 = (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, 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 <typename A>
static auto deserialize_array(const element_container &n, std::string_view name, A &arr, priority_tag<1>)
-> 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, 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 <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(mxml::element_container &e, const T &value)
{
serializer sr(e);
sr.serialize_element(value);
}
template <typename T>
void to_xml(mxml::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 mxml::element_container &e, T &value)
{
deserializer dsr(e);
dsr.deserialize_element(value);
}
template <typename T>
void from_xml(const mxml::element_container &e, std::string_view name, T &value)
{
deserializer dsr(e);
dsr.deserialize_element(name, value);
}
} // namespace mxml