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 51fafc0d0c594527807ffbeae0075095fedc502a
parent 47d893bbb0127d01048076fbce364a7ba6f5eb1a
author Dimitrije Dobrota <mail@dimitrijedobrota.com>
date Thu, 24 Apr 2025 21:07:41 +0200

Redesign api for simplicity and flexibility

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

9 files changed, 141 insertions(+), 195 deletions(-)


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

@@ -16,7 +16,7 @@ int main() }; std::cout << html::html {
comment("Hello this is a comment"),
comment {"Hello this is a comment"},
html::ul { ul_attrs, html::li {

@@ -28,7 +28,7 @@ int main() "Item 2", }, },
html::meta(),
html::hr {},
}; return 0;

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

@@ -27,26 +27,16 @@ public: { }
explicit feed(std::string_view xmlns, std::span<const element> children)
: element_builder(attributes(xmlns), children)
{
}
explicit feed(const is_element auto&... children) : feed(default_xmlns, children...) { }
explicit feed(std::span<const element> children)
: feed(default_xmlns, children)
{
}
};
using hemplate::blank;
using hemplate::comment; using hemplate::element; using hemplate::transform;
using hemplate::transparent;
using hemplate::xml; // clang-format off

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

@@ -56,6 +56,11 @@ public: void set(const attribute_list& list); void set(attribute attr);
bool empty() const
{
return m_attributes.empty() && m_class.empty() && m_style.empty();
}
explicit operator std::string() const { std::string res;

@@ -91,20 +96,6 @@ private: } // namespace hemplate template<>
struct std::formatter<hemplate::attribute>
{
static constexpr auto parse(std::format_parse_context& ctx)
{
return ctx.begin();
}
static auto format(const hemplate::attribute& type, std::format_context& ctx)
{
return std::format_to(ctx.out(), "{}", static_cast<std::string>(type));
}
};
template<>
struct std::formatter<hemplate::attribute_list> { static constexpr auto parse(std::format_parse_context& ctx)

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

@@ -11,11 +11,16 @@ namespace hemplate {
using comment = element_builder<"comment", element::Type::Comment>;
using transparent = element_builder<"transparent", element::Type::Transparent>;
class HEMPLATE_EXPORT comment : public element
{
public:
explicit comment(std::string_view data)
: element(std::format("<-- {} -->", data), "", "")
{
}
};
class HEMPLATE_EXPORT xml
: public element_builder<"xml", element::Type::Special>
class HEMPLATE_EXPORT xml : public element
{ public: static constexpr const auto default_version = "1.0";

@@ -31,13 +36,15 @@ public: std::string_view version = default_version, std::string_view encoding = default_encoding )
: element_builder(data(version, encoding))
: element(
std::format("<? {}?>", attribute_list {version, encoding}), "", ""
)
{ } }; template<std::ranges::forward_range R>
transparent transform(
blank transform(
const R& range, based::Procedure<std::ranges::range_value_t<R>> auto proc ) {

@@ -48,6 +55,7 @@ transparent transform( res.emplace_back(proc(elem)); }
return transparent {res};
return blank {res};
}
} // namespace hemplate

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

@@ -18,102 +18,64 @@ class element; template<typename T> concept is_element = std::derived_from<T, element>;
class HEMPLATE_EXPORT element : public attribute_list
class HEMPLATE_EXPORT element
{ public: enum class Type : uint8_t { Atomic, Boolean,
Comment,
Special,
Transparent,
};
const attribute_list& attributes() const { return *this; }
private:
template<based::string_literal Tag, element::Type MyType>
friend class element_builder;
Type m_type;
std::string m_tag;
std::string m_otag;
std::string m_ctag;
std::vector<element> m_children; std::string m_data;
explicit element(
Type type, std::string_view tag, const is_element auto&... children
)
: m_type(type)
, m_tag(tag)
, m_children(std::initializer_list<element> {children...})
{
}
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(Type type, std::string_view tag, std::string_view data)
: m_type(type)
, m_tag(tag)
, m_data(data)
public:
explicit element(std::string_view open_tag)
: m_otag(open_tag)
{ } explicit element(
Type type, std::string_view tag, std::span<const element> children
std::string_view open_tag,
std::string_view close_tag,
std::string_view data
)
: m_type(type)
, m_tag(tag)
, m_children(children.begin(), children.end())
: m_otag(open_tag)
, m_ctag(close_tag)
, m_data(data)
{ } explicit element(
Type type,
std::string_view tag,
attribute_list attributes,
std::string_view open_tag,
std::string_view close_tag,
const is_element auto&... children )
: attribute_list(std::move(attributes))
, m_type(type)
, m_tag(tag)
: m_otag(open_tag)
, m_ctag(close_tag)
, m_children(std::initializer_list<element> {children...}) { } explicit element(
Type type,
std::string_view tag,
attribute_list attributes,
std::string_view data
)
: attribute_list(std::move(attributes))
, m_type(type)
, m_tag(tag)
, m_data(data)
{
}
explicit element(
Type type,
std::string_view tag,
attribute_list attributes,
std::string_view open_tag,
std::string_view close_tag,
std::span<const element> children )
: attribute_list(std::move(attributes))
, m_type(type)
, m_tag(tag)
, m_children(children.begin(), children.end())
: m_otag(open_tag)
, m_ctag(close_tag)
, m_children(std::begin(children), std::end(children))
{ }
void render_atomic(std::ostream& out, std::size_t indent_value) const;
void render_boolean(std::ostream& out, std::size_t indent_value) const;
void render_comment(std::ostream& out, std::size_t indent_value) const;
void render_special(std::ostream& out, std::size_t indent_value) const;
void render_children(std::ostream& out, std::size_t indent_value) const;
void render(std::ostream& out, std::size_t indent_value) const;
public:
explicit operator std::string() const { std::stringstream ss;

@@ -129,24 +91,89 @@ public: }; template<based::string_literal Tag, element::Type MyType>
class HEMPLATE_EXPORT element_builder : public element
class element_builder;
template<based::string_literal Tag>
class HEMPLATE_EXPORT element_builder<Tag, element::Type::Boolean>
: public element
{
static auto close() { return std::format("</{}>", Tag.data()); }
static auto open(const attribute_list& attrs = {})
{
return attrs.empty() ? std::format("<{}>", Tag.data())
: std::format("<{} {}>", Tag.data(), attrs);
}
public:
// NOLINTBEGIN *-no-array-decay
template<typename... Args>
explicit element_builder(Args&&... args)
: element(MyType, Tag.data(), std::forward<Args>(args)...)
explicit element_builder(std::string_view data)
: element(open(), close(), data)
{
}
explicit element_builder(const is_element auto&... children)
: element(open(), close(), children...)
{
}
explicit element_builder(std::span<const element> children)
: element(open(), close(), children)
{
}
explicit element_builder(const attribute_list& attrs, std::string_view data)
: element(open(attrs), close(), data)
{
}
explicit element_builder(
const attribute_list& attrs, const is_element auto&... children
)
: element(open(attrs), close(), children...)
{
}
explicit element_builder(
const attribute_list& attrs, std::span<const element> children
)
: element(open(attrs), close(), children)
{
}
};
template<based::string_literal Tag>
class HEMPLATE_EXPORT element_builder<Tag, element::Type::Atomic>
: public element
{
static auto open(const attribute_list& attrs = {})
{
return attrs.empty() ? std::format("<{} />", Tag.data())
: std::format("<{} {} />", Tag.data(), attrs);
}
public:
explicit element_builder(const attribute_list& list = {})
: element(open(list))
{
}
};
class HEMPLATE_EXPORT blank : public element
{
public:
explicit blank(std::string_view data)
: element("", "", data)
{
}
explicit blank(const is_element auto&... children)
: element("", "", children...)
{ }
template<typename... Args>
explicit element_builder(attribute_list list, Args&&... args)
: element(
MyType, Tag.data(), std::move(list), std::forward<Args>(args)...
)
explicit blank(std::span<const element> children)
: element("", "", children)
{ }
// NOLINTEND *-no-array-decay
}; } // namespace hemplate

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

@@ -5,17 +5,17 @@ namespace hemplate::html {
using hemplate::blank;
using hemplate::comment; using hemplate::element; using hemplate::transform;
using hemplate::transparent;
using hemplate::xml;
class doctype : public element_builder<"DOCTYPE", element::Type::Special>
class doctype : public element
{ public: explicit doctype()
: element_builder("!DOCTYPE html")
: element("<!DOCTYPE html>", "", "")
{ } };

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

@@ -33,24 +33,10 @@ public: { }
explicit rss(
std::string_view version,
std::string_view xmlns,
std::span<const element> children
)
: element_builder(attributes(version, xmlns), children)
{
}
explicit rss(const is_element auto&... children) : rss(default_version, default_xmlns, children...) { }
explicit rss(std::span<const element> children)
: rss(default_version, default_xmlns, children)
{
}
}; class HEMPLATE_EXPORT atomLink // NOLINT *-identifier-naming

@@ -84,10 +70,9 @@ public: explicit atomLink( std::string_view rel, std::string_view type,
attribute_list attrs,
std::span<const element> children
const is_element auto&... children
)
: element_builder(attributes(attrs, rel, type), children)
: atomLink(rel, type, {}, children...)
{ }

@@ -96,26 +81,16 @@ public: { }
explicit atomLink(attribute_list attrs, std::span<const element> children)
: atomLink(default_rel, default_type, std::move(attrs), children)
{
}
explicit atomLink(const is_element auto&... children) : atomLink({}, children...) { }
explicit atomLink(std::span<const element> children)
: atomLink({}, children)
{
}
};
using hemplate::blank;
using hemplate::comment; using hemplate::element; using hemplate::transform;
using hemplate::transparent;
using hemplate::xml; // clang-format off

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

@@ -25,26 +25,16 @@ public: { }
explicit urlset(std::string_view xmlns, std::span<const element> children)
: element_builder(attributes(xmlns), children)
{
}
explicit urlset(const is_element auto&... children) : urlset(default_xmlns, children...) { }
explicit urlset(std::span<const element> children)
: urlset(default_xmlns, children)
{
}
};
using hemplate::blank;
using hemplate::comment; using hemplate::element; using hemplate::transform;
using hemplate::transparent;
using hemplate::xml; // clang-format off

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

@@ -1,4 +1,3 @@
#include <format>
#include <ostream> #include <string>

@@ -7,24 +6,6 @@ namespace hemplate {
void element::render_comment(std::ostream& out, std::size_t indent_value) const
{
const std::string indent(indent_value, ' ');
out << indent << "<!-- " << m_data << " -->\n";
}
void element::render_atomic(std::ostream& out, std::size_t indent_value) const
{
const std::string indent(indent_value, ' ');
out << indent << std::format("<{} {}/>\n", m_tag, attributes());
}
void element::render_special(std::ostream& out, std::size_t indent_value) const
{
const std::string indent(indent_value, ' ');
out << indent << std::format("<{}>\n", m_data);
}
void element::render_children(std::ostream& out, std::size_t indent_value) const { for (const auto& child : m_children) {

@@ -36,49 +17,33 @@ void element::render(std::ostream& out, std::size_t indent_value) const { const std::string indent(indent_value, ' ');
switch (m_type) {
case Type::Atomic:
render_atomic(out, indent_value);
return;
case Type::Comment:
render_comment(out, indent_value);
return;
case Type::Special:
render_special(out, indent_value);
return;
case Type::Transparent:
if (m_otag.empty()) {
if (!m_data.empty()) {
out << indent << m_data << '\n';
} else {
render_children(out, indent_value);
return;
default:
break;
}
return;
}
if (m_tag.empty()) {
out << indent << m_data << '\n';
if (m_ctag.empty()) {
out << indent << m_otag << '\n';
return; } if (!m_data.empty()) {
out << indent << std::format("<{} {}>\n", m_tag, attributes());
if (!m_children.empty()) {
render_children(out, indent_value + 2);
} else {
out << indent << " " << m_data << '\n';
}
out << indent << std::format("</{}>\n", m_tag);
out << indent << m_otag << m_data << m_ctag << '\n';
return; } if (!m_children.empty()) {
out << indent << std::format("<{} {}>\n", m_tag, attributes());
out << indent << m_otag << '\n';
render_children(out, indent_value + 2);
out << indent << std::format("</{}>\n", m_tag);
out << m_ctag << '\n';
return; }
out << indent << std::format("<{0} {1}></{0}>\n", m_tag, attributes());
out << indent << m_otag << m_ctag << '\n';
} } // namespace hemplate