hemplate

Simple XML template engine
git clone git://git.dimitrijedobrota.com/hemplate.git
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING

commit 6317bad529beb61dcd1fefeac8a4e5278449672b
parent a4013908e40f037e1f775cac2697165670954199
author Dimitrije Dobrota <mail@dimitrijedobrota.com>
date Fri, 25 Apr 2025 19:40:56 +0200

Finally figure it out!

Diffstat:
M example/html.cpp | +++
M include/hemplate/atom.hpp | ++++++ ----
M include/hemplate/element.hpp | ++++++++++++++++++++++++++++++++++++++ --------------------------------------------
M include/hemplate/rss.hpp | +++++++++++++++++++++++++++++++ ---------------------------
M include/hemplate/sitemap.hpp | ++++++ ----
M source/element.cpp | +++++++++++++ ----------

6 files changed, 113 insertions(+), 107 deletions(-)


diff --git a/ example/html.cpp b/ example/html.cpp

@@ -1,4 +1,5 @@ #include <iostream>
#include <vector>
#include "hemplate/html.hpp"

@@ -15,6 +16,8 @@ int main() {"style", "margin-bottom: 1em"}, };
const std::vector<html::b> vec = {html::b("1"), html::b("2"), html::b("3")};
std::cout << html::html { comment {"Hello this is a comment"}, html::ul {

diff --git a/ include/hemplate/atom.hpp b/ include/hemplate/atom.hpp

@@ -21,13 +21,15 @@ class HEMPLATE_EXPORT feed : public element_boolean<"feed"> public: static constexpr const auto default_xmlns = "http://www.w3.org/2005/Atom";
explicit feed(std::string_view xmlns, const is_element auto&... children)
: element_builder(attributes(xmlns), children...)
template<typename... Args>
explicit feed(std::string_view xmlns, Args&&... args)
: element_builder(attributes(xmlns), std::forward<Args>(args)...)
{ }
explicit feed(const is_element auto&... children)
: feed(default_xmlns, children...)
template<typename... Args>
explicit feed(Args&&... args)
: element_builder(attributes(default_xmlns), std::forward<Args>(args)...)
{ } };

diff --git a/ include/hemplate/element.hpp b/ include/hemplate/element.hpp

@@ -6,6 +6,7 @@ #include <vector> #include <based/string.hpp>
#include <based/template.hpp>
#include "hemplate/attribute.hpp" #include "hemplate/hemplate_export.hpp"

@@ -13,16 +14,17 @@ namespace hemplate {
class element;
class element_base;
template<typename T>
concept is_element = std::derived_from<T, element>;
concept is_element = std::derived_from<T, element_base>;
class HEMPLATE_EXPORT element
class HEMPLATE_EXPORT element_base
{ public: enum class Type : uint8_t {
Blank,
Atomic, Boolean, };

@@ -34,52 +36,35 @@ private: std::string m_otag; std::string m_ctag;
std::vector<element> m_children;
std::string m_data;
using child_t = std::variant<element_base, std::string>;
std::vector<child_t> m_cdn;
void render_children(std::ostream& out, std::size_t indent_value) const; void render(std::ostream& out, std::size_t indent_value) const;
explicit element(
std::string_view open_tag,
std::string_view close_tag,
const is_element auto&... children
)
: m_otag(open_tag)
, m_ctag(close_tag)
, m_children(std::initializer_list<element> {children...})
{
}
explicit element(
std::string_view open_tag,
std::string_view close_tag,
std::span<const element> children
template<typename... Args>
explicit element_base(
std::string_view open_tag, std::string_view close_tag, Args&&... args
) : m_otag(open_tag) , m_ctag(close_tag)
, m_children(std::begin(children), std::end(children))
{
m_cdn.reserve(sizeof...(args));
const auto add = based::overload {
[&](const element_base& elem) { m_cdn.emplace_back(elem); },
[&](const std::ranges::forward_range auto& range)
requires(!std::constructible_from<std::string_view, decltype(range)>)
{
m_cdn.reserve(std::size(m_cdn) + std::size(range));
m_cdn.insert(std::end(m_cdn), std::begin(range), std::end(range));
},
[&](const std::string_view data)
{ m_cdn.emplace_back(std::string(data)); },
};
(add(std::forward<Args>(args)), ...);
} public:
// NOLINTBEGIN *-explicit-constructor
element(std::string_view data)
: m_data(data)
{
}
element(const is_element auto&... children)
: m_children(std::initializer_list<element> {children...})
{
}
element(std::span<const element> children)
: m_children(std::begin(children), std::end(children))
{
}
// NOLINTEND *-explicit-constructor
explicit operator std::string() const { std::stringstream ss;

@@ -87,19 +72,36 @@ public: return ss.str(); }
friend std::ostream& operator<<(std::ostream& out, const element& element)
friend std::ostream& operator<<(
std::ostream& out, const element_base& element
)
{ element.render(out, 0); return out; } };
template<based::string_literal Tag, element::Type MyType>
template<based::string_literal Tag, element_base::Type MyType>
class element_builder;
template<>
class HEMPLATE_EXPORT element_builder<"", element_base::Type::Blank>
: public element_base
{
public:
template<typename... Args>
requires(!std::same_as<attribute_list, std::remove_cvref_t<Args>> && ...)
explicit element_builder(Args&&... args)
: element_base("", "", std::forward<Args>(args)...)
{
}
};
using element = element_builder<"", element_base::Type::Blank>;
template<based::string_literal Tag>
class HEMPLATE_EXPORT element_builder<Tag, element::Type::Boolean>
: public element
class HEMPLATE_EXPORT element_builder<Tag, element_base::Type::Boolean>
: public element_base
{ static auto close() { return std::format("</{}>", Tag.data()); } static auto open(const attribute_list& attrs = {})

@@ -110,35 +112,25 @@ class HEMPLATE_EXPORT element_builder<Tag, element::Type::Boolean> public: template<typename... Args>
explicit element_builder(Args&&... args)
: element(open(), close(), element(std::forward<Args>(args))...)
{
}
explicit element_builder(std::span<const element> children)
: element(open(), close(), children)
{
}
template<typename... Args>
explicit element_builder(const attribute_list& attrs, Args&&... args)
: element(open(attrs), close(), element(std::forward<Args>(args))...)
: element_base(open(attrs), close(), std::forward<Args>(args)...)
{ }
explicit element_builder(
const attribute_list& attrs, std::span<const element> children
)
: element(open(attrs), close(), children)
template<typename... Args>
requires(!std::same_as<attribute_list, std::remove_cvref_t<Args>> && ...)
explicit element_builder(Args&&... args)
: element_base(open(), close(), std::forward<Args>(args)...)
{ } }; template<based::string_literal Tag>
using element_boolean = element_builder<Tag, element::Type::Boolean>;
using element_boolean = element_builder<Tag, element_base::Type::Boolean>;
template<based::string_literal Tag>
class HEMPLATE_EXPORT element_builder<Tag, element::Type::Atomic>
: public element
class HEMPLATE_EXPORT element_builder<Tag, element_base::Type::Atomic>
: public element_base
{ static auto open(const attribute_list& attrs = {}) {

@@ -148,12 +140,12 @@ class HEMPLATE_EXPORT element_builder<Tag, element::Type::Atomic> public: explicit element_builder(const attribute_list& list = {})
: element(open(list))
: element_base("", "", open(list))
{ } }; template<based::string_literal Tag>
using element_atomic = element_builder<Tag, element::Type::Atomic>;
using element_atomic = element_builder<Tag, element_base::Type::Atomic>;
} // namespace hemplate

diff --git a/ include/hemplate/rss.hpp b/ include/hemplate/rss.hpp

@@ -23,17 +23,18 @@ public: static constexpr const auto default_version = "2.0"; static constexpr const auto default_xmlns = "http://www.w3.org/2005/Atom";
explicit rss(
std::string_view version,
std::string_view xmlns,
const is_element auto&... children
)
: element_builder(attributes(version, xmlns), children...)
template<typename... Args>
explicit rss(std::string_view version, std::string_view xmlns, Args&&... args)
: element_builder(attributes(version, xmlns), std::forward<Args>(args)...)
{ }
explicit rss(const is_element auto&... children)
: rss(default_version, default_xmlns, children...)
template<typename... Args>
explicit rss(Args&&... args)
: element_builder(
attributes(default_version, default_xmlns),
std::forward<Args>(args)...
)
{ } };

@@ -42,46 +43,49 @@ class HEMPLATE_EXPORT atomLink // NOLINT *-identifier-naming : public element_boolean<"atom:link"> { static auto attributes(
attribute_list& list, std::string_view rel, std::string_view type
const attribute_list& list, std::string_view rel, std::string_view type
) {
list.set({
{"rel", rel},
{"type", type},
});
return list;
return attribute_list {list, {{"rel", rel}, {"type", type}}};
} public: static constexpr const auto default_rel = "self"; static constexpr const auto default_type = "application/rss+xml";
template<typename... Args>
explicit atomLink( std::string_view rel, std::string_view type,
attribute_list attrs,
const is_element auto&... children
const attribute_list& attrs,
Args&&... args
)
: element_builder(attributes(attrs, rel, type), children...)
: element_builder(
attributes(attrs, rel, type), std::forward<Args>(args)...
)
{ }
explicit atomLink(
std::string_view rel,
std::string_view type,
const is_element auto&... children
)
: atomLink(rel, type, {}, children...)
template<typename... Args>
explicit atomLink(std::string_view rel, std::string_view type, Args&&... args)
: element_builder(
attributes({}, rel, type), {}, std::forward<Args>(args)...
)
{ }
explicit atomLink(attribute_list attrs, const is_element auto&... children)
: atomLink(default_rel, default_type, std::move(attrs), children...)
template<typename... Args>
explicit atomLink(const attribute_list& attrs, Args&&... args)
: element_builder(
attributes(attrs, default_rel, default_type),
std::forward<Args>(args)...
)
{ }
explicit atomLink(const is_element auto&... children)
: atomLink({}, children...)
template<typename... Args>
explicit atomLink(Args&&... args)
: element_builder(std::forward<Args>(args)...)
{ } };

diff --git a/ include/hemplate/sitemap.hpp b/ include/hemplate/sitemap.hpp

@@ -19,13 +19,15 @@ public: static constexpr const auto default_xmlns = "http://www.sitemaps.org/schemas/sitemap/0.9";
explicit urlset(std::string_view xmlns, const is_element auto&... children)
: element_builder(attributes(xmlns), children...)
template<typename... Args>
explicit urlset(std::string_view xmlns, Args&&... args)
: element_builder(attributes(xmlns), std::forward<Args>(args)...)
{ }
explicit urlset(const is_element auto&... children)
: urlset(default_xmlns, children...)
template<typename... Args>
explicit urlset(Args&&... args)
: urlset(default_xmlns, std::forward<Args>(args)...)
{ } };

diff --git a/ source/element.cpp b/ source/element.cpp

@@ -6,27 +6,30 @@ namespace hemplate {
void element::render_children(std::ostream& out, std::size_t indent_value) const
void element_base::render_children(std::ostream& out, std::size_t indent_value)
const
{
for (const auto& child : m_children) {
child.render(out, indent_value);
const auto render = based::overload {
[&](const element_base& elem) { elem.render(out, indent_value); },
[&](const std::string& data)
{ out << std::string(indent_value, ' ') << data << '\n'; },
};
for (const auto& child : m_cdn) {
std::visit(render, child);
} }
void element::render(std::ostream& out, std::size_t indent_value) const
void element_base::render(std::ostream& out, std::size_t indent_value) const
{ const std::string indent(indent_value, ' '); if (m_otag.empty()) {
if (!m_data.empty()) {
out << indent << m_data << '\n';
} else {
render_children(out, indent_value);
}
render_children(out, indent_value);
return; }
if (!m_children.empty()) {
if (!m_cdn.empty()) {
out << indent << m_otag << '\n'; render_children(out, indent_value + 2); out << indent << m_ctag << '\n';