Inja 3.5.0
A Template Engine for Modern C++
Loading...
Searching...
No Matches
renderer.hpp
1#ifndef INCLUDE_INJA_RENDERER_HPP_
2#define INCLUDE_INJA_RENDERER_HPP_
3
4#include <algorithm>
5#include <array>
6#include <cctype>
7#include <cmath>
8#include <cstddef>
9#include <memory>
10#include <numeric>
11#include <ostream>
12#include <sstream>
13#include <stack>
14#include <string>
15#include <utility>
16#include <vector>
17
18#include "config.hpp"
19#include "exceptions.hpp"
20#include "function_storage.hpp"
21#include "node.hpp"
22#include "template.hpp"
23#include "throw.hpp"
24#include "utils.hpp"
25
26namespace inja {
27
31inline std::string htmlescape(const std::string& data) {
32 std::string buffer;
33 buffer.reserve(static_cast<size_t>(1.1 * data.size()));
34 for (size_t pos = 0; pos != data.size(); ++pos) {
35 switch (data[pos]) {
36 case '&': buffer.append("&amp;"); break;
37 case '\"': buffer.append("&quot;"); break;
38 case '\'': buffer.append("&apos;"); break;
39 case '<': buffer.append("&lt;"); break;
40 case '>': buffer.append("&gt;"); break;
41 default: buffer.append(&data[pos], 1); break;
42 }
43 }
44 return buffer;
45}
46
50class Renderer : public NodeVisitor {
51 using Op = FunctionStorage::Operation;
52
53 const RenderConfig config;
54 const TemplateStorage& template_storage;
55 const FunctionStorage& function_storage;
56
57 const Template* current_template;
58 size_t current_level {0};
59 std::vector<const Template*> template_stack;
60 std::vector<const BlockStatementNode*> block_statement_stack;
61
62 const json* data_input;
63 std::ostream* output_stream;
64
65 json additional_data;
66 json* current_loop_data = &additional_data["loop"];
67
68 std::vector<std::shared_ptr<json>> data_tmp_stack;
69 std::stack<const json*> data_eval_stack;
70 std::stack<const DataNode*> not_found_stack;
71
72 bool break_rendering {false};
73
74 static bool truthy(const json* data) {
75 if (data->is_boolean()) {
76 return data->get<bool>();
77 } else if (data->is_number()) {
78 return (*data != 0);
79 } else if (data->is_null()) {
80 return false;
81 }
82 return !data->empty();
83 }
84
85 void print_data(const std::shared_ptr<json>& value) {
86 if (value->is_string()) {
87 if (config.html_autoescape) {
88 *output_stream << htmlescape(value->get_ref<const json::string_t&>());
89 } else {
90 *output_stream << value->get_ref<const json::string_t&>();
91 }
92 } else if (value->is_number_unsigned()) {
93 *output_stream << value->get<const json::number_unsigned_t>();
94 } else if (value->is_number_integer()) {
95 *output_stream << value->get<const json::number_integer_t>();
96 } else if (value->is_null()) {
97 } else {
98 *output_stream << value->dump();
99 }
100 }
101
102 const std::shared_ptr<json> eval_expression_list(const ExpressionListNode& expression_list) {
103 if (!expression_list.root) {
104 throw_renderer_error("empty expression", expression_list);
105 }
106
107 expression_list.root->accept(*this);
108
109 if (data_eval_stack.empty()) {
110 throw_renderer_error("empty expression", expression_list);
111 } else if (data_eval_stack.size() != 1) {
112 throw_renderer_error("malformed expression", expression_list);
113 }
114
115 const auto result = data_eval_stack.top();
116 data_eval_stack.pop();
117
118 if (result == nullptr) {
119 if (not_found_stack.empty()) {
120 throw_renderer_error("expression could not be evaluated", expression_list);
121 }
122
123 const auto node = not_found_stack.top();
124 not_found_stack.pop();
125
126 throw_renderer_error("variable '" + static_cast<std::string>(node->name) + "' not found", *node);
127 }
128 return std::make_shared<json>(*result);
129 }
130
131 void throw_renderer_error(const std::string& message, const AstNode& node) {
132 const SourceLocation loc = get_source_location(current_template->content, node.pos);
133 INJA_THROW(RenderError(message, loc));
134 }
135
136 void make_result(const json&& result) {
137 auto result_ptr = std::make_shared<json>(result);
138 data_tmp_stack.push_back(result_ptr);
139 data_eval_stack.push(result_ptr.get());
140 }
141
142 template <size_t N, size_t N_start = 0, bool throw_not_found = true> std::array<const json*, N> get_arguments(const FunctionNode& node) {
143 if (node.arguments.size() < N_start + N) {
144 throw_renderer_error("function needs " + std::to_string(N_start + N) + " variables, but has only found " + std::to_string(node.arguments.size()), node);
145 }
146
147 for (size_t i = N_start; i < N_start + N; i += 1) {
148 node.arguments[i]->accept(*this);
149 }
150
151 if (data_eval_stack.size() < N) {
152 throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node);
153 }
154
155 std::array<const json*, N> result;
156 for (size_t i = 0; i < N; i += 1) {
157 result[N - i - 1] = data_eval_stack.top();
158 data_eval_stack.pop();
159
160 if (!result[N - i - 1]) {
161 const auto data_node = not_found_stack.top();
162 not_found_stack.pop();
163
164 if (throw_not_found) {
165 throw_renderer_error("variable '" + static_cast<std::string>(data_node->name) + "' not found", *data_node);
166 }
167 }
168 }
169 return result;
170 }
171
172 template <bool throw_not_found = true> Arguments get_argument_vector(const FunctionNode& node) {
173 const size_t N = node.arguments.size();
174 for (const auto& a : node.arguments) {
175 a->accept(*this);
176 }
177
178 if (data_eval_stack.size() < N) {
179 throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node);
180 }
181
182 Arguments result {N};
183 for (size_t i = 0; i < N; i += 1) {
184 result[N - i - 1] = data_eval_stack.top();
185 data_eval_stack.pop();
186
187 if (!result[N - i - 1]) {
188 const auto data_node = not_found_stack.top();
189 not_found_stack.pop();
190
191 if (throw_not_found) {
192 throw_renderer_error("variable '" + static_cast<std::string>(data_node->name) + "' not found", *data_node);
193 }
194 }
195 }
196 return result;
197 }
198
199 void visit(const BlockNode& node) override {
200 for (const auto& n : node.nodes) {
201 n->accept(*this);
202
203 if (break_rendering) {
204 break;
205 }
206 }
207 }
208
209 void visit(const TextNode& node) override {
210 output_stream->write(current_template->content.c_str() + node.pos, node.length);
211 }
212
213 void visit(const ExpressionNode&) override {}
214
215 void visit(const LiteralNode& node) override {
216 data_eval_stack.push(&node.value);
217 }
218
219 void visit(const DataNode& node) override {
220 if (additional_data.contains(node.ptr)) {
221 data_eval_stack.push(&(additional_data[node.ptr]));
222 } else if (data_input->contains(node.ptr)) {
223 data_eval_stack.push(&(*data_input)[node.ptr]);
224 } else {
225 // Try to evaluate as a no-argument callback
226 const auto function_data = function_storage.find_function(node.name, 0);
227 if (function_data.operation == FunctionStorage::Operation::Callback) {
228 Arguments empty_args {};
229 const auto value = std::make_shared<json>(function_data.callback(empty_args));
230 data_tmp_stack.push_back(value);
231 data_eval_stack.push(value.get());
232 } else {
233 data_eval_stack.push(nullptr);
234 not_found_stack.emplace(&node);
235 }
236 }
237 }
238
239 void visit(const FunctionNode& node) override {
240 switch (node.operation) {
241 case Op::Not: {
242 const auto args = get_arguments<1>(node);
243 make_result(!truthy(args[0]));
244 } break;
245 case Op::And: {
246 make_result(truthy(get_arguments<1, 0>(node)[0]) && truthy(get_arguments<1, 1>(node)[0]));
247 } break;
248 case Op::Or: {
249 make_result(truthy(get_arguments<1, 0>(node)[0]) || truthy(get_arguments<1, 1>(node)[0]));
250 } break;
251 case Op::In: {
252 const auto args = get_arguments<2>(node);
253 make_result(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end());
254 } break;
255 case Op::Equal: {
256 const auto args = get_arguments<2>(node);
257 make_result(*args[0] == *args[1]);
258 } break;
259 case Op::NotEqual: {
260 const auto args = get_arguments<2>(node);
261 make_result(*args[0] != *args[1]);
262 } break;
263 case Op::Greater: {
264 const auto args = get_arguments<2>(node);
265 make_result(*args[0] > *args[1]);
266 } break;
267 case Op::GreaterEqual: {
268 const auto args = get_arguments<2>(node);
269 make_result(*args[0] >= *args[1]);
270 } break;
271 case Op::Less: {
272 const auto args = get_arguments<2>(node);
273 make_result(*args[0] < *args[1]);
274 } break;
275 case Op::LessEqual: {
276 const auto args = get_arguments<2>(node);
277 make_result(*args[0] <= *args[1]);
278 } break;
279 case Op::Add: {
280 const auto args = get_arguments<2>(node);
281 if (args[0]->is_string() && args[1]->is_string()) {
282 make_result(args[0]->get_ref<const json::string_t&>() + args[1]->get_ref<const json::string_t&>());
283 } else if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
284 make_result(args[0]->get<const json::number_integer_t>() + args[1]->get<const json::number_integer_t>());
285 } else {
286 make_result(args[0]->get<const json::number_float_t>() + args[1]->get<const json::number_float_t>());
287 }
288 } break;
289 case Op::Subtract: {
290 const auto args = get_arguments<2>(node);
291 if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
292 make_result(args[0]->get<const json::number_integer_t>() - args[1]->get<const json::number_integer_t>());
293 } else {
294 make_result(args[0]->get<const json::number_float_t>() - args[1]->get<const json::number_float_t>());
295 }
296 } break;
297 case Op::Multiplication: {
298 const auto args = get_arguments<2>(node);
299 if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
300 make_result(args[0]->get<const json::number_integer_t>() * args[1]->get<const json::number_integer_t>());
301 } else {
302 make_result(args[0]->get<const json::number_float_t>() * args[1]->get<const json::number_float_t>());
303 }
304 } break;
305 case Op::Division: {
306 const auto args = get_arguments<2>(node);
307 if (args[1]->get<const json::number_float_t>() == 0) {
308 throw_renderer_error("division by zero", node);
309 }
310 make_result(args[0]->get<const json::number_float_t>() / args[1]->get<const json::number_float_t>());
311 } break;
312 case Op::Power: {
313 const auto args = get_arguments<2>(node);
314 if (args[0]->is_number_integer() && args[1]->get<const json::number_integer_t>() >= 0) {
315 const auto result = static_cast<json::number_integer_t>(std::pow(args[0]->get<const json::number_integer_t>(), args[1]->get<const json::number_integer_t>()));
316 make_result(result);
317 } else {
318 const auto result = std::pow(args[0]->get<const json::number_float_t>(), args[1]->get<const json::number_integer_t>());
319 make_result(result);
320 }
321 } break;
322 case Op::Modulo: {
323 const auto args = get_arguments<2>(node);
324 make_result(args[0]->get<const json::number_integer_t>() % args[1]->get<const json::number_integer_t>());
325 } break;
326 case Op::AtId: {
327 const auto container = get_arguments<1, 0, false>(node)[0];
328 node.arguments[1]->accept(*this);
329 if (not_found_stack.empty()) {
330 throw_renderer_error("could not find element with given name", node);
331 }
332 const auto id_node = not_found_stack.top();
333 not_found_stack.pop();
334 data_eval_stack.pop();
335 data_eval_stack.push(&container->at(id_node->name));
336 } break;
337 case Op::At: {
338 const auto args = get_arguments<2>(node);
339 if (args[0]->is_object()) {
340 data_eval_stack.push(&args[0]->at(args[1]->get<std::string>()));
341 } else {
342 data_eval_stack.push(&args[0]->at(args[1]->get<int>()));
343 }
344 } break;
345 case Op::Capitalize: {
346 auto result = get_arguments<1>(node)[0]->get<json::string_t>();
347 result[0] = static_cast<char>(::toupper(result[0]));
348 std::transform(result.begin() + 1, result.end(), result.begin() + 1, [](char c) { return static_cast<char>(::tolower(c)); });
349 make_result(std::move(result));
350 } break;
351 case Op::Default: {
352 const auto test_arg = get_arguments<1, 0, false>(node)[0];
353 data_eval_stack.push((test_arg != nullptr) ? test_arg : get_arguments<1, 1>(node)[0]);
354 } break;
355 case Op::DivisibleBy: {
356 const auto args = get_arguments<2>(node);
357 const auto divisor = args[1]->get<const json::number_integer_t>();
358 make_result((divisor != 0) && (args[0]->get<const json::number_integer_t>() % divisor == 0));
359 } break;
360 case Op::Even: {
361 make_result(get_arguments<1>(node)[0]->get<const json::number_integer_t>() % 2 == 0);
362 } break;
363 case Op::Exists: {
364 auto&& name = get_arguments<1>(node)[0]->get_ref<const json::string_t&>();
365 make_result(data_input->contains(json::json_pointer(DataNode::convert_dot_to_ptr(name))));
366 } break;
367 case Op::ExistsInObject: {
368 const auto args = get_arguments<2>(node);
369 auto&& name = args[1]->get_ref<const json::string_t&>();
370 make_result(args[0]->find(name) != args[0]->end());
371 } break;
372 case Op::First: {
373 const auto result = &get_arguments<1>(node)[0]->front();
374 data_eval_stack.push(result);
375 } break;
376 case Op::Float: {
377 make_result(std::stod(get_arguments<1>(node)[0]->get_ref<const json::string_t&>()));
378 } break;
379 case Op::Int: {
380 make_result(std::stoi(get_arguments<1>(node)[0]->get_ref<const json::string_t&>()));
381 } break;
382 case Op::Last: {
383 const auto result = &get_arguments<1>(node)[0]->back();
384 data_eval_stack.push(result);
385 } break;
386 case Op::Length: {
387 const auto val = get_arguments<1>(node)[0];
388 if (val->is_string()) {
389 make_result(val->get_ref<const json::string_t&>().length());
390 } else {
391 make_result(val->size());
392 }
393 } break;
394 case Op::Lower: {
395 auto result = get_arguments<1>(node)[0]->get<json::string_t>();
396 std::transform(result.begin(), result.end(), result.begin(), [](char c) { return static_cast<char>(::tolower(c)); });
397 make_result(std::move(result));
398 } break;
399 case Op::Max: {
400 const auto args = get_arguments<1>(node);
401 const auto result = std::max_element(args[0]->begin(), args[0]->end());
402 data_eval_stack.push(&(*result));
403 } break;
404 case Op::Min: {
405 const auto args = get_arguments<1>(node);
406 const auto result = std::min_element(args[0]->begin(), args[0]->end());
407 data_eval_stack.push(&(*result));
408 } break;
409 case Op::Odd: {
410 make_result(get_arguments<1>(node)[0]->get<const json::number_integer_t>() % 2 != 0);
411 } break;
412 case Op::Range: {
413 std::vector<int> result(get_arguments<1>(node)[0]->get<const json::number_integer_t>());
414 std::iota(result.begin(), result.end(), 0);
415 make_result(std::move(result));
416 } break;
417 case Op::Replace: {
418 const auto args = get_arguments<3>(node);
419 auto result = args[0]->get<std::string>();
420 replace_substring(result, args[1]->get<std::string>(), args[2]->get<std::string>());
421 make_result(std::move(result));
422 } break;
423 case Op::Round: {
424 const auto args = get_arguments<2>(node);
425 const auto precision = args[1]->get<const json::number_integer_t>();
426 const double result = std::round(args[0]->get<const json::number_float_t>() * std::pow(10.0, precision)) / std::pow(10.0, precision);
427 if (precision == 0) {
428 make_result(static_cast<int>(result));
429 } else {
430 make_result(result);
431 }
432 } break;
433 case Op::Sort: {
434 auto result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->get<std::vector<json>>());
435 std::sort(result_ptr->begin(), result_ptr->end());
436 data_tmp_stack.push_back(result_ptr);
437 data_eval_stack.push(result_ptr.get());
438 } break;
439 case Op::Upper: {
440 auto result = get_arguments<1>(node)[0]->get<json::string_t>();
441 std::transform(result.begin(), result.end(), result.begin(), [](char c) { return static_cast<char>(::toupper(c)); });
442 make_result(std::move(result));
443 } break;
444 case Op::IsBoolean: {
445 make_result(get_arguments<1>(node)[0]->is_boolean());
446 } break;
447 case Op::IsNumber: {
448 make_result(get_arguments<1>(node)[0]->is_number());
449 } break;
450 case Op::IsInteger: {
451 make_result(get_arguments<1>(node)[0]->is_number_integer());
452 } break;
453 case Op::IsFloat: {
454 make_result(get_arguments<1>(node)[0]->is_number_float());
455 } break;
456 case Op::IsObject: {
457 make_result(get_arguments<1>(node)[0]->is_object());
458 } break;
459 case Op::IsArray: {
460 make_result(get_arguments<1>(node)[0]->is_array());
461 } break;
462 case Op::IsString: {
463 make_result(get_arguments<1>(node)[0]->is_string());
464 } break;
465 case Op::Callback: {
466 auto args = get_argument_vector(node);
467 make_result(node.callback(args));
468 } break;
469 case Op::Super: {
470 const auto args = get_argument_vector(node);
471 const size_t old_level = current_level;
472 const size_t level_diff = (args.size() == 1) ? args[0]->get<int>() : 1;
473 const size_t level = current_level + level_diff;
474
475 if (block_statement_stack.empty()) {
476 throw_renderer_error("super() call is not within a block", node);
477 }
478
479 if (level < 1 || level > template_stack.size() - 1) {
480 throw_renderer_error("level of super() call does not match parent templates (between 1 and " + std::to_string(template_stack.size() - 1) + ")", node);
481 }
482
483 const auto current_block_statement = block_statement_stack.back();
484 const Template* new_template = template_stack.at(level);
485 const Template* old_template = current_template;
486 const auto block_it = new_template->block_storage.find(current_block_statement->name);
487 if (block_it != new_template->block_storage.end()) {
488 current_template = new_template;
489 current_level = level;
490 block_it->second->block.accept(*this);
491 current_level = old_level;
492 current_template = old_template;
493 } else {
494 throw_renderer_error("could not find block with name '" + current_block_statement->name + "'", node);
495 }
496 make_result(nullptr);
497 } break;
498 case Op::Join: {
499 const auto args = get_arguments<2>(node);
500 const auto separator = args[1]->get<json::string_t>();
501 std::ostringstream os;
502 std::string sep;
503 for (const auto& value : *args[0]) {
504 os << sep;
505 if (value.is_string()) {
506 os << value.get<std::string>(); // otherwise the value is surrounded with ""
507 } else {
508 os << value.dump();
509 }
510 sep = separator;
511 }
512 make_result(os.str());
513 } break;
514 case Op::None:
515 break;
516 }
517 }
518
519 void visit(const ExpressionListNode& node) override {
520 print_data(eval_expression_list(node));
521 }
522
523 void visit(const StatementNode&) override {}
524
525 void visit(const ForStatementNode&) override {}
526
527 void visit(const ForArrayStatementNode& node) override {
528 const auto result = eval_expression_list(node.condition);
529 if (!result->is_array()) {
530 throw_renderer_error("object must be an array", node);
531 }
532
533 if (!current_loop_data->empty()) {
534 auto tmp = *current_loop_data; // Because of clang-3
535 (*current_loop_data)["parent"] = std::move(tmp);
536 }
537
538 size_t index = 0;
539 (*current_loop_data)["is_first"] = true;
540 (*current_loop_data)["is_last"] = (result->size() <= 1);
541 for (auto it = result->begin(); it != result->end(); ++it) {
542 additional_data[static_cast<std::string>(node.value)] = *it;
543
544 (*current_loop_data)["index"] = index;
545 (*current_loop_data)["index1"] = index + 1;
546 if (index == 1) {
547 (*current_loop_data)["is_first"] = false;
548 }
549 if (index == result->size() - 1) {
550 (*current_loop_data)["is_last"] = true;
551 }
552
553 node.body.accept(*this);
554 ++index;
555 }
556
557 additional_data[static_cast<std::string>(node.value)].clear();
558 if (!(*current_loop_data)["parent"].empty()) {
559 const auto tmp = (*current_loop_data)["parent"];
560 *current_loop_data = tmp;
561 } else {
562 current_loop_data = &additional_data["loop"];
563 }
564 }
565
566 void visit(const ForObjectStatementNode& node) override {
567 const auto result = eval_expression_list(node.condition);
568 if (!result->is_object()) {
569 throw_renderer_error("object must be an object", node);
570 }
571
572 if (!current_loop_data->empty()) {
573 (*current_loop_data)["parent"] = std::move(*current_loop_data);
574 }
575
576 size_t index = 0;
577 (*current_loop_data)["is_first"] = true;
578 (*current_loop_data)["is_last"] = (result->size() <= 1);
579 for (auto it = result->begin(); it != result->end(); ++it) {
580 additional_data[static_cast<std::string>(node.key)] = it.key();
581 additional_data[static_cast<std::string>(node.value)] = it.value();
582
583 (*current_loop_data)["index"] = index;
584 (*current_loop_data)["index1"] = index + 1;
585 if (index == 1) {
586 (*current_loop_data)["is_first"] = false;
587 }
588 if (index == result->size() - 1) {
589 (*current_loop_data)["is_last"] = true;
590 }
591
592 node.body.accept(*this);
593 ++index;
594 }
595
596 additional_data[static_cast<std::string>(node.key)].clear();
597 additional_data[static_cast<std::string>(node.value)].clear();
598 if (!(*current_loop_data)["parent"].empty()) {
599 *current_loop_data = std::move((*current_loop_data)["parent"]);
600 } else {
601 current_loop_data = &additional_data["loop"];
602 }
603 }
604
605 void visit(const IfStatementNode& node) override {
606 const auto result = eval_expression_list(node.condition);
607 if (truthy(result.get())) {
608 node.true_statement.accept(*this);
609 } else if (node.has_false_statement) {
610 node.false_statement.accept(*this);
611 }
612 }
613
614 void visit(const IncludeStatementNode& node) override {
615 auto sub_renderer = Renderer(config, template_storage, function_storage);
616 const auto included_template_it = template_storage.find(node.file);
617 if (included_template_it != template_storage.end()) {
618 sub_renderer.render_to(*output_stream, included_template_it->second, *data_input, &additional_data);
619 } else if (config.throw_at_missing_includes) {
620 throw_renderer_error("include '" + node.file + "' not found", node);
621 }
622 }
623
624 void visit(const ExtendsStatementNode& node) override {
625 const auto included_template_it = template_storage.find(node.file);
626 if (included_template_it != template_storage.end()) {
627 const Template* parent_template = &included_template_it->second;
628 render_to(*output_stream, *parent_template, *data_input, &additional_data);
629 break_rendering = true;
630 } else if (config.throw_at_missing_includes) {
631 throw_renderer_error("extends '" + node.file + "' not found", node);
632 }
633 }
634
635 void visit(const BlockStatementNode& node) override {
636 const size_t old_level = current_level;
637 current_level = 0;
638 current_template = template_stack.front();
639 const auto block_it = current_template->block_storage.find(node.name);
640 if (block_it != current_template->block_storage.end()) {
641 block_statement_stack.emplace_back(&node);
642 block_it->second->block.accept(*this);
643 block_statement_stack.pop_back();
644 }
645 current_level = old_level;
646 current_template = template_stack.back();
647 }
648
649 void visit(const SetStatementNode& node) override {
650 std::string ptr = node.key;
651 replace_substring(ptr, ".", "/");
652 ptr = "/" + ptr;
653 additional_data[json::json_pointer(ptr)] = *eval_expression_list(node.expression);
654 }
655
656public:
657 explicit Renderer(const RenderConfig& config, const TemplateStorage& template_storage, const FunctionStorage& function_storage)
658 : config(config), template_storage(template_storage), function_storage(function_storage) {}
659
660 void render_to(std::ostream& os, const Template& tmpl, const json& data, json* loop_data = nullptr) {
661 output_stream = &os;
662 current_template = &tmpl;
663 data_input = &data;
664 if (loop_data != nullptr) {
665 additional_data = *loop_data;
666 current_loop_data = &additional_data["loop"];
667 }
668
669 template_stack.emplace_back(current_template);
670 current_template->root.accept(*this);
671
672 data_tmp_stack.clear();
673 }
674};
675
676} // namespace inja
677
678#endif // INCLUDE_INJA_RENDERER_HPP_
Base node class for the abstract syntax tree (AST).
Definition node.hpp:60
Definition node.hpp:70
Definition node.hpp:349
Definition node.hpp:112
Definition node.hpp:255
Definition node.hpp:92
Definition node.hpp:338
Definition node.hpp:285
Definition node.hpp:296
Definition node.hpp:274
Definition node.hpp:135
Class for builtin functions and user-defined callbacks.
Definition function_storage.hpp:22
Definition node.hpp:309
Definition node.hpp:327
Definition node.hpp:101
Definition node.hpp:35
Class for rendering a Template with data.
Definition renderer.hpp:50
Definition node.hpp:362
Definition node.hpp:267
Definition node.hpp:81
Class for render configuration.
Definition config.hpp:76
Definition exceptions.hpp:33
Definition exceptions.hpp:10
The main inja Template.
Definition template.hpp:16