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