#include "Templates.hpp" #include #include "Caps.hpp" namespace molo::example::fw { std::string html_escape(std::string_view in) { std::string out; out.reserve(in.size()); for (char c : in) { switch (c) { case '&': out += "&"; break; case '<': out += "<"; break; case '>': out += ">"; break; case '"': out += """; break; default: out.push_back(c); break; } } return out; } namespace { std::string trim(std::string s) { while (!s.empty() && (s.front() == ' ' || s.front() == '\t' || s.front() == '\n' || s.front() == '\r')) s.erase(s.begin()); while (!s.empty() && (s.back() == ' ' || s.back() == '\t' || s.back() == '\n' || s.back() == '\r')) s.pop_back(); return s; } std::string apply_filter(std::string value, const std::string& filter) { if (filter == "upper") { for (char& c : value) c = (char)std::toupper((unsigned char)c); } else if (filter == "lower") { for (char& c : value) c = (char)std::tolower((unsigned char)c); } else if (filter == "escape") { value = html_escape(value); } return value; } std::string ctx_string(const TemplateCtx& ctx, const std::string& key) { auto it = ctx.find(key); if (it == ctx.end()) return ""; if (auto* s = std::get_if(&it->second)) return *s; return ""; } bool ctx_truthy(const TemplateCtx& ctx, const std::string& key) { auto it = ctx.find(key); if (it == ctx.end()) return false; if (auto* s = std::get_if(&it->second)) return !s->empty() && *s != "false" && *s != "0"; if (auto* l = std::get_if(&it->second)) return !l->empty(); return false; } } // namespace void Environment::add_template(std::string name, std::string source) { templates_[std::move(name)] = std::move(source); } void Environment::add_global(std::string key, std::string value) { globals_[std::move(key)] = std::move(value); } bool Environment::has_template(const std::string& name) const { return templates_.count(name) > 0; } std::string Environment::render(const std::string& name, TemplateCtx ctx) const { auto it = templates_.find(name); if (it == templates_.end()) throw std::runtime_error("template not found"); for (const auto& [k, v] : globals_) { if (!ctx.count(k)) ctx[k] = v; } return render_source(name, it->second, ctx, 0); } std::string Environment::render_source(const std::string& name, const std::string& src, TemplateCtx& ctx, int depth) const { (void)name; if (depth > caps::kMaxTemplateDepth) throw std::runtime_error("template depth exceeded"); std::string extends; std::size_t pos = src.find("{% extends"); if (pos != std::string::npos) { std::size_t q1 = src.find('"', pos); std::size_t q2 = q1 == std::string::npos ? std::string::npos : src.find('"', q1 + 1); if (q1 != std::string::npos && q2 != std::string::npos) extends = src.substr(q1 + 1, q2 - q1 - 1); } if (!extends.empty()) { auto it = templates_.find(extends); if (it == templates_.end()) throw std::runtime_error("base not found"); std::map blocks; std::size_t b = 0; while (true) { std::size_t bs = src.find("{% block", b); if (bs == std::string::npos) break; std::size_t be = src.find("%}", bs); std::size_t cs = src.find("{% endblock %}", be); if (be == std::string::npos || cs == std::string::npos) break; std::string head = trim(src.substr(bs + 9, be - bs - 9)); blocks[head] = src.substr(be + 2, cs - be - 2); b = cs + 14; } std::string base = it->second; for (const auto& [k, v] : blocks) { std::string open = "{% block " + k + " %}"; std::size_t s = base.find(open); std::size_t e = s == std::string::npos ? std::string::npos : base.find("{% endblock %}", s); if (s != std::string::npos && e != std::string::npos) base = base.substr(0, s) + v + base.substr(e + 14); } return render_blocks(base, ctx, depth); } return render_blocks(src, ctx, depth); } std::string Environment::render_blocks(const std::string& src, TemplateCtx& ctx, int depth) const { std::string out; std::size_t i = 0; while (i < src.size()) { std::size_t v = src.find("{{", i); std::size_t t = src.find("{%", i); if (v == std::string::npos && t == std::string::npos) { out += src.substr(i); break; } if (v != std::string::npos && (t == std::string::npos || v < t)) { out += src.substr(i, v - i); std::size_t e = src.find("}}", v); if (e == std::string::npos) break; std::string expr = trim(src.substr(v + 2, e - v - 2)); bool safe = false; std::string var = expr; std::vector filters; std::size_t p = expr.find('|'); if (p != std::string::npos) { var = trim(expr.substr(0, p)); std::string rest = expr.substr(p + 1); std::size_t s = 0; while (s < rest.size()) { std::size_t q = rest.find('|', s); std::string f = trim(rest.substr(s, q == std::string::npos ? q : q - s)); if (f == "safe") { safe = true; } else if (!f.empty()) { filters.push_back(f); } if (q == std::string::npos) break; s = q + 1; } } std::string value; std::size_t dot = var.find('.'); if (dot != std::string::npos) { value = ctx_string(ctx, var); } else { value = ctx_string(ctx, var); } for (const auto& f : filters) value = apply_filter(value, f); if (!safe) value = html_escape(value); out += value; i = e + 2; } else { out += src.substr(i, t - i); std::size_t e = src.find("%}", t); if (e == std::string::npos) break; std::string tag = trim(src.substr(t + 2, e - t - 2)); if (tag.rfind("if ", 0) == 0) { std::string cond = trim(tag.substr(3)); std::size_t end = src.find("{% endif %}", e); if (end == std::string::npos) break; std::string inner = src.substr(e + 2, end - e - 2); std::size_t els = inner.find("{% else %}"); std::string yes = els == std::string::npos ? inner : inner.substr(0, els); std::string no = els == std::string::npos ? "" : inner.substr(els + 10); out += render_blocks(ctx_truthy(ctx, cond) ? yes : no, ctx, depth + 1); i = end + 11; } else if (tag.rfind("for ", 0) == 0) { std::string rest = trim(tag.substr(4)); std::size_t in = rest.find(" in "); std::string item = in == std::string::npos ? "" : trim(rest.substr(0, in)); std::string list = in == std::string::npos ? "" : trim(rest.substr(in + 4)); std::size_t end = src.find("{% endfor %}", e); if (end == std::string::npos) break; std::string inner = src.substr(e + 2, end - e - 2); auto it = ctx.find(list); if (it != ctx.end()) { if (auto* l = std::get_if(&it->second)) { for (const auto& row : *l) { TemplateCtx sub = ctx; for (const auto& [k, val] : row) { sub[item + "." + k] = val; sub[k] = val; } out += render_blocks(inner, sub, depth + 1); } } } i = end + 12; } else if (tag.rfind("include ", 0) == 0) { std::string rest = trim(tag.substr(8)); if (rest.size() >= 2 && rest.front() == '"') { std::string inc = rest.substr(1, rest.size() - 2); auto it = templates_.find(inc); if (it != templates_.end()) out += render_blocks(it->second, ctx, depth + 1); } i = e + 2; } else if (tag.rfind("block ", 0) == 0) { std::size_t end = src.find("{% endblock %}", e); if (end == std::string::npos) break; out += render_blocks(src.substr(e + 2, end - e - 2), ctx, depth + 1); i = end + 14; } else { i = e + 2; } } } return out; } } // namespace molo::example::fw