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