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 cb6446371e20af7b6f6b6637da005fa73ebbe589
parent d6ac3a263b2f1141f8c28b6c29ae246ba8b58ad4
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Fri, 2 May 2025 20:53:15 +0200

Inline element for cleaner output

* Elements with only one string will be printed in a line

Diffstat:
M include/hemplate/element.hpp | ++++++++++++++++++++++++++++++++++++++ ------------
M test/source/classes_test.cpp | ++++++++++++++++++++++++++++++ -------------
M test/source/element_test.cpp | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ----------

3 files changed, 210 insertions(+), 44 deletions(-)


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

@@ -35,20 +35,40 @@ class HEMPLATE_EXPORT element_base

using child_t = std::variant<element_base, std::string>;
std::vector<child_t> m_cdn;

void add(std::ranges::forward_range auto range)
void add(std::string_view data) { m_cdn.emplace_back(std::string(data)); }

void add(element_base base_elem)
{
if (base_elem.m_otag.empty()) {
add(std::move(base_elem.m_cdn));
return;
}

m_cdn.emplace_back(std::move(base_elem));
}

void add(std::vector<child_t> children)
{
for (auto& range_elem : children) {
std::visit(
[this](auto&& elem)
{
add(elem);
},
std::move(range_elem)
);
}
}

void add(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::make_move_iterator(std::begin(range)),
std::make_move_iterator(std::end(range))
);
for (auto& elem : range) {
add(std::move(elem));
}
}

void add(std::string_view data) { m_cdn.emplace_back(std::string(data)); }
void add(element_base elem) { m_cdn.emplace_back(std::move(elem)); }

template<typename... Args>
explicit element_base(
std::string_view open_tag, std::string_view close_tag, Args&&... args

@@ -89,9 +109,15 @@ class HEMPLATE_EXPORT element_base

return;
}

out << indent << m_otag << '\n';
render_children(out, indent_value + 2);
out << indent << m_ctag << '\n';
if (m_cdn.size() == 1 && std::holds_alternative<std::string>(m_cdn.front()))
{
out << indent << m_otag << std::get<std::string>(m_cdn.front()) << m_ctag
<< '\n';
} else {
out << indent << m_otag << '\n';
render_children(out, indent_value + 2);
out << indent << m_ctag << '\n';
}
}

public:

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

@@ -42,17 +42,34 @@ TEST_CASE("transform", "[classes/transform]")

using tag = hemplate::element_boolean<"t">;
using child = hemplate::element_boolean<"c">;

const std::vector<std::string> vec = {"1", "2"};
const auto t = tag {hemplate::transform(
vec,
[](const auto& e)
{
return child {e};
}
)};

REQUIRE(
std::string(t)
== "<t>\n <c>\n 1\n </c>\n <c>\n 2\n </c>\n</t>\n"
);
SECTION("direct")
{
const std::vector<std::string> vec = {"1", "2"};
const auto t = tag {hemplate::transform(
vec,
[](const auto& e)
{
return child {e};
}
)};

REQUIRE(
std::string(t)
== "<t>\n <c>1</c>\n <c>2</c>\n</t>\n"
);
}

SECTION("indirect")
{
const std::vector<std::string> vec = {"1", "2"};
const auto t = tag {hemplate::transform(
vec,
[](const auto& e)
{
return hemplate::element {e};
}
)};

REQUIRE(std::string(t) == "<t>\n 1\n 2\n</t>\n");
}
}

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

@@ -27,14 +27,14 @@ TEST_CASE("boolean", "[element]")

{
const auto t = tag {"text"};

REQUIRE(std::string(t) == "<tag>\n text\n</tag>\n");
REQUIRE(std::string(t) == "<tag>text</tag>\n");
}

SECTION("attribute data")
{
const auto t = tag {{{"attr", "val"}}, "text"};

REQUIRE(std::string(t) == "<tag attr=\"val\">\n text\n</tag>\n");
REQUIRE(std::string(t) == "<tag attr=\"val\">text</tag>\n");
}

SECTION("child")

@@ -64,9 +64,7 @@ TEST_CASE("boolean", "[element]")

child {"text"},
};

REQUIRE(
std::string(t) == "<tag>\n <child>\n text\n </child>\n</tag>\n"
);
REQUIRE(std::string(t) == "<tag>\n <child>text</child>\n</tag>\n");
}

SECTION("attribute child data")

@@ -77,8 +75,7 @@ TEST_CASE("boolean", "[element]")

};

REQUIRE(
std::string(t)
== "<tag attr=\"val\">\n <child>\n text\n </child>\n</tag>\n"
std::string(t) == "<tag attr=\"val\">\n <child>text</child>\n</tag>\n"
);
}

@@ -89,6 +86,12 @@ TEST_CASE("boolean", "[element]")


REQUIRE(std::string(t) == "<tag>\n hello\n world\n</tag>\n");
}

SECTION("tag element elemetn tag")
{
const auto t = tag {element {element {tag {}}}};
REQUIRE(std::string(t) == "<tag>\n <tag>\n </tag>\n</tag>\n");
}
}

TEST_CASE("atomic", "[element]")

@@ -112,6 +115,7 @@ TEST_CASE("atomic", "[element]")


TEST_CASE("element", "[element]")
{
using tag = element_boolean<"tag">;
using child = element_boolean<"child">;

SECTION("empty")

@@ -128,39 +132,158 @@ TEST_CASE("element", "[element]")

REQUIRE(std::string(t) == "text\n");
}

SECTION("child")
SECTION("tag")
{
const auto t = element {
child {},
tag {},
};

REQUIRE(std::string(t) == "<child>\n</child>\n");
REQUIRE(std::string(t) == "<tag>\n</tag>\n");
}

SECTION("child element")
SECTION("tag element")
{
const auto t = child {
const auto t = tag {
element {},
};

REQUIRE(std::string(t) == "<child>\n</child>\n");
REQUIRE(std::string(t) == "<tag>\n</tag>\n");
}

SECTION("element child data")
SECTION("element tag")
{
const auto t = element {
child {"text"},
tag {},
};

REQUIRE(std::string(t) == "<child>\n text\n</child>\n");
REQUIRE(std::string(t) == "<tag>\n</tag>\n");
}

SECTION("child element data")
SECTION("element tag data")
{
const auto t = child {
const auto t = element {
tag {"text"},
};

REQUIRE(std::string(t) == "<tag>text</tag>\n");
}

SECTION("tag element data")
{
const auto t = tag {
element {"text"},
};

REQUIRE(std::string(t) == "<tag>text</tag>\n");
}

SECTION("tag element data")
{
const auto t = tag {
element {"text"},
};

REQUIRE(std::string(t) == "<child>\n text\n</child>\n");
REQUIRE(std::string(t) == "<tag>text</tag>\n");
}

SECTION("element tag child data")
{
const auto t = element {
tag {
child {
"text",
},
},
};

REQUIRE(std::string(t) == "<tag>\n <child>text</child>\n</tag>\n");
}

SECTION("element tag element child data")
{
const auto t = element {
tag {
element {
child {
"text",
},
},
},
};

REQUIRE(std::string(t) == "<tag>\n <child>text</child>\n</tag>\n");
}

SECTION("element tag child element data")
{
const auto t = element {
tag {
child {
element {
"text",
},
},
},
};

REQUIRE(std::string(t) == "<tag>\n <child>text</child>\n</tag>\n");
}

SECTION("element tag element child element data")
{
const auto t = element {
tag {
element {
child {
element {
"text",
},
},
},
},
};

REQUIRE(std::string(t) == "<tag>\n <child>text</child>\n</tag>\n");
}

SECTION("tag element child data")
{
const auto t = tag {
element {
child {
"text",
},
},
};

REQUIRE(std::string(t) == "<tag>\n <child>text</child>\n</tag>\n");
}

SECTION("tag child element data")
{
const auto t = tag {
child {
element {
"text",
},
},
};

REQUIRE(std::string(t) == "<tag>\n <child>text</child>\n</tag>\n");
}

SECTION("tag element child element data")
{
const auto t = tag {
element {
child {
element {
"text",
},
},
},
};

REQUIRE(std::string(t) == "<tag>\n <child>text</child>\n</tag>\n");
}
}