hemplateSimple 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
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;
}