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