Crow  1.1
A C++ microframework for the web
 
Loading...
Searching...
No Matches
mustache.h
1#pragma once
2#include <string>
3#include <vector>
4#include <fstream>
5#include <iterator>
6#include <functional>
7#include "crow/json.h"
8#include "crow/logging.h"
9#include "crow/returnable.h"
10#include "crow/utility.h"
11
12namespace crow
13{
14 namespace mustache
15 {
16 using context = json::wvalue;
17
18 template_t load(const std::string& filename);
19
20 class invalid_template_exception : public std::exception
21 {
22 public:
23 invalid_template_exception(const std::string& msg):
24 msg("crow::mustache error: " + msg)
25 {}
26 virtual const char* what() const throw()
27 {
28 return msg.c_str();
29 }
30 std::string msg;
31 };
32
34 {
36 returnable("text/html") {}
37
38 rendered_template(std::string& body):
39 returnable("text/html"), body_(std::move(body)) {}
40
41 std::string body_;
42
43 std::string dump() const override
44 {
45 return body_;
46 }
47 };
48
49 enum class ActionType
50 {
51 Ignore,
52 Tag,
53 UnescapeTag,
54 OpenBlock,
55 CloseBlock,
56 ElseBlock,
57 Partial,
58 };
59
60 struct Action
61 {
62 int start;
63 int end;
64 int pos;
65 ActionType t;
66 Action(ActionType t, size_t start, size_t end, size_t pos = 0):
67 start(static_cast<int>(start)), end(static_cast<int>(end)), pos(static_cast<int>(pos)), t(t)
68 {
69 }
70 };
71
72 /// A mustache template object.
74 {
75 public:
76 template_t(std::string body):
77 body_(std::move(body))
78 {
79 // {{ {{# {{/ {{^ {{! {{> {{=
80 parse();
81 }
82
83 private:
84 std::string tag_name(const Action& action) const
85 {
86 return body_.substr(action.start, action.end - action.start);
87 }
88 auto find_context(const std::string& name, const std::vector<const context*>& stack, bool shouldUseOnlyFirstStackValue = false) const -> std::pair<bool, const context&>
89 {
90 if (name == ".")
91 {
92 return {true, *stack.back()};
93 }
94 static json::wvalue empty_str;
95 empty_str = "";
96
97 int dotPosition = name.find(".");
98 if (dotPosition == static_cast<int>(name.npos))
99 {
100 for (auto it = stack.rbegin(); it != stack.rend(); ++it)
101 {
102 if ((*it)->t() == json::type::Object)
103 {
104 if ((*it)->count(name))
105 return {true, (**it)[name]};
106 }
107 }
108 }
109 else
110 {
111 std::vector<int> dotPositions;
112 dotPositions.push_back(-1);
113 while (dotPosition != static_cast<int>(name.npos))
114 {
115 dotPositions.push_back(dotPosition);
116 dotPosition = name.find(".", dotPosition + 1);
117 }
118 dotPositions.push_back(name.size());
119 std::vector<std::string> names;
120 names.reserve(dotPositions.size() - 1);
121 for (int i = 1; i < static_cast<int>(dotPositions.size()); i++)
122 names.emplace_back(name.substr(dotPositions[i - 1] + 1, dotPositions[i] - dotPositions[i - 1] - 1));
123
124 for (auto it = stack.rbegin(); it != stack.rend(); ++it)
125 {
126 const context* view = *it;
127 bool found = true;
128 for (auto jt = names.begin(); jt != names.end(); ++jt)
129 {
130 if (view->t() == json::type::Object &&
131 view->count(*jt))
132 {
133 view = &(*view)[*jt];
134 }
135 else
136 {
137 if (shouldUseOnlyFirstStackValue)
138 {
139 return {false, empty_str};
140 }
141 found = false;
142 break;
143 }
144 }
145 if (found)
146 return {true, *view};
147 }
148 }
149
150 return {false, empty_str};
151 }
152
153 void escape(const std::string& in, std::string& out) const
154 {
155 out.reserve(out.size() + in.size());
156 for (auto it = in.begin(); it != in.end(); ++it)
157 {
158 switch (*it)
159 {
160 case '&': out += "&amp;"; break;
161 case '<': out += "&lt;"; break;
162 case '>': out += "&gt;"; break;
163 case '"': out += "&quot;"; break;
164 case '\'': out += "&#39;"; break;
165 case '/': out += "&#x2F;"; break;
166 case '`': out += "&#x60;"; break;
167 case '=': out += "&#x3D;"; break;
168 default: out += *it; break;
169 }
170 }
171 }
172
173 bool isTagInsideObjectBlock(const int& current, const std::vector<const context*>& stack) const
174 {
175 int openedBlock = 0;
176 for (int i = current; i > 0; --i)
177 {
178 auto& action = actions_[i - 1];
179
180 if (action.t == ActionType::OpenBlock)
181 {
182 if (openedBlock == 0 && (*stack.rbegin())->t() == json::type::Object)
183 {
184 return true;
185 }
186 --openedBlock;
187 }
188 else if (action.t == ActionType::CloseBlock)
189 {
190 ++openedBlock;
191 }
192 }
193
194 return false;
195 }
196
197 void render_internal(int actionBegin, int actionEnd, std::vector<const context*>& stack, std::string& out, int indent) const
198 {
199 int current = actionBegin;
200
201 if (indent)
202 out.insert(out.size(), indent, ' ');
203
204 while (current < actionEnd)
205 {
206 auto& fragment = fragments_[current];
207 auto& action = actions_[current];
208 render_fragment(fragment, indent, out);
209 switch (action.t)
210 {
211 case ActionType::Ignore:
212 // do nothing
213 break;
214 case ActionType::Partial:
215 {
216 std::string partial_name = tag_name(action);
217 auto partial_templ = load(partial_name);
218 int partial_indent = action.pos;
219 partial_templ.render_internal(0, partial_templ.fragments_.size() - 1, stack, out, partial_indent ? indent + partial_indent : 0);
220 }
221 break;
222 case ActionType::UnescapeTag:
223 case ActionType::Tag:
224 {
225 bool shouldUseOnlyFirstStackValue = false;
226 if (isTagInsideObjectBlock(current, stack))
227 {
228 shouldUseOnlyFirstStackValue = true;
229 }
230 auto optional_ctx = find_context(tag_name(action), stack, shouldUseOnlyFirstStackValue);
231 auto& ctx = optional_ctx.second;
232 switch (ctx.t())
233 {
234 case json::type::False:
235 case json::type::True:
236 case json::type::Number:
237 out += ctx.dump();
238 break;
239 case json::type::String:
240 if (action.t == ActionType::Tag)
241 escape(ctx.s, out);
242 else
243 out += ctx.s;
244 break;
245 case json::type::Function:
246 {
247 std::string execute_result = ctx.execute();
248 while (execute_result.find("{{") != std::string::npos)
249 {
250 template_t result_plug(execute_result);
251 execute_result = result_plug.render_string(*(stack[0]));
252 }
253
254 if (action.t == ActionType::Tag)
255 escape(execute_result, out);
256 else
257 out += execute_result;
258 }
259 break;
260 default:
261 throw std::runtime_error("not implemented tag type" + utility::lexical_cast<std::string>(static_cast<int>(ctx.t())));
262 }
263 }
264 break;
265 case ActionType::ElseBlock:
266 {
267 static context nullContext;
268 auto optional_ctx = find_context(tag_name(action), stack);
269 if (!optional_ctx.first)
270 {
271 stack.emplace_back(&nullContext);
272 break;
273 }
274
275 auto& ctx = optional_ctx.second;
276 switch (ctx.t())
277 {
278 case json::type::List:
279 if (ctx.l && !ctx.l->empty())
280 current = action.pos;
281 else
282 stack.emplace_back(&nullContext);
283 break;
284 case json::type::False:
285 case json::type::Null:
286 stack.emplace_back(&nullContext);
287 break;
288 default:
289 current = action.pos;
290 break;
291 }
292 break;
293 }
294 case ActionType::OpenBlock:
295 {
296 auto optional_ctx = find_context(tag_name(action), stack);
297 if (!optional_ctx.first)
298 {
299 current = action.pos;
300 break;
301 }
302
303 auto& ctx = optional_ctx.second;
304 switch (ctx.t())
305 {
306 case json::type::List:
307 if (ctx.l)
308 for (auto it = ctx.l->begin(); it != ctx.l->end(); ++it)
309 {
310 stack.push_back(&*it);
311 render_internal(current + 1, action.pos, stack, out, indent);
312 stack.pop_back();
313 }
314 current = action.pos;
315 break;
316 case json::type::Number:
317 case json::type::String:
318 case json::type::Object:
319 case json::type::True:
320 stack.push_back(&ctx);
321 break;
322 case json::type::False:
323 case json::type::Null:
324 current = action.pos;
325 break;
326 default:
327 throw std::runtime_error("{{#: not implemented context type: " + utility::lexical_cast<std::string>(static_cast<int>(ctx.t())));
328 break;
329 }
330 break;
331 }
332 case ActionType::CloseBlock:
333 stack.pop_back();
334 break;
335 default:
336 throw std::runtime_error("not implemented " + utility::lexical_cast<std::string>(static_cast<int>(action.t)));
337 }
338 current++;
339 }
340 auto& fragment = fragments_[actionEnd];
341 render_fragment(fragment, indent, out);
342 }
343 void render_fragment(const std::pair<int, int> fragment, int indent, std::string& out) const
344 {
345 if (indent)
346 {
347 for (int i = fragment.first; i < fragment.second; i++)
348 {
349 out += body_[i];
350 if (body_[i] == '\n' && i + 1 != static_cast<int>(body_.size()))
351 out.insert(out.size(), indent, ' ');
352 }
353 }
354 else
355 out.insert(out.size(), body_, fragment.first, fragment.second - fragment.first);
356 }
357
358 public:
359 /// Output a returnable template from this mustache template
361 {
362 context empty_ctx;
363 std::vector<const context*> stack;
364 stack.emplace_back(&empty_ctx);
365
366 std::string ret;
367 render_internal(0, fragments_.size() - 1, stack, ret, 0);
368 return rendered_template(ret);
369 }
370
371 /// Apply the values from the context provided and output a returnable template from this mustache template
373 {
374 std::vector<const context*> stack;
375 stack.emplace_back(&ctx);
376
377 std::string ret;
378 render_internal(0, fragments_.size() - 1, stack, ret, 0);
379 return rendered_template(ret);
380 }
381
382 /// Apply the values from the context provided and output a returnable template from this mustache template
384 {
385 return render(ctx);
386 }
387
388 /// Output a returnable template from this mustache template
389 std::string render_string() const
390 {
391 context empty_ctx;
392 std::vector<const context*> stack;
393 stack.emplace_back(&empty_ctx);
394
395 std::string ret;
396 render_internal(0, fragments_.size() - 1, stack, ret, 0);
397 return ret;
398 }
399
400 /// Apply the values from the context provided and output a returnable template from this mustache template
401 std::string render_string(const context& ctx) const
402 {
403 std::vector<const context*> stack;
404 stack.emplace_back(&ctx);
405
406 std::string ret;
407 render_internal(0, fragments_.size() - 1, stack, ret, 0);
408 return ret;
409 }
410
411 private:
412 void parse()
413 {
414 std::string tag_open = "{{";
415 std::string tag_close = "}}";
416
417 std::vector<int> blockPositions;
418
419 size_t current = 0;
420 while (1)
421 {
422 size_t idx = body_.find(tag_open, current);
423 if (idx == body_.npos)
424 {
425 fragments_.emplace_back(static_cast<int>(current), static_cast<int>(body_.size()));
426 actions_.emplace_back(ActionType::Ignore, 0, 0);
427 break;
428 }
429 fragments_.emplace_back(static_cast<int>(current), static_cast<int>(idx));
430
431 idx += tag_open.size();
432 size_t endIdx = body_.find(tag_close, idx);
433 if (endIdx == idx)
434 {
435 throw invalid_template_exception("empty tag is not allowed");
436 }
437 if (endIdx == body_.npos)
438 {
439 // error, no matching tag
440 throw invalid_template_exception("not matched opening tag");
441 }
442 current = endIdx + tag_close.size();
443 switch (body_[idx])
444 {
445 case '#':
446 idx++;
447 while (body_[idx] == ' ')
448 idx++;
449 while (body_[endIdx - 1] == ' ')
450 endIdx--;
451 blockPositions.emplace_back(static_cast<int>(actions_.size()));
452 actions_.emplace_back(ActionType::OpenBlock, idx, endIdx);
453 break;
454 case '/':
455 idx++;
456 while (body_[idx] == ' ')
457 idx++;
458 while (body_[endIdx - 1] == ' ')
459 endIdx--;
460 {
461 auto& matched = actions_[blockPositions.back()];
462 if (body_.compare(idx, endIdx - idx,
463 body_, matched.start, matched.end - matched.start) != 0)
464 {
465 throw invalid_template_exception("not matched {{# {{/ pair: " +
466 body_.substr(matched.start, matched.end - matched.start) + ", " +
467 body_.substr(idx, endIdx - idx));
468 }
469 matched.pos = actions_.size();
470 }
471 actions_.emplace_back(ActionType::CloseBlock, idx, endIdx, blockPositions.back());
472 blockPositions.pop_back();
473 break;
474 case '^':
475 idx++;
476 while (body_[idx] == ' ')
477 idx++;
478 while (body_[endIdx - 1] == ' ')
479 endIdx--;
480 blockPositions.emplace_back(static_cast<int>(actions_.size()));
481 actions_.emplace_back(ActionType::ElseBlock, idx, endIdx);
482 break;
483 case '!':
484 // do nothing action
485 actions_.emplace_back(ActionType::Ignore, idx + 1, endIdx);
486 break;
487 case '>': // partial
488 idx++;
489 while (body_[idx] == ' ')
490 idx++;
491 while (body_[endIdx - 1] == ' ')
492 endIdx--;
493 actions_.emplace_back(ActionType::Partial, idx, endIdx);
494 break;
495 case '{':
496 if (tag_open != "{{" || tag_close != "}}")
497 throw invalid_template_exception("cannot use triple mustache when delimiter changed");
498
499 idx++;
500 if (body_[endIdx + 2] != '}')
501 {
502 throw invalid_template_exception("{{{: }}} not matched");
503 }
504 while (body_[idx] == ' ')
505 idx++;
506 while (body_[endIdx - 1] == ' ')
507 endIdx--;
508 actions_.emplace_back(ActionType::UnescapeTag, idx, endIdx);
509 current++;
510 break;
511 case '&':
512 idx++;
513 while (body_[idx] == ' ')
514 idx++;
515 while (body_[endIdx - 1] == ' ')
516 endIdx--;
517 actions_.emplace_back(ActionType::UnescapeTag, idx, endIdx);
518 break;
519 case '=':
520 // tag itself is no-op
521 idx++;
522 actions_.emplace_back(ActionType::Ignore, idx, endIdx);
523 endIdx--;
524 if (body_[endIdx] != '=')
525 throw invalid_template_exception("{{=: not matching = tag: " + body_.substr(idx, endIdx - idx));
526 endIdx--;
527 while (body_[idx] == ' ')
528 idx++;
529 while (body_[endIdx] == ' ')
530 endIdx--;
531 endIdx++;
532 {
533 bool succeeded = false;
534 for (size_t i = idx; i < endIdx; i++)
535 {
536 if (body_[i] == ' ')
537 {
538 tag_open = body_.substr(idx, i - idx);
539 while (body_[i] == ' ')
540 i++;
541 tag_close = body_.substr(i, endIdx - i);
542 if (tag_open.empty())
543 throw invalid_template_exception("{{=: empty open tag");
544 if (tag_close.empty())
545 throw invalid_template_exception("{{=: empty close tag");
546
547 if (tag_close.find(" ") != tag_close.npos)
548 throw invalid_template_exception("{{=: invalid open/close tag: " + tag_open + " " + tag_close);
549 succeeded = true;
550 break;
551 }
552 }
553 if (!succeeded)
554 throw invalid_template_exception("{{=: cannot find space between new open/close tags");
555 }
556 break;
557 default:
558 // normal tag case;
559 while (body_[idx] == ' ')
560 idx++;
561 while (body_[endIdx - 1] == ' ')
562 endIdx--;
563 actions_.emplace_back(ActionType::Tag, idx, endIdx);
564 break;
565 }
566 }
567
568 // removing standalones
569 for (int i = actions_.size() - 2; i >= 0; i--)
570 {
571 if (actions_[i].t == ActionType::Tag || actions_[i].t == ActionType::UnescapeTag)
572 continue;
573 auto& fragment_before = fragments_[i];
574 auto& fragment_after = fragments_[i + 1];
575 bool is_last_action = i == static_cast<int>(actions_.size()) - 2;
576 bool all_space_before = true;
577 int j, k;
578 for (j = fragment_before.second - 1; j >= fragment_before.first; j--)
579 {
580 if (body_[j] != ' ')
581 {
582 all_space_before = false;
583 break;
584 }
585 }
586 if (all_space_before && i > 0)
587 continue;
588 if (!all_space_before && body_[j] != '\n')
589 continue;
590 bool all_space_after = true;
591 for (k = fragment_after.first; k < static_cast<int>(body_.size()) && k < fragment_after.second; k++)
592 {
593 if (body_[k] != ' ')
594 {
595 all_space_after = false;
596 break;
597 }
598 }
599 if (all_space_after && !is_last_action)
600 continue;
601 if (!all_space_after &&
602 !(
603 body_[k] == '\n' ||
604 (body_[k] == '\r' &&
605 k + 1 < static_cast<int>(body_.size()) &&
606 body_[k + 1] == '\n')))
607 continue;
608 if (actions_[i].t == ActionType::Partial)
609 {
610 actions_[i].pos = fragment_before.second - j - 1;
611 }
612 fragment_before.second = j + 1;
613 if (!all_space_after)
614 {
615 if (body_[k] == '\n')
616 k++;
617 else
618 k += 2;
619 fragment_after.first = k;
620 }
621 }
622 }
623
624 std::vector<std::pair<int, int>> fragments_;
625 std::vector<Action> actions_;
626 std::string body_;
627 };
628
629 inline template_t compile(const std::string& body)
630 {
631 return template_t(body);
632 }
633 namespace detail
634 {
635 inline std::string& get_template_base_directory_ref()
636 {
637 static std::string template_base_directory = "templates";
638 return template_base_directory;
639 }
640
641 /// A base directory not related to any blueprint
642 inline std::string& get_global_template_base_directory_ref()
643 {
644 static std::string template_base_directory = "templates";
645 return template_base_directory;
646 }
647 } // namespace detail
648
649 inline std::string default_loader(const std::string& filename)
650 {
651 std::string path = detail::get_template_base_directory_ref();
652 std::ifstream inf(utility::join_path(path, filename));
653 if (!inf)
654 {
655 CROW_LOG_WARNING << "Template \"" << filename << "\" not found.";
656 return {};
657 }
658 return {std::istreambuf_iterator<char>(inf), std::istreambuf_iterator<char>()};
659 }
660
661 namespace detail
662 {
663 inline std::function<std::string(std::string)>& get_loader_ref()
664 {
665 static std::function<std::string(std::string)> loader = default_loader;
666 return loader;
667 }
668 } // namespace detail
669
670 inline void set_base(const std::string& path)
671 {
672 auto& base = detail::get_template_base_directory_ref();
673 base = path;
674 if (base.back() != '\\' &&
675 base.back() != '/')
676 {
677 base += '/';
678 }
679 }
680
681 inline void set_global_base(const std::string& path)
682 {
683 auto& base = detail::get_global_template_base_directory_ref();
684 base = path;
685 if (base.back() != '\\' &&
686 base.back() != '/')
687 {
688 base += '/';
689 }
690 }
691
692 inline void set_loader(std::function<std::string(std::string)> loader)
693 {
694 detail::get_loader_ref() = std::move(loader);
695 }
696
697 inline std::string load_text(const std::string& filename)
698 {
699 std::string filename_sanitized(filename);
700 utility::sanitize_filename(filename_sanitized);
701 return detail::get_loader_ref()(filename_sanitized);
702 }
703
704 inline std::string load_text_unsafe(const std::string& filename)
705 {
706 return detail::get_loader_ref()(filename);
707 }
708
709 inline template_t load(const std::string& filename)
710 {
711 std::string filename_sanitized(filename);
712 utility::sanitize_filename(filename_sanitized);
713 return compile(detail::get_loader_ref()(filename_sanitized));
714 }
715
716 inline template_t load_unsafe(const std::string& filename)
717 {
718 return compile(detail::get_loader_ref()(filename));
719 }
720 } // namespace mustache
721} // namespace crow
JSON write value.
Definition json.h:1289
A mustache template object.
Definition mustache.h:74
std::string render_string() const
Output a returnable template from this mustache template.
Definition mustache.h:389
rendered_template render() const
Output a returnable template from this mustache template.
Definition mustache.h:360
rendered_template render(const context &ctx) const
Apply the values from the context provided and output a returnable template from this mustache templa...
Definition mustache.h:372
rendered_template render(const context &&ctx) const
Apply the values from the context provided and output a returnable template from this mustache templa...
Definition mustache.h:383
std::string render_string(const context &ctx) const
Apply the values from the context provided and output a returnable template from this mustache templa...
Definition mustache.h:401
The main namespace of the library. In this namespace is defined the most important classes and functi...
Definition mustache.h:61
Definition mustache.h:34
An abstract class that allows any other class to be returned by a handler.
Definition returnable.h:9