#include <iostream>
#include <stdexcept>
#include <tuple>
#include <vector>
#include <map>
#include <string>
#include <cstring>
#include <sstream>
#include <pugixml.hpp>
using namespace std::literals::string_literals;
struct NodeData
{
std::string name;
std::string text;
std::map<std::string, std::vector<NodeData>> subnodes;
std::map<std::string, std::string> attributes;
};
class Required;
class copy_t {};
class NodeBase {};
template<const char* name, class... Args>
class Node;
class AttributeBase {};
template<const char* name, class... Args>
class Attribute;
inline void parse_subnodes(NodeData& data, pugi::xml_node& node);
template<class NodeDescription, class... NodeDescriptions>
inline void parse_subnodes(NodeData& data, pugi::xml_node& node, NodeDescription desc, NodeDescriptions... descs);
template<class... Args>
struct is_required;
template<>
struct is_required<> : std::false_type { };
template<class Arg, class... Args>
struct is_required<Arg, Args...> : std::conditional_t<std::is_same_v<Arg, Required>, std::true_type, is_required<Args...>> { };
template<class... Args>
constexpr bool is_required_v = is_required<Args...>::value;
template<class NodeType>
struct NodeName;
template<const char* name_, class... Args>
struct NodeName<Node<name_, Args...>>
{
static inline constexpr const char* name = name_;
};
template<const char* name_, class... Args>
struct NodeName<Attribute<name_, Args...>>
{
static inline constexpr const char* name = name_;
};
class Required
{
public:
Required() { }
inline auto subnode(pugi::xml_node node) { return node; }
inline bool validate(pugi::xml_node node) { return true; }
inline void parse(NodeData& data, pugi::xml_node node) { }
template<class ParentNode>
inline void serialize(ParentNode& parent, const NodeData& data) { }
};
template<const char* name, class... Args>
class Attribute : AttributeBase
{
public:
inline Attribute(Args&&... args)
{ }
inline bool validate(pugi::xml_attribute attr)
{
if (!attr)
{
if (is_required_v<Args...>) throw std::runtime_error("Expected xml attribute "s + name);
return false;
}
return true;
}
inline auto subnode(pugi::xml_node node)
{
auto attr = node.attribute(name);
return attr;
}
inline void parse(NodeData& data, pugi::xml_attribute attr)
{
data.attributes[name] = attr.as_string();
}
template<class ParentNode>
inline void serialize(ParentNode& parent, const NodeData& data)
{
auto it = data.attributes.find(name);
auto end = data.attributes.end();
if (it == end) return;
parent.append_attribute(name) = it->second.c_str();
}
};
template<class... Args>
class Text
{
public:
inline Text(Args... args)
{ }
inline auto subnode(pugi::xml_node node)
{
std::cout << "Node: " << node.attribute("id").as_string() << std::endl;
return node.text();
}
inline bool validate(pugi::xml_text text)
{
if (text.empty())
{
if (is_required_v<Args...>) throw std::runtime_error("A text node is required");
return false;
}
return true;
}
inline void parse(NodeData& data, pugi::xml_text textNode)
{
data.text = textNode.as_string();
}
template<class ParentNode>
inline void serialize(ParentNode& parent, const NodeData& data)
{
parent.text().set(data.text.c_str());
}
};
template<const char* name, class... Args>
class Node : NodeBase
{
public:
inline Node(copy_t copy, const Node& other)
: args{other.args}
{ }
inline Node(Args... args)
: args{std::forward<Args>(args)...}
{ }
inline bool validate(pugi::xml_node node)
{
if (!node)
{
if (is_required_v<Args...>) throw std::runtime_error("Expected an xml node of name "s + name);
return false;
}
if (std::strcmp(name, node.name()))
throw std::runtime_error("Expected "s + name + " node instead of "s + node.name());
return true;
}
inline auto subnode(pugi::xml_node node)
{
auto subnode = node.child(name);
return subnode;
}
inline void parse(NodeData& data, pugi::xml_node node)
{
data.name = name;
std::apply([&](auto&... args) { parse_subnodes(data, node, args...); }, args);
}
template<class ParentNode>
inline void serialize(ParentNode& parent, const NodeData& data)
{
pugi::xml_node node = parent.append_child(data.name.c_str());
std::apply([&](auto&... args) { serialize_subnodes(node, data, args...); }, args);
validate(node);
}
std::tuple<std::decay_t<Args>...> args;
};
template<class SubNodeType, class... Args>
class NodeList
{
public:
inline NodeList(const SubNodeType& node, Args... args)
: subNodeType(node)
{ }
inline auto subnode(pugi::xml_node node)
{
auto children = node.children(NodeName<SubNodeType>::name);
return children;
}
inline bool validate(pugi::xml_object_range<pugi::xml_named_node_iterator> children)
{
for (auto& child : children) if (!subNodeType.validate(child)) return false;
return true;
}
inline void parse(NodeData& data, pugi::xml_object_range<pugi::xml_named_node_iterator> children)
{
auto& subnodes = data.subnodes[NodeName<SubNodeType>::name];
for (auto& child : children)
{
subnodes.emplace_back();
auto& subnode = subnodes.back();
subNodeType.parse(subnode, child);
}
}
template<class ParentNode>
inline void serialize(ParentNode& parent, const NodeData& data)
{
auto it = data.subnodes.find(NodeName<SubNodeType>::name);
auto end = data.subnodes.end();
if (it == end) return;
for (auto& child : it->second) subNodeType.serialize(parent, child);
}
SubNodeType subNodeType;
};
inline void serialize_subnodes(pugi::xml_node& parent, const NodeData& data)
{ }
template<class NodeDescription, class... NodeDescriptions>
inline void serialize_subnodes(pugi::xml_node& parent, const NodeData& data, NodeDescription desc, NodeDescriptions... descs)
{
desc.serialize(parent, data);
serialize_subnodes(parent, data, descs...);
}
inline void parse_subnodes(NodeData& data, pugi::xml_node& node)
{ }
template<class NodeDescription, class... NodeDescriptions>
inline void parse_subnodes(NodeData& data, pugi::xml_node& node, NodeDescription desc, NodeDescriptions... descs)
{
auto subnode = desc.subnode(node);
if (desc.validate(subnode)) desc.parse(data, subnode);
parse_subnodes(data, node, descs...);
}
template<class NodeDescription>
inline auto parse(const std::string& s, NodeDescription desc)
{
NodeData data;
pugi::xml_document doc;
doc.load_buffer(s.data(), s.size());
desc.validate(doc.document_element());
desc.parse(data, doc.document_element());
return data;
}
template<class NodeDescription>
inline auto serialize(const NodeData& data, NodeDescription desc)
{
pugi::xml_document doc;
auto root = doc.append_child(data.name.c_str());
std::apply([&](auto&... args) { serialize_subnodes(root, data, args...); }, desc.args);
desc.validate(root);
std::stringstream ss;
doc.print(ss, "", pugi::format_raw);
return ss.str();
}
template<const char* name>
class NodeBuilder
{
public:
template<class... Args>
auto operator()(Args... args) {
return Node<name, Args...>(std::forward<Args>(args)...);
}
};
template<const char* name>
class AttributeBuilder
{
public:
template<class... Args>
auto operator()(Args... args) {
return Attribute<name, Args...>(std::forward<Args>(args)...);
}
};
template<class CharT, CharT... chars> auto operator""_node()
{
static const char name[] = {chars..., 0};
return NodeBuilder<name>();
}
template<class CharT, CharT... chars> auto operator""_attr()
{
static const char name[] = {chars..., 0};
return AttributeBuilder<name>();
}
int main()
{
auto xml =
"root"_node(
Required(),
"key"_attr(Required()),
"client_id"_attr(),
NodeList(
"data"_node(
"id"_attr(Required()),
Text(Required()))));
auto examples = {
"<wrong />"s,
"<root />"s,
"<root key=\"mykey\" />"s,
"<root key=\"mykey\"><data id=\"1\" /></root>"s,
"<root key=\"mykey\"><data id=\"1\">D1</data><data id=\"2\">D2</data></root>"s
};
for (const auto& s : examples)
{
try
{
std::cout << "== Example: " << s << std::endl;
auto root = parse(s, xml);
std::cout << "OK" << std::endl;
auto dataNodeCount = root.subnodes["data"].size();
std::cout << "KEY: " << root.attributes["key"] << '\n'
<< "Data subnode count: " << dataNodeCount << '\n'
<< "Data1 value: " << (dataNodeCount > 0 ? root.subnodes["data"].at(0).text : "") << '\n'
<< "Data2 value: " << (dataNodeCount > 1 ? root.subnodes["data"].at(1).text : "") << '\n'
<< std::flush;
std::cout << "Serialized: " << serialize(root, xml) << std::endl;
}
catch(const std::exception& e)
{
std::cout << e.what() << std::endl;
}
}
return 0;
}