Crow  1.1
A C++ microframework for the web
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 
12 namespace 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.
73  class template_t
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
372  rendered_template render(const context& ctx) const
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
383  rendered_template render(const context&& ctx) const
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
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