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");
} }