2014-07-30 15:50:38 +00:00
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
2014-08-02 01:46:00 +00:00
|
|
|
#include <fstream>
|
|
|
|
#include <iterator>
|
|
|
|
#include <functional>
|
2016-09-21 14:11:06 +00:00
|
|
|
#include "crow/json.h"
|
2021-02-09 03:52:05 +00:00
|
|
|
#include "crow/logging.h"
|
2022-02-18 01:25:02 +00:00
|
|
|
#include "crow/returnable.h"
|
2022-01-11 18:48:51 +00:00
|
|
|
#include "crow/utility.h"
|
|
|
|
|
2014-07-30 15:50:38 +00:00
|
|
|
namespace crow
|
|
|
|
{
|
|
|
|
namespace mustache
|
|
|
|
{
|
|
|
|
using context = json::wvalue;
|
|
|
|
|
2014-08-02 01:46:00 +00:00
|
|
|
template_t load(const std::string& filename);
|
|
|
|
|
2014-07-30 15:50:38 +00:00
|
|
|
class invalid_template_exception : public std::exception
|
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
public:
|
|
|
|
invalid_template_exception(const std::string& msg):
|
|
|
|
msg("crow::mustache error: " + msg)
|
2021-11-27 12:28:50 +00:00
|
|
|
{}
|
2014-07-30 15:50:38 +00:00
|
|
|
virtual const char* what() const throw()
|
|
|
|
{
|
|
|
|
return msg.c_str();
|
|
|
|
}
|
|
|
|
std::string msg;
|
|
|
|
};
|
|
|
|
|
2022-02-18 01:25:02 +00:00
|
|
|
struct rendered_template : returnable
|
|
|
|
{
|
|
|
|
rendered_template():
|
|
|
|
returnable("text/html") {}
|
|
|
|
|
|
|
|
rendered_template(std::string& body):
|
|
|
|
returnable("text/html"), body_(std::move(body)) {}
|
|
|
|
|
|
|
|
std::string body_;
|
|
|
|
|
|
|
|
std::string dump() const override
|
|
|
|
{
|
|
|
|
return body_;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-07-30 15:50:38 +00:00
|
|
|
enum class ActionType
|
|
|
|
{
|
|
|
|
Ignore,
|
|
|
|
Tag,
|
|
|
|
UnescapeTag,
|
|
|
|
OpenBlock,
|
|
|
|
CloseBlock,
|
|
|
|
ElseBlock,
|
|
|
|
Partial,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Action
|
|
|
|
{
|
|
|
|
int start;
|
|
|
|
int end;
|
|
|
|
int pos;
|
|
|
|
ActionType t;
|
2021-11-25 11:45:38 +00:00
|
|
|
Action(ActionType t, size_t start, size_t end, size_t pos = 0):
|
|
|
|
start(static_cast<int>(start)), end(static_cast<int>(end)), pos(static_cast<int>(pos)), t(t)
|
|
|
|
{
|
|
|
|
}
|
2014-07-30 15:50:38 +00:00
|
|
|
};
|
|
|
|
|
2020-11-18 22:13:57 +00:00
|
|
|
/// A mustache template object.
|
2021-07-03 20:28:52 +00:00
|
|
|
class template_t
|
2014-07-30 15:50:38 +00:00
|
|
|
{
|
|
|
|
public:
|
2021-11-25 11:45:38 +00:00
|
|
|
template_t(std::string body):
|
|
|
|
body_(std::move(body))
|
2014-07-30 15:50:38 +00:00
|
|
|
{
|
|
|
|
// {{ {{# {{/ {{^ {{! {{> {{=
|
|
|
|
parse();
|
|
|
|
}
|
|
|
|
|
2014-08-01 21:30:36 +00:00
|
|
|
private:
|
2022-01-24 19:41:29 +00:00
|
|
|
std::string tag_name(const Action& action) const
|
2014-07-30 15:50:38 +00:00
|
|
|
{
|
2014-08-01 21:30:36 +00:00
|
|
|
return body_.substr(action.start, action.end - action.start);
|
|
|
|
}
|
2022-01-24 19:41:29 +00:00
|
|
|
auto find_context(const std::string& name, const std::vector<context*>& stack, bool shouldUseOnlyFirstStackValue = false) const -> std::pair<bool, context&>
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
|
|
|
if (name == ".")
|
|
|
|
{
|
|
|
|
return {true, *stack.back()};
|
|
|
|
}
|
2020-10-30 19:27:26 +00:00
|
|
|
static json::wvalue empty_str;
|
|
|
|
empty_str = "";
|
|
|
|
|
2014-08-01 21:30:36 +00:00
|
|
|
int dotPosition = name.find(".");
|
2021-02-21 00:14:30 +00:00
|
|
|
if (dotPosition == static_cast<int>(name.npos))
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
for (auto it = stack.rbegin(); it != stack.rend(); ++it)
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
|
|
|
if ((*it)->t() == json::type::Object)
|
|
|
|
{
|
|
|
|
if ((*it)->count(name))
|
|
|
|
return {true, (**it)[name]};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::vector<int> dotPositions;
|
|
|
|
dotPositions.push_back(-1);
|
2021-11-25 11:45:38 +00:00
|
|
|
while (dotPosition != static_cast<int>(name.npos))
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
|
|
|
dotPositions.push_back(dotPosition);
|
2021-11-25 11:45:38 +00:00
|
|
|
dotPosition = name.find(".", dotPosition + 1);
|
2014-08-01 21:30:36 +00:00
|
|
|
}
|
|
|
|
dotPositions.push_back(name.size());
|
|
|
|
std::vector<std::string> names;
|
2021-11-25 11:45:38 +00:00
|
|
|
names.reserve(dotPositions.size() - 1);
|
|
|
|
for (int i = 1; i < static_cast<int>(dotPositions.size()); i++)
|
|
|
|
names.emplace_back(name.substr(dotPositions[i - 1] + 1, dotPositions[i] - dotPositions[i - 1] - 1));
|
2014-08-01 21:30:36 +00:00
|
|
|
|
2021-11-25 11:45:38 +00:00
|
|
|
for (auto it = stack.rbegin(); it != stack.rend(); ++it)
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
|
|
|
context* view = *it;
|
|
|
|
bool found = true;
|
2021-11-25 11:45:38 +00:00
|
|
|
for (auto jt = names.begin(); jt != names.end(); ++jt)
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
|
|
|
if (view->t() == json::type::Object &&
|
|
|
|
view->count(*jt))
|
|
|
|
{
|
|
|
|
view = &(*view)[*jt];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
if (shouldUseOnlyFirstStackValue)
|
|
|
|
{
|
2020-10-30 19:27:26 +00:00
|
|
|
return {false, empty_str};
|
|
|
|
}
|
2014-08-01 21:30:36 +00:00
|
|
|
found = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (found)
|
|
|
|
return {true, *view};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {false, empty_str};
|
|
|
|
}
|
|
|
|
|
2022-01-24 19:41:29 +00:00
|
|
|
void escape(const std::string& in, std::string& out) const
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
|
|
|
out.reserve(out.size() + in.size());
|
2021-11-25 11:45:38 +00:00
|
|
|
for (auto it = in.begin(); it != in.end(); ++it)
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
switch (*it)
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
|
|
|
case '&': out += "&"; break;
|
|
|
|
case '<': out += "<"; break;
|
|
|
|
case '>': out += ">"; break;
|
|
|
|
case '"': out += """; break;
|
|
|
|
case '\'': out += "'"; break;
|
|
|
|
case '/': out += "/"; break;
|
2022-02-11 15:26:26 +00:00
|
|
|
case '`': out += "`"; break;
|
|
|
|
case '=': out += "="; break;
|
2014-08-01 21:30:36 +00:00
|
|
|
default: out += *it; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-24 19:41:29 +00:00
|
|
|
bool isTagInsideObjectBlock(const int& current, const std::vector<context*>& stack) const
|
2020-10-30 19:27:26 +00:00
|
|
|
{
|
|
|
|
int openedBlock = 0;
|
|
|
|
int totalBlocksBefore = 0;
|
2021-11-25 11:45:38 +00:00
|
|
|
for (int i = current; i > 0; --i)
|
|
|
|
{
|
2020-10-30 19:27:26 +00:00
|
|
|
++totalBlocksBefore;
|
|
|
|
auto& action = actions_[i - 1];
|
|
|
|
|
2021-11-25 11:45:38 +00:00
|
|
|
if (action.t == ActionType::OpenBlock)
|
|
|
|
{
|
|
|
|
if (openedBlock == 0 && (*stack.rbegin())->t() == json::type::Object)
|
|
|
|
{
|
2020-10-30 19:27:26 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
--openedBlock;
|
2021-11-25 11:45:38 +00:00
|
|
|
}
|
|
|
|
else if (action.t == ActionType::CloseBlock)
|
|
|
|
{
|
2020-10-30 19:27:26 +00:00
|
|
|
++openedBlock;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-01-24 19:41:29 +00:00
|
|
|
void render_internal(int actionBegin, int actionEnd, std::vector<context*>& stack, std::string& out, int indent) const
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
|
|
|
int current = actionBegin;
|
2014-08-02 01:46:00 +00:00
|
|
|
|
|
|
|
if (indent)
|
|
|
|
out.insert(out.size(), indent, ' ');
|
|
|
|
|
2021-11-25 11:45:38 +00:00
|
|
|
while (current < actionEnd)
|
2014-07-30 15:50:38 +00:00
|
|
|
{
|
2014-08-01 21:30:36 +00:00
|
|
|
auto& fragment = fragments_[current];
|
|
|
|
auto& action = actions_[current];
|
2014-08-02 01:46:00 +00:00
|
|
|
render_fragment(fragment, indent, out);
|
2021-11-25 11:45:38 +00:00
|
|
|
switch (action.t)
|
2014-07-30 15:50:38 +00:00
|
|
|
{
|
2014-08-01 21:30:36 +00:00
|
|
|
case ActionType::Ignore:
|
|
|
|
// do nothing
|
|
|
|
break;
|
2014-08-02 01:46:00 +00:00
|
|
|
case ActionType::Partial:
|
2021-11-25 11:45:38 +00:00
|
|
|
{
|
|
|
|
std::string partial_name = tag_name(action);
|
|
|
|
auto partial_templ = load(partial_name);
|
|
|
|
int partial_indent = action.pos;
|
|
|
|
partial_templ.render_internal(0, partial_templ.fragments_.size() - 1, stack, out, partial_indent ? indent + partial_indent : 0);
|
|
|
|
}
|
|
|
|
break;
|
2014-08-01 21:30:36 +00:00
|
|
|
case ActionType::UnescapeTag:
|
|
|
|
case ActionType::Tag:
|
2021-11-25 11:45:38 +00:00
|
|
|
{
|
|
|
|
bool shouldUseOnlyFirstStackValue = false;
|
|
|
|
if (isTagInsideObjectBlock(current, stack))
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
shouldUseOnlyFirstStackValue = true;
|
2014-08-01 21:30:36 +00:00
|
|
|
}
|
2021-11-25 11:45:38 +00:00
|
|
|
auto optional_ctx = find_context(tag_name(action), stack, shouldUseOnlyFirstStackValue);
|
|
|
|
auto& ctx = optional_ctx.second;
|
|
|
|
switch (ctx.t())
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
case json::type::Number:
|
|
|
|
out += ctx.dump();
|
2014-08-01 21:30:36 +00:00
|
|
|
break;
|
2021-11-25 11:45:38 +00:00
|
|
|
case json::type::String:
|
|
|
|
if (action.t == ActionType::Tag)
|
|
|
|
escape(ctx.s, out);
|
|
|
|
else
|
|
|
|
out += ctx.s;
|
|
|
|
break;
|
2021-12-10 23:38:30 +00:00
|
|
|
case json::type::Function:
|
2021-12-17 06:45:50 +00:00
|
|
|
{
|
|
|
|
std::string execute_result = ctx.execute();
|
|
|
|
while (execute_result.find("{{") != std::string::npos)
|
2021-12-10 23:38:30 +00:00
|
|
|
{
|
2021-12-17 06:45:50 +00:00
|
|
|
template_t result_plug(execute_result);
|
2022-02-18 01:25:02 +00:00
|
|
|
execute_result = result_plug.render_string(*(stack[0]));
|
2021-12-10 23:38:30 +00:00
|
|
|
}
|
2021-12-17 06:45:50 +00:00
|
|
|
|
|
|
|
if (action.t == ActionType::Tag)
|
|
|
|
escape(execute_result, out);
|
|
|
|
else
|
|
|
|
out += execute_result;
|
|
|
|
}
|
|
|
|
break;
|
2021-11-25 11:45:38 +00:00
|
|
|
default:
|
2022-06-06 15:14:37 +00:00
|
|
|
throw std::runtime_error("not implemented tag type" + utility::lexical_cast<std::string>(static_cast<int>(ctx.t())));
|
2021-11-25 11:45:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ActionType::ElseBlock:
|
|
|
|
{
|
|
|
|
static context nullContext;
|
|
|
|
auto optional_ctx = find_context(tag_name(action), stack);
|
|
|
|
if (!optional_ctx.first)
|
|
|
|
{
|
|
|
|
stack.emplace_back(&nullContext);
|
|
|
|
break;
|
|
|
|
}
|
2014-08-01 21:30:36 +00:00
|
|
|
|
2021-11-25 11:45:38 +00:00
|
|
|
auto& ctx = optional_ctx.second;
|
|
|
|
switch (ctx.t())
|
|
|
|
{
|
|
|
|
case json::type::List:
|
|
|
|
if (ctx.l && !ctx.l->empty())
|
2014-08-01 21:30:36 +00:00
|
|
|
current = action.pos;
|
2021-11-25 11:45:38 +00:00
|
|
|
else
|
|
|
|
stack.emplace_back(&nullContext);
|
|
|
|
break;
|
|
|
|
case json::type::False:
|
|
|
|
case json::type::Null:
|
|
|
|
stack.emplace_back(&nullContext);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
current = action.pos;
|
|
|
|
break;
|
2014-08-01 21:30:36 +00:00
|
|
|
}
|
2021-11-25 11:45:38 +00:00
|
|
|
break;
|
|
|
|
}
|
2014-08-01 21:30:36 +00:00
|
|
|
case ActionType::OpenBlock:
|
2021-11-25 11:45:38 +00:00
|
|
|
{
|
|
|
|
auto optional_ctx = find_context(tag_name(action), stack);
|
|
|
|
if (!optional_ctx.first)
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
current = action.pos;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto& ctx = optional_ctx.second;
|
|
|
|
switch (ctx.t())
|
|
|
|
{
|
|
|
|
case json::type::List:
|
|
|
|
if (ctx.l)
|
|
|
|
for (auto it = ctx.l->begin(); it != ctx.l->end(); ++it)
|
|
|
|
{
|
|
|
|
stack.push_back(&*it);
|
|
|
|
render_internal(current + 1, action.pos, stack, out, indent);
|
|
|
|
stack.pop_back();
|
|
|
|
}
|
2014-08-01 21:30:36 +00:00
|
|
|
current = action.pos;
|
|
|
|
break;
|
2021-11-25 11:45:38 +00:00
|
|
|
case json::type::Number:
|
|
|
|
case json::type::String:
|
|
|
|
case json::type::Object:
|
|
|
|
case json::type::True:
|
|
|
|
stack.push_back(&ctx);
|
|
|
|
break;
|
|
|
|
case json::type::False:
|
|
|
|
case json::type::Null:
|
|
|
|
current = action.pos;
|
|
|
|
break;
|
|
|
|
default:
|
2022-06-06 15:14:37 +00:00
|
|
|
throw std::runtime_error("{{#: not implemented context type: " + utility::lexical_cast<std::string>(static_cast<int>(ctx.t())));
|
2021-11-25 11:45:38 +00:00
|
|
|
break;
|
2014-08-01 21:30:36 +00:00
|
|
|
}
|
2021-11-25 11:45:38 +00:00
|
|
|
break;
|
|
|
|
}
|
2014-08-01 21:30:36 +00:00
|
|
|
case ActionType::CloseBlock:
|
|
|
|
stack.pop_back();
|
|
|
|
break;
|
|
|
|
default:
|
2022-06-06 15:14:37 +00:00
|
|
|
throw std::runtime_error("not implemented " + utility::lexical_cast<std::string>(static_cast<int>(action.t)));
|
2014-07-30 15:50:38 +00:00
|
|
|
}
|
2014-08-01 21:30:36 +00:00
|
|
|
current++;
|
2014-07-30 15:50:38 +00:00
|
|
|
}
|
2014-08-01 21:30:36 +00:00
|
|
|
auto& fragment = fragments_[actionEnd];
|
2014-08-02 01:46:00 +00:00
|
|
|
render_fragment(fragment, indent, out);
|
|
|
|
}
|
2022-01-24 19:41:29 +00:00
|
|
|
void render_fragment(const std::pair<int, int> fragment, int indent, std::string& out) const
|
2014-08-02 01:46:00 +00:00
|
|
|
{
|
|
|
|
if (indent)
|
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
for (int i = fragment.first; i < fragment.second; i++)
|
2014-08-02 01:46:00 +00:00
|
|
|
{
|
|
|
|
out += body_[i];
|
2021-11-25 11:45:38 +00:00
|
|
|
if (body_[i] == '\n' && i + 1 != static_cast<int>(body_.size()))
|
2014-08-02 01:46:00 +00:00
|
|
|
out.insert(out.size(), indent, ' ');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2021-11-25 11:45:38 +00:00
|
|
|
out.insert(out.size(), body_, fragment.first, fragment.second - fragment.first);
|
2014-08-01 21:30:36 +00:00
|
|
|
}
|
2021-11-25 11:45:38 +00:00
|
|
|
|
2014-08-01 21:30:36 +00:00
|
|
|
public:
|
2022-02-18 01:25:02 +00:00
|
|
|
/// Output a returnable template from this mustache template
|
|
|
|
rendered_template render() const
|
|
|
|
{
|
|
|
|
context empty_ctx;
|
|
|
|
std::vector<context*> stack;
|
|
|
|
stack.emplace_back(&empty_ctx);
|
|
|
|
|
|
|
|
std::string ret;
|
|
|
|
render_internal(0, fragments_.size() - 1, stack, ret, 0);
|
|
|
|
return rendered_template(ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Apply the values from the context provided and output a returnable template from this mustache template
|
|
|
|
rendered_template render(context& ctx) const
|
|
|
|
{
|
|
|
|
std::vector<context*> stack;
|
|
|
|
stack.emplace_back(&ctx);
|
|
|
|
|
|
|
|
std::string ret;
|
|
|
|
render_internal(0, fragments_.size() - 1, stack, ret, 0);
|
|
|
|
return rendered_template(ret);
|
|
|
|
}
|
|
|
|
|
2022-06-12 17:47:31 +00:00
|
|
|
/// Apply the values from the context provided and output a returnable template from this mustache template
|
|
|
|
rendered_template render(context&& ctx) const
|
|
|
|
{
|
|
|
|
return render(ctx);
|
|
|
|
}
|
|
|
|
|
2022-02-18 01:25:02 +00:00
|
|
|
/// Output a returnable template from this mustache template
|
|
|
|
std::string render_string() const
|
2014-08-05 18:54:38 +00:00
|
|
|
{
|
2014-12-11 16:38:57 +00:00
|
|
|
context empty_ctx;
|
|
|
|
std::vector<context*> stack;
|
|
|
|
stack.emplace_back(&empty_ctx);
|
2014-08-05 18:54:38 +00:00
|
|
|
|
|
|
|
std::string ret;
|
2021-11-25 11:45:38 +00:00
|
|
|
render_internal(0, fragments_.size() - 1, stack, ret, 0);
|
2014-08-05 18:54:38 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2022-02-18 01:25:02 +00:00
|
|
|
|
|
|
|
/// Apply the values from the context provided and output a returnable template from this mustache template
|
|
|
|
std::string render_string(context& ctx) const
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
2014-12-11 16:38:57 +00:00
|
|
|
std::vector<context*> stack;
|
|
|
|
stack.emplace_back(&ctx);
|
2014-08-01 21:30:36 +00:00
|
|
|
|
|
|
|
std::string ret;
|
2021-11-25 11:45:38 +00:00
|
|
|
render_internal(0, fragments_.size() - 1, stack, ret, 0);
|
2014-07-30 15:50:38 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void parse()
|
|
|
|
{
|
|
|
|
std::string tag_open = "{{";
|
|
|
|
std::string tag_close = "}}";
|
|
|
|
|
|
|
|
std::vector<int> blockPositions;
|
2021-07-03 20:28:52 +00:00
|
|
|
|
2014-07-30 15:50:38 +00:00
|
|
|
size_t current = 0;
|
2021-11-25 11:45:38 +00:00
|
|
|
while (1)
|
2014-07-30 15:50:38 +00:00
|
|
|
{
|
|
|
|
size_t idx = body_.find(tag_open, current);
|
|
|
|
if (idx == body_.npos)
|
|
|
|
{
|
2021-02-21 00:14:30 +00:00
|
|
|
fragments_.emplace_back(static_cast<int>(current), static_cast<int>(body_.size()));
|
2014-07-30 15:50:38 +00:00
|
|
|
actions_.emplace_back(ActionType::Ignore, 0, 0);
|
|
|
|
break;
|
|
|
|
}
|
2021-02-21 00:14:30 +00:00
|
|
|
fragments_.emplace_back(static_cast<int>(current), static_cast<int>(idx));
|
2014-07-30 15:50:38 +00:00
|
|
|
|
|
|
|
idx += tag_open.size();
|
|
|
|
size_t endIdx = body_.find(tag_close, idx);
|
|
|
|
if (endIdx == idx)
|
|
|
|
{
|
|
|
|
throw invalid_template_exception("empty tag is not allowed");
|
|
|
|
}
|
|
|
|
if (endIdx == body_.npos)
|
|
|
|
{
|
|
|
|
// error, no matching tag
|
|
|
|
throw invalid_template_exception("not matched opening tag");
|
|
|
|
}
|
|
|
|
current = endIdx + tag_close.size();
|
2021-11-25 11:45:38 +00:00
|
|
|
switch (body_[idx])
|
2014-07-30 15:50:38 +00:00
|
|
|
{
|
|
|
|
case '#':
|
|
|
|
idx++;
|
2021-11-25 11:45:38 +00:00
|
|
|
while (body_[idx] == ' ')
|
|
|
|
idx++;
|
|
|
|
while (body_[endIdx - 1] == ' ')
|
|
|
|
endIdx--;
|
2021-02-21 00:14:30 +00:00
|
|
|
blockPositions.emplace_back(static_cast<int>(actions_.size()));
|
2014-07-30 15:50:38 +00:00
|
|
|
actions_.emplace_back(ActionType::OpenBlock, idx, endIdx);
|
|
|
|
break;
|
|
|
|
case '/':
|
|
|
|
idx++;
|
2021-11-25 11:45:38 +00:00
|
|
|
while (body_[idx] == ' ')
|
|
|
|
idx++;
|
|
|
|
while (body_[endIdx - 1] == ' ')
|
|
|
|
endIdx--;
|
2014-07-30 15:50:38 +00:00
|
|
|
{
|
|
|
|
auto& matched = actions_[blockPositions.back()];
|
2021-11-25 11:45:38 +00:00
|
|
|
if (body_.compare(idx, endIdx - idx,
|
|
|
|
body_, matched.start, matched.end - matched.start) != 0)
|
2014-07-30 15:50:38 +00:00
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
throw invalid_template_exception("not matched {{# {{/ pair: " +
|
|
|
|
body_.substr(matched.start, matched.end - matched.start) + ", " +
|
|
|
|
body_.substr(idx, endIdx - idx));
|
2014-07-30 15:50:38 +00:00
|
|
|
}
|
2014-12-11 16:38:57 +00:00
|
|
|
matched.pos = actions_.size();
|
2014-07-30 15:50:38 +00:00
|
|
|
}
|
|
|
|
actions_.emplace_back(ActionType::CloseBlock, idx, endIdx, blockPositions.back());
|
|
|
|
blockPositions.pop_back();
|
|
|
|
break;
|
|
|
|
case '^':
|
2014-08-01 21:30:36 +00:00
|
|
|
idx++;
|
2021-11-25 11:45:38 +00:00
|
|
|
while (body_[idx] == ' ')
|
|
|
|
idx++;
|
|
|
|
while (body_[endIdx - 1] == ' ')
|
|
|
|
endIdx--;
|
2021-02-21 00:14:30 +00:00
|
|
|
blockPositions.emplace_back(static_cast<int>(actions_.size()));
|
2014-08-01 21:30:36 +00:00
|
|
|
actions_.emplace_back(ActionType::ElseBlock, idx, endIdx);
|
2014-07-30 15:50:38 +00:00
|
|
|
break;
|
|
|
|
case '!':
|
|
|
|
// do nothing action
|
2021-11-25 11:45:38 +00:00
|
|
|
actions_.emplace_back(ActionType::Ignore, idx + 1, endIdx);
|
2014-07-30 15:50:38 +00:00
|
|
|
break;
|
|
|
|
case '>': // partial
|
2014-08-01 21:30:36 +00:00
|
|
|
idx++;
|
2021-11-25 11:45:38 +00:00
|
|
|
while (body_[idx] == ' ')
|
|
|
|
idx++;
|
|
|
|
while (body_[endIdx - 1] == ' ')
|
|
|
|
endIdx--;
|
2014-08-01 21:30:36 +00:00
|
|
|
actions_.emplace_back(ActionType::Partial, idx, endIdx);
|
2014-07-30 15:50:38 +00:00
|
|
|
break;
|
|
|
|
case '{':
|
|
|
|
if (tag_open != "{{" || tag_close != "}}")
|
|
|
|
throw invalid_template_exception("cannot use triple mustache when delimiter changed");
|
|
|
|
|
2021-11-25 11:45:38 +00:00
|
|
|
idx++;
|
|
|
|
if (body_[endIdx + 2] != '}')
|
2014-07-30 15:50:38 +00:00
|
|
|
{
|
|
|
|
throw invalid_template_exception("{{{: }}} not matched");
|
|
|
|
}
|
2021-11-25 11:45:38 +00:00
|
|
|
while (body_[idx] == ' ')
|
|
|
|
idx++;
|
|
|
|
while (body_[endIdx - 1] == ' ')
|
|
|
|
endIdx--;
|
2014-07-30 15:50:38 +00:00
|
|
|
actions_.emplace_back(ActionType::UnescapeTag, idx, endIdx);
|
|
|
|
current++;
|
|
|
|
break;
|
|
|
|
case '&':
|
2021-11-25 11:45:38 +00:00
|
|
|
idx++;
|
|
|
|
while (body_[idx] == ' ')
|
|
|
|
idx++;
|
|
|
|
while (body_[endIdx - 1] == ' ')
|
|
|
|
endIdx--;
|
2014-07-30 15:50:38 +00:00
|
|
|
actions_.emplace_back(ActionType::UnescapeTag, idx, endIdx);
|
|
|
|
break;
|
|
|
|
case '=':
|
|
|
|
// tag itself is no-op
|
2021-11-25 11:45:38 +00:00
|
|
|
idx++;
|
2014-07-30 15:50:38 +00:00
|
|
|
actions_.emplace_back(ActionType::Ignore, idx, endIdx);
|
2021-11-25 11:45:38 +00:00
|
|
|
endIdx--;
|
2014-07-30 15:50:38 +00:00
|
|
|
if (body_[endIdx] != '=')
|
2021-11-25 11:45:38 +00:00
|
|
|
throw invalid_template_exception("{{=: not matching = tag: " + body_.substr(idx, endIdx - idx));
|
|
|
|
endIdx--;
|
|
|
|
while (body_[idx] == ' ')
|
|
|
|
idx++;
|
|
|
|
while (body_[endIdx] == ' ')
|
|
|
|
endIdx--;
|
2014-07-30 15:50:38 +00:00
|
|
|
endIdx++;
|
|
|
|
{
|
|
|
|
bool succeeded = false;
|
2021-11-25 11:45:38 +00:00
|
|
|
for (size_t i = idx; i < endIdx; i++)
|
2014-07-30 15:50:38 +00:00
|
|
|
{
|
|
|
|
if (body_[i] == ' ')
|
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
tag_open = body_.substr(idx, i - idx);
|
|
|
|
while (body_[i] == ' ')
|
|
|
|
i++;
|
|
|
|
tag_close = body_.substr(i, endIdx - i);
|
2014-07-30 15:50:38 +00:00
|
|
|
if (tag_open.empty())
|
|
|
|
throw invalid_template_exception("{{=: empty open tag");
|
|
|
|
if (tag_close.empty())
|
|
|
|
throw invalid_template_exception("{{=: empty close tag");
|
|
|
|
|
|
|
|
if (tag_close.find(" ") != tag_close.npos)
|
2021-11-25 11:45:38 +00:00
|
|
|
throw invalid_template_exception("{{=: invalid open/close tag: " + tag_open + " " + tag_close);
|
2014-07-30 15:50:38 +00:00
|
|
|
succeeded = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!succeeded)
|
|
|
|
throw invalid_template_exception("{{=: cannot find space between new open/close tags");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// normal tag case;
|
2021-11-25 11:45:38 +00:00
|
|
|
while (body_[idx] == ' ')
|
|
|
|
idx++;
|
|
|
|
while (body_[endIdx - 1] == ' ')
|
|
|
|
endIdx--;
|
2014-07-30 15:50:38 +00:00
|
|
|
actions_.emplace_back(ActionType::Tag, idx, endIdx);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-08-01 21:30:36 +00:00
|
|
|
|
|
|
|
// removing standalones
|
2021-11-25 11:45:38 +00:00
|
|
|
for (int i = actions_.size() - 2; i >= 0; i--)
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
|
|
|
if (actions_[i].t == ActionType::Tag || actions_[i].t == ActionType::UnescapeTag)
|
|
|
|
continue;
|
|
|
|
auto& fragment_before = fragments_[i];
|
2021-11-25 11:45:38 +00:00
|
|
|
auto& fragment_after = fragments_[i + 1];
|
|
|
|
bool is_last_action = i == static_cast<int>(actions_.size()) - 2;
|
2014-08-01 21:30:36 +00:00
|
|
|
bool all_space_before = true;
|
|
|
|
int j, k;
|
2021-11-25 11:45:38 +00:00
|
|
|
for (j = fragment_before.second - 1; j >= fragment_before.first; j--)
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
|
|
|
if (body_[j] != ' ')
|
|
|
|
{
|
|
|
|
all_space_before = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (all_space_before && i > 0)
|
|
|
|
continue;
|
|
|
|
if (!all_space_before && body_[j] != '\n')
|
|
|
|
continue;
|
|
|
|
bool all_space_after = true;
|
2021-11-25 11:45:38 +00:00
|
|
|
for (k = fragment_after.first; k < static_cast<int>(body_.size()) && k < fragment_after.second; k++)
|
2014-08-01 21:30:36 +00:00
|
|
|
{
|
|
|
|
if (body_[k] != ' ')
|
|
|
|
{
|
|
|
|
all_space_after = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (all_space_after && !is_last_action)
|
|
|
|
continue;
|
2021-11-25 11:45:38 +00:00
|
|
|
if (!all_space_after &&
|
|
|
|
!(
|
|
|
|
body_[k] == '\n' ||
|
|
|
|
(body_[k] == '\r' &&
|
|
|
|
k + 1 < static_cast<int>(body_.size()) &&
|
|
|
|
body_[k + 1] == '\n')))
|
2014-08-01 21:30:36 +00:00
|
|
|
continue;
|
2014-08-02 01:46:00 +00:00
|
|
|
if (actions_[i].t == ActionType::Partial)
|
|
|
|
{
|
|
|
|
actions_[i].pos = fragment_before.second - j - 1;
|
|
|
|
}
|
2021-11-25 11:45:38 +00:00
|
|
|
fragment_before.second = j + 1;
|
2014-08-01 21:30:36 +00:00
|
|
|
if (!all_space_after)
|
|
|
|
{
|
|
|
|
if (body_[k] == '\n')
|
|
|
|
k++;
|
2021-11-25 11:45:38 +00:00
|
|
|
else
|
2014-08-01 21:30:36 +00:00
|
|
|
k += 2;
|
|
|
|
fragment_after.first = k;
|
|
|
|
}
|
|
|
|
}
|
2014-07-30 15:50:38 +00:00
|
|
|
}
|
2021-11-25 11:45:38 +00:00
|
|
|
|
|
|
|
std::vector<std::pair<int, int>> fragments_;
|
2014-07-30 15:50:38 +00:00
|
|
|
std::vector<Action> actions_;
|
|
|
|
std::string body_;
|
|
|
|
};
|
|
|
|
|
2014-08-06 20:55:31 +00:00
|
|
|
inline template_t compile(const std::string& body)
|
2014-07-30 15:50:38 +00:00
|
|
|
{
|
|
|
|
return template_t(body);
|
|
|
|
}
|
2014-08-02 01:46:00 +00:00
|
|
|
namespace detail
|
|
|
|
{
|
2014-08-06 20:55:31 +00:00
|
|
|
inline std::string& get_template_base_directory_ref()
|
|
|
|
{
|
|
|
|
static std::string template_base_directory = "templates";
|
|
|
|
return template_base_directory;
|
|
|
|
}
|
2022-03-18 10:55:27 +00:00
|
|
|
|
|
|
|
/// A base directory not related to any blueprint
|
|
|
|
inline std::string& get_global_template_base_directory_ref()
|
|
|
|
{
|
|
|
|
static std::string template_base_directory = "templates";
|
|
|
|
return template_base_directory;
|
|
|
|
}
|
2021-11-25 11:45:38 +00:00
|
|
|
} // namespace detail
|
2014-08-02 01:46:00 +00:00
|
|
|
|
2014-08-06 20:55:31 +00:00
|
|
|
inline std::string default_loader(const std::string& filename)
|
2014-08-02 01:46:00 +00:00
|
|
|
{
|
2016-08-28 05:46:31 +00:00
|
|
|
std::string path = detail::get_template_base_directory_ref();
|
2022-06-21 12:38:22 +00:00
|
|
|
std::ifstream inf(utility::join_path(path, filename));
|
2014-08-02 01:46:00 +00:00
|
|
|
if (!inf)
|
2021-02-09 03:52:05 +00:00
|
|
|
{
|
|
|
|
CROW_LOG_WARNING << "Template \"" << filename << "\" not found.";
|
2014-08-02 01:46:00 +00:00
|
|
|
return {};
|
2021-02-09 03:52:05 +00:00
|
|
|
}
|
2014-08-02 01:46:00 +00:00
|
|
|
return {std::istreambuf_iterator<char>(inf), std::istreambuf_iterator<char>()};
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace detail
|
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
inline std::function<std::string(std::string)>& get_loader_ref()
|
2014-08-06 20:55:31 +00:00
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
static std::function<std::string(std::string)> loader = default_loader;
|
2014-08-06 20:55:31 +00:00
|
|
|
return loader;
|
|
|
|
}
|
2021-11-25 11:45:38 +00:00
|
|
|
} // namespace detail
|
2014-08-02 01:46:00 +00:00
|
|
|
|
2014-08-06 20:55:31 +00:00
|
|
|
inline void set_base(const std::string& path)
|
2014-08-02 01:46:00 +00:00
|
|
|
{
|
2014-08-06 20:55:31 +00:00
|
|
|
auto& base = detail::get_template_base_directory_ref();
|
|
|
|
base = path;
|
2021-11-25 11:45:38 +00:00
|
|
|
if (base.back() != '\\' &&
|
2014-08-06 20:55:31 +00:00
|
|
|
base.back() != '/')
|
2014-08-02 01:46:00 +00:00
|
|
|
{
|
2014-08-06 20:55:31 +00:00
|
|
|
base += '/';
|
2014-08-02 01:46:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-18 10:55:27 +00:00
|
|
|
inline void set_global_base(const std::string& path)
|
|
|
|
{
|
|
|
|
auto& base = detail::get_global_template_base_directory_ref();
|
|
|
|
base = path;
|
|
|
|
if (base.back() != '\\' &&
|
|
|
|
base.back() != '/')
|
|
|
|
{
|
|
|
|
base += '/';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-06 20:55:31 +00:00
|
|
|
inline void set_loader(std::function<std::string(std::string)> loader)
|
2014-08-02 01:46:00 +00:00
|
|
|
{
|
2014-08-06 20:55:31 +00:00
|
|
|
detail::get_loader_ref() = std::move(loader);
|
2014-08-02 01:46:00 +00:00
|
|
|
}
|
|
|
|
|
2016-09-09 16:15:16 +00:00
|
|
|
inline std::string load_text(const std::string& filename)
|
2022-02-11 09:48:33 +00:00
|
|
|
{
|
|
|
|
std::string filename_sanitized(filename);
|
|
|
|
utility::sanitize_filename(filename_sanitized);
|
|
|
|
return detail::get_loader_ref()(filename_sanitized);
|
|
|
|
}
|
|
|
|
|
|
|
|
inline std::string load_text_unsafe(const std::string& filename)
|
2016-09-09 16:15:16 +00:00
|
|
|
{
|
|
|
|
return detail::get_loader_ref()(filename);
|
|
|
|
}
|
|
|
|
|
2014-08-06 20:55:31 +00:00
|
|
|
inline template_t load(const std::string& filename)
|
2014-08-02 01:46:00 +00:00
|
|
|
{
|
2022-01-11 18:48:51 +00:00
|
|
|
std::string filename_sanitized(filename);
|
|
|
|
utility::sanitize_filename(filename_sanitized);
|
|
|
|
return compile(detail::get_loader_ref()(filename_sanitized));
|
2014-08-02 01:46:00 +00:00
|
|
|
}
|
2022-02-11 09:48:33 +00:00
|
|
|
|
|
|
|
inline template_t load_unsafe(const std::string& filename)
|
|
|
|
{
|
|
|
|
return compile(detail::get_loader_ref()(filename));
|
|
|
|
}
|
2021-11-25 11:45:38 +00:00
|
|
|
} // namespace mustache
|
|
|
|
} // namespace crow
|