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';