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 6beaa8d2241e59447631bf587da23d85b65b0234
parent 75399e4340eb97655444be98f94bc795ed5285a8
author Dimitrije Dobrota <mail@dimitrijedobrota.com>
date Sat, 19 Apr 2025 19:31:53 +0200

Mid rewrite

Diffstat:
M CMakeLists.txt | + -
M include/hemplate/attribute.hpp | +++++++++++++++++++++++++++++++++++++++ -------------------
M include/hemplate/classes.hpp | +++++++++++ --
M include/hemplate/element.hpp | +++++++++++++++++++++++++++++++++++++++++++++++ -----------------------------------
D include/hemplate/elementAtomic.hpp | --------------------------------------
D include/hemplate/elementBoolean.hpp | -----------------------------------------------------------------
M include/hemplate/streamable.hpp | ++++++++++++++++++++++++ ----------
M source/attribute.cpp | --------------------------
M source/element.cpp | +++++++++++++++++++++++++++++ -----------------------------------------------------
M test/source/hemplate_test.cpp | +++++++++++ ---------

10 files changed, 207 insertions(+), 298 deletions(-)


diff --git a/ CMakeLists.txt b/ CMakeLists.txt

@@ -4,7 +4,7 @@ include(cmake/prelude.cmake) project( hemplate
VERSION 0.3.0
VERSION 0.4.0
DESCRIPTION "Simple HTML template engine" HOMEPAGE_URL "https://git.dimitrijedobrota.com/hemplate.git" LANGUAGES CXX

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

@@ -1,5 +1,6 @@ #pragma once
#include <iostream>
#include <string> #include <utility> #include <vector>

@@ -9,16 +10,9 @@ namespace hemplate {
class HEMPLATE_EXPORT attribute : public streamable
class HEMPLATE_EXPORT attribute : public streamable<attribute>
{ public:
attribute() = default;
attribute(const attribute&) = default;
attribute(attribute&&) = default;
attribute& operator=(const attribute&) = default;
attribute& operator=(attribute&&) = default;
~attribute() override = default;
attribute(std::string name) // NOLINT : m_name(std::move(name)) {

@@ -30,8 +24,7 @@ public: { }
bool operator!=(const attribute& rhs) const;
bool operator==(const attribute& rhs) const;
bool operator==(const attribute& rhs) const = default;
const std::string& get_name() const { return m_name; } const std::string& get_value() const { return m_value; }

@@ -39,22 +32,23 @@ public: void set_name(const std::string& name) { m_name = name; } void set_value(const std::string& value) { m_value = value; }
void render(std::ostream& out) const override;
bool empty() const { return get_value().empty(); }
// NOLINTNEXTLINE *-explicit-constructor
operator std::string() const
{
return get_name() + "=\"" + get_value() + "\" ";
}
private: std::string m_name; std::string m_value; };
class HEMPLATE_EXPORT attributeList : public streamable
class HEMPLATE_EXPORT attributeList : public streamable<attributeList>
{ public:
attributeList() = default;
attributeList(const attributeList&) = default;
attributeList(attributeList&&) = default;
attributeList& operator=(const attributeList&) = default;
attributeList& operator=(attributeList&&) = default;
~attributeList() override = default;
attributeList() = default;
attributeList(std::initializer_list<attribute> list); attributeList(attribute attr); // NOLINT

@@ -64,7 +58,30 @@ public: bool empty() const;
void render(std::ostream& out) const override;
explicit operator std::string() const
{
std::string res;
if (!m_class.empty())
{
res += m_class;
res += ' ';
}
if (!m_style.empty())
{
res += m_style;
res += ' ';
}
for (const auto& attr : m_attributes)
{
res += attr;
res += ' ';
}
return res;
}
private: std::vector<attribute> m_attributes;

@@ -73,3 +90,6 @@ private: }; } // namespace hemplate
CUSTOM_FORMAT(hemplate::attribute)
CUSTOM_FORMAT(hemplate::attributeList)

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

@@ -3,8 +3,7 @@ #include <array> #include <cstdint>
#include "hemplate/elementAtomic.hpp"
#include "hemplate/elementBoolean.hpp"
#include "hemplate/element.hpp"
#include "hemplate/hemplate_export.hpp" namespace hemplate {

@@ -30,6 +29,7 @@ struct tag static char const* get_name() { return Name.data(); } };
/*
class comment : public elementBoolean<tag<"">> { public:

@@ -69,9 +69,11 @@ private: std::string m_version; std::string m_encoding; };
*/
namespace html {
/*
class doctype : public elementAtomic<tag<"doctype">> { public:

@@ -79,6 +81,7 @@ public: void render(std::ostream& out) const override { out << "<!DOCTYPE html>"; } };
*/
using a = elementBoolean<tag<"a">>; using abbr = elementBoolean<tag<"abbr">>;

@@ -201,6 +204,7 @@ namespace rss { std::string format_time(std::int64_t sec); std::string format_time_now();
/*
class rss : public elementBoolean<tag<"rss">> { public:

@@ -222,6 +226,7 @@ public: { } };
*/
using author = elementBoolean<tag<"author">>; using category = elementBoolean<tag<"category">>;

@@ -262,6 +267,7 @@ namespace atom { std::string format_time(std::int64_t sec); std::string format_time_now();
/*
class feed : public elementBoolean<tag<"feed">> { public:

@@ -270,6 +276,7 @@ public: { } };
*/
using author = elementBoolean<tag<"author">>; using category = elementBoolean<tag<"category">>;

@@ -302,6 +309,7 @@ using usagePoint = elementBoolean<tag<"usagePoint">>; namespace sitemap {
/*
class urlset : public elementBoolean<tag<"urlset">> { public:

@@ -311,6 +319,7 @@ public: { } };
*/
using changefreq = elementBoolean<tag<"changefreq">>; using lastmod = elementBoolean<tag<"lastmod">>;

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

@@ -1,102 +1,125 @@ #pragma once #include <memory>
#include <span>
#include <string> #include <vector> #include "hemplate/attribute.hpp" #include "hemplate/hemplate_export.hpp"
#include "hemplate/streamable.hpp"
namespace hemplate {
class element;
class HEMPLATE_EXPORT elementList : public streamable
class HEMPLATE_EXPORT element
{
public:
elementList() = default;
~elementList() override = default;
elementList(elementList&&) = default;
elementList& operator=(elementList&&) = default;
template<class... Ts>
explicit elementList(Ts&&... args)
enum class Type : uint8_t
{
std::initializer_list<element*> list = {&std::forward(args)...};
for (const auto& elem : list) add(*elem);
}
// explicitly clone all the elements
elementList(const elementList& rhs);
elementList& operator=(const elementList& rhs);
elementList& add(const element& elem);
elementList& add(std::unique_ptr<element> elem);
Atomic,
Boolean,
Comment,
};
bool empty() const { return m_elems.empty(); }
Type m_type;
void render(std::ostream& out) const override;
std::string m_name;
std::string m_data;
private:
std::vector<std::unique_ptr<element>> m_elems;
};
std::vector<element> m_children;
attributeList m_attributes;
class HEMPLATE_EXPORT element : public streamable
{
public:
enum class Type : uint8_t
explicit element(Type type,
std::string_view name,
const std::derived_from<element> auto&... children)
: m_type(type)
, m_name(name)
, m_children(std::initializer_list<element> {children...})
{
Atomic,
Boolean,
};
}
explicit element(attributeList attributes,
elementList embedded,
std::string data,
Type type)
: m_attributes(std::move(attributes))
, m_embeded(std::move(embedded))
, m_data(std::move(data))
, m_type(type)
explicit element(Type type, std::string_view name, std::string_view data)
: m_type(type)
, m_name(name)
, m_data(data)
{ }
element(const element&) = default;
element(element&&) noexcept = default;
element& operator=(const element&) = default;
element& operator=(element&&) noexcept = default;
~element() override = default;
explicit element(Type type,
std::string_view name,
std::span<const element> children)
: m_type(type)
, m_name(name)
, m_children(children.begin(), children.end())
{
}
Type get_type() const { return m_type; }
std::string get_data() const { return m_data; }
const elementList& get_embeded() const { return m_embeded; }
const attributeList& get_attributes() const { return m_attributes; }
template<typename T>
friend class elementAtomic;
virtual bool get_state() const { return false; }
virtual const char* get_name() const = 0;
template<typename T>
friend class elementBoolean;
void set_data(const std::string& data) { m_data = data; }
void set_embedded(const elementList& embed) { m_embeded = embed; }
void set_attributes(const attributeList& attrs) { m_attributes = attrs; }
void render(std::ostream& out, std::size_t indent_value) const;
virtual void tgl_state() const {}
public:
friend std::ostream& operator<<(std::ostream& out, const element& element)
{
element.render(out, 0);
return out;
}
element& add(const element& elem); element& add(std::unique_ptr<element> elem); element& set(const std::string& name); element& set(const std::string& name, const std::string& value);
};
virtual std::unique_ptr<element> clone() const = 0;
template<typename Tag>
class HEMPLATE_EXPORT elementAtomic : public element
{
public:
explicit elementAtomic(std::string_view data)
: element(element::Type::Atomic, Tag::get_name(), data)
{
}
void render(std::ostream& out) const override;
explicit elementAtomic(const std::derived_from<element> auto&... children)
: element(element::Type::Atomic, Tag::get_name(), children...)
{
}
private:
attributeList m_attributes;
elementList m_embeded;
std::string m_data;
Type m_type;
explicit elementAtomic(std::span<const element> children)
: element(element::Type::Atomic, Tag::get_name(), children)
{
}
};
template<typename Tag>
class HEMPLATE_EXPORT elementBoolean : public element
{
static bool m_state; // NOLINT
public:
explicit elementBoolean(std::string_view data)
: element(element::Type::Boolean, Tag::get_name(), data)
{
}
explicit elementBoolean(const std::derived_from<element> auto&... children)
: element(element::Type::Boolean, Tag::get_name(), children...)
{
}
explicit elementBoolean(std::span<const element> children)
: element(element::Type::Boolean, Tag::get_name(), children)
{
}
bool get_state() const { return m_state; }
void tgl_state() const { m_state = !m_state; }
};
template<typename Tag>
bool elementBoolean<Tag>::m_state = false; // NOLINT
} // namespace hemplate

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

@@ -1,38 +0,0 @@
#pragma once
#include "hemplate/element.hpp"
#include "hemplate/hemplate_export.hpp"
namespace hemplate {
template<typename Tag>
class HEMPLATE_EXPORT elementAtomic : public element
{
public:
elementAtomic(const elementAtomic&) = default;
elementAtomic(elementAtomic&&) noexcept = default;
elementAtomic& operator=(const elementAtomic&) = default;
elementAtomic& operator=(elementAtomic&&) noexcept = default;
~elementAtomic() override = default;
elementAtomic()
: element({}, {}, "", Type::Atomic)
{
}
elementAtomic(attributeList attributes) // NOLINT
: element(std::move(attributes), {}, "", Type::Atomic)
{
}
const char* get_name() const override { return Tag::get_name(); }
std::unique_ptr<element> clone() const override
{
return std::make_unique<elementAtomic<Tag>>(*this);
}
private:
};
} // namespace hemplate

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

@@ -1,65 +0,0 @@
#pragma once
#include "hemplate/element.hpp"
#include "hemplate/hemplate_export.hpp"
namespace hemplate {
template<typename Tag>
class HEMPLATE_EXPORT elementBoolean : public element
{
public:
elementBoolean(const elementBoolean&) = default;
elementBoolean(elementBoolean&&) noexcept = default;
elementBoolean& operator=(const elementBoolean&) = default;
elementBoolean& operator=(elementBoolean&&) noexcept = default;
~elementBoolean() override = default;
elementBoolean()
: element({}, {}, "", Type::Boolean)
{
}
elementBoolean(std::string text) // NOLINT
: element({}, {}, std::move(text), Type::Boolean)
{
}
elementBoolean(attributeList attributes) // NOLINT
: element(std::move(attributes), {}, "", Type::Boolean)
{
}
elementBoolean(elementList embedded) // NOLINT
: element({}, std::move(embedded), "", Type::Boolean)
{
}
elementBoolean(std::string text, attributeList attributes)
: element(std::move(attributes), {}, std::move(text), Type::Boolean)
{
}
elementBoolean(attributeList attributes, elementList embedded)
: element(std::move(attributes), std::move(embedded), "", Type::Boolean)
{
}
const char* get_name() const override { return Tag::get_name(); }
bool get_state() const override { return m_state; }
void tgl_state() const override { m_state = !m_state; }
std::unique_ptr<element> clone() const override
{
return std::make_unique<elementBoolean<Tag>>(*this);
}
private:
static bool m_state; // NOLINT
};
template<typename Tag>
bool elementBoolean<Tag>::m_state = false; // NOLINT
} // namespace hemplate

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

@@ -1,28 +1,42 @@ #pragma once
#include <format>
#include <ostream> #include "hemplate/hemplate_export.hpp" namespace hemplate {
template<typename D>
class HEMPLATE_EXPORT streamable {
public:
streamable() = default;
streamable(const streamable&) = default;
streamable(streamable&&) noexcept = default;
streamable& operator=(const streamable&) = default;
streamable& operator=(streamable&&) noexcept = default;
virtual ~streamable() = default;
friend D;
streamable() = default;
virtual void render(std::ostream& out) const = 0;
public:
bool operator==(const streamable& rhs) const = default;
friend std::ostream& operator<<(std::ostream& out, const streamable& obj) {
obj.render(out);
return out;
return out << static_cast<std::string>(static_cast<D&>(obj));
} };
// NOLINTNEXTLINE cppcoreguidelines-macro-usage
#define CUSTOM_FORMAT(Type) \
template<> \
struct std::formatter<Type> \
{ \
constexpr auto parse(std::format_parse_context& ctx) \
{ \
return ctx.begin(); \
} \
\
auto format(const Type& type, std::format_context& ctx) const \
{ \
return std::format_to(ctx.out(), "{}", static_cast<std::string>(type)); \
} \
};
} // namespace hemplate

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

@@ -1,6 +1,3 @@
#include <initializer_list>
#include <ostream>
#include "hemplate/attribute.hpp" namespace hemplate {

@@ -15,27 +12,12 @@ attributeList::attributeList(attribute attr) // NOLINT set(attr.get_name(), attr.get_value()); }
bool attribute::operator!=(const attribute& rhs) const
{
return !(*this == rhs);
}
bool attribute::operator==(const attribute& rhs) const
{
return m_name == rhs.m_name && m_value == rhs.m_value;
}
bool attributeList::empty() const { return m_attributes.empty() && m_class.get_value().empty() && m_style.get_value().empty(); }
void attribute::render(std::ostream& out) const
{
out << get_name() << "=\"" << get_value() << "\"";
}
attributeList& attributeList::set(const std::string& name) { if (name != "class" && name != "style") m_attributes.emplace_back(name);

@@ -59,12 +41,4 @@ attributeList& attributeList::set(const std::string& name, return *this; }
void attributeList::render(std::ostream& out) const
{
if (!m_class.get_value().empty()) out << m_class << ' ';
if (!m_style.get_value().empty()) out << m_style << ' ';
for (const auto& attr : m_attributes) out << attr << ' ';
}
} // namespace hemplate

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

@@ -1,7 +1,6 @@
#include <memory>
#include <format>
#include <ostream> #include <string>
#include <utility>
#include "hemplate/element.hpp"

@@ -9,13 +8,7 @@ namespace hemplate { element& element::add(const element& elem) {
m_embeded.add(elem);
return *this;
}
element& element::add(std::unique_ptr<element> elem)
{
m_embeded.add(std::move(elem));
m_children.emplace_back(elem);
return *this; }

@@ -31,81 +24,58 @@ element& element::set(const std::string& name, const std::string& value) return *this; }
void element::render(std::ostream& out) const
void element::render(std::ostream& out, std::size_t indent_value) const
{
if (*get_name() == '\0')
const std::string indent(indent_value, ' ');
if (m_name.empty())
{
out << m_data;
out << indent << m_data << '\n';
return; }
const auto open_tag = [this, &out](bool atomic)
{
out << '<' << get_name();
if (!m_attributes.empty()) out << ' ', m_attributes.render(out);
out << (atomic ? " />" : ">");
};
const auto close_tag = [this, &out]() { out << "</" << get_name() << '>'; };
if (m_type == Type::Atomic) {
open_tag(true);
out << indent << std::format("<{} {}/>\n", m_name, m_attributes);
return; } if (!m_data.empty()) {
open_tag(false);
if (!m_embeded.empty()) m_embeded.render(out);
else out << m_data;
close_tag();
out << indent << std::format("<{} {}>\n", m_name, m_attributes);
if (!m_children.empty())
{
for (const auto& child : m_children)
{
child.render(out, indent_value + 2);
}
}
else
{
out << indent << " " << m_data << '\n';
}
out << indent << std::format("</{}>\n", m_name);
return; }
if (m_embeded.empty())
if (m_children.empty())
{
tgl_state();
get_state() ? open_tag(false) : close_tag();
/*
tgl_state();
get_state() ? open_tag(false) : close_tag();
*/
} else {
open_tag(false);
m_embeded.render(out);
close_tag();
out << indent << std::format("<{} {}>\n", m_name, m_attributes);
for (const auto& child : m_children)
{
child.render(out, indent_value + 2);
}
out << indent << std::format("</{}>\n", m_name);
} }
elementList::elementList(const elementList& rhs)
{
this->operator=(rhs);
}
elementList& elementList::operator=(const elementList& rhs)
{
if (this == &rhs) return *this;
m_elems.clear();
for (const auto& elem : rhs.m_elems) add(*elem);
return *this;
}
elementList& elementList::add(const element& elem)
{
m_elems.push_back(elem.clone());
return *this;
}
elementList& elementList::add(std::unique_ptr<element> elem)
{
m_elems.push_back(std::move(elem));
return *this;
}
void elementList::render(std::ostream& out) const
{
for (const auto& elem : m_elems) elem->render(out);
}
} // namespace hemplate

diff --git a/ test/source/hemplate_test.cpp b/ test/source/hemplate_test.cpp

@@ -1,7 +1,7 @@ #include <iostream>
#include "hemplate/classes.hpp"
#include "hemplate/attribute.hpp"
#include "hemplate/classes.hpp"
int main() {

@@ -12,21 +12,23 @@ int main() {"class", "home_ul"}, {"style", "margin-bottom: 1em"}});
std::cout << comment("Hello this is a commen");
// std::cout << comment("Hello this is a commen");
std::cout << html::html() << std::endl;
std::cout << html::ul("Won't see", ul_attrs)
std::cout << html::ul("Won't see")
.set("style", "margin-top: 1em") .set("class", "center")
.add(html::li("Item 1", li_attrs).set("class", "item1"))
.add(html::li("Item 2", li_attrs).set("class", "item2"))
.add(html::li("Item 1").set("class", "item1"))
.add(html::li("Item 2").set("class", "item2"))
<< std::endl; std::cout << html::meta() << std::endl; std::cout << html::html() << std::endl;
std::cout << comment();
std::cout << "split ";
std::cout << "comment ";
std::cout << comment() << std::endl;
/*
std::cout << comment();
std::cout << "split ";
std::cout << "comment ";
std::cout << comment() << std::endl;
*/
return 0; }