Line data Source code
1 : /**
2 : Copyright (c) 2024 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "SPPugTemplate.h"
24 : #include "SPPugContext.h"
25 : #include "SPPugToken.h"
26 :
27 : namespace STAPPLER_VERSIONIZED stappler::pug {
28 :
29 : struct TemplateRender {
30 : template <typename T>
31 : using Vector = memory::PoolInterface::VectorType<T>;
32 : using String = memory::PoolInterface::StringType;
33 : using StringStream = memory::PoolInterface::StringStreamType;
34 :
35 : TemplateRender(Template::Chunk *root, bool pretty);
36 :
37 : bool renderControlToken(Token *, Template::ChunkType, bool allowEmpty);
38 :
39 : bool renderToken(Token *);
40 : bool renderTokenTree(Token *);
41 :
42 : bool renderComment(Token *);
43 : bool renderPlainText(Token *);
44 : bool renderLine(Token *, bool interpolated = false);
45 : bool renderTag(Token *, Token *nextTok, bool interpolated = false);
46 : Token * renderTagAttributes(Token *);
47 :
48 : bool makeStartIndent(bool validate);
49 :
50 : bool isCommand(const StringView &) const;
51 : bool isSelfClosing(const StringView &) const;
52 : bool isInlineTag(const StringView &) const;
53 :
54 : bool pushOutput(Expression *, Template::ChunkType);
55 :
56 : Template::Chunk *runCode(Token *);
57 : bool runCode(Expression *, Token::Type);
58 :
59 : Template::Chunk * flushBuffer(Template::ChunkType = Template::HtmlEntity);
60 : void end();
61 :
62 : bool pushChunk(Template::Chunk *);
63 : bool popChunk();
64 :
65 : Vector<StringView> &extractIncludes();
66 :
67 : StringStream _buffer;
68 : Template::Chunk *_root = nullptr;
69 : bool _pretty = false;
70 : bool _started = false;
71 : size_t _indentation = 0;
72 :
73 : Template::Chunk *_current = nullptr;
74 :
75 : size_t _stackSize = 0;
76 : std::array<Template::Chunk *, 16> _chunkStack;
77 : Vector<StringView> _includes;
78 : };
79 :
80 475 : TemplateRender::TemplateRender(Template::Chunk *root, bool pretty) : _root(root), _pretty(pretty), _current(root) { }
81 :
82 2350 : bool TemplateRender::renderControlToken(Token *tok, Template::ChunkType type, bool allowEmpty) {
83 2350 : if (allowEmpty || tok->child) {
84 2350 : flushBuffer();
85 2350 : _current->chunks.emplace_back(new Template::Chunk{type, String(), tok->expression});
86 2350 : if (type == Template::ControlMixin) {
87 150 : if (tok->expression->op == Expression::Call && tok->expression->left->isToken) {
88 125 : _current->chunks.back()->value = tok->expression->left->value.getString();
89 25 : } else if (tok->expression->op == Expression::NoOp && tok->expression->isToken) {
90 25 : _current->chunks.back()->value = tok->expression->value.getString();
91 : } else {
92 0 : return false;
93 : }
94 : }
95 2350 : pushChunk(_current->chunks.back());
96 2350 : auto ret = renderTokenTree(tok->child);
97 2350 : popChunk();
98 2350 : return ret;
99 : }
100 0 : return false;
101 : }
102 :
103 31050 : bool TemplateRender::renderToken(Token *tok) {
104 31050 : switch (tok->type) {
105 475 : case Token::Root:
106 475 : renderTokenTree(tok->child);
107 475 : break;
108 175 : case Token::LineData:
109 175 : return renderLine(tok, true);
110 : break;
111 15550 : case Token::Line:
112 15550 : switch (tok->child->type) {
113 9250 : case Token::LineData:
114 9250 : return renderLine(tok->child);
115 : break;
116 1650 : case Token::LinePiped:
117 1650 : if (tok->prev && tok->prev->child && tok->prev->child->type == Token::LinePiped) {
118 775 : _buffer << '\n';
119 775 : if (_pretty) {
120 2050 : for (size_t i = 0; i < _indentation; ++ i) {
121 1325 : _buffer << '\t';
122 : }
123 : }
124 : }
125 1650 : return renderTokenTree(tok->child->child);
126 : break;
127 625 : case Token::LinePlainText:
128 625 : if (_pretty || (tok->prev && (tok->prev->type == Token::LinePlainText || (
129 125 : tok->prev->child && tok->prev->child->type == Token::LinePlainText)))) {
130 525 : _buffer << '\n';
131 : }
132 1400 : for (size_t i = 0; i < _indentation; ++ i) {
133 775 : _buffer << '\t';
134 : }
135 625 : if (tok->child->child) {
136 525 : renderTokenTree(tok->child->child);
137 : } else {
138 100 : _buffer << tok->child->data;
139 : }
140 625 : if (tok->child->next) {
141 100 : ++ _indentation;
142 100 : auto ret = renderTokenTree(tok->child->next);
143 100 : -- _indentation;
144 100 : return ret;
145 : }
146 525 : return true;
147 : break;
148 325 : case Token::LineComment:
149 325 : return renderComment(tok->child);
150 : break;
151 25 : case Token::LineDot:
152 25 : return renderPlainText(tok->child);
153 : break;
154 3200 : case Token::LineOut:
155 : case Token::LineCode:
156 3200 : if (tok->child->child) {
157 3200 : if (auto chunk = runCode(tok->child->child)) {
158 3200 : pushChunk(chunk);
159 3200 : renderTokenTree(tok->child->next);
160 3200 : popChunk();
161 : }
162 : }
163 3200 : break;
164 25 : case Token::LineCodeBlock:
165 25 : return renderTokenTree(tok->child->child);
166 : break;
167 450 : case Token::MixinCall:
168 450 : flushBuffer();
169 900 : _current->chunks.emplace_back(new Template::Chunk{Template::MixinCall, tok->child->data.str<memory::PoolInterface>(), nullptr});
170 450 : if (tok->child->child && tok->child->child->type == Token::MixinArgs) {
171 350 : _current->chunks.back()->expr = tok->child->child->expression;
172 : }
173 450 : return true;
174 : break;
175 0 : default:
176 0 : break;
177 : }
178 3200 : break;
179 3425 : case Token::PlainText:
180 3425 : _buffer << tok->data;
181 3425 : break;
182 :
183 2525 : case Token::OutputEscaped: pushOutput(tok->expression, Template::OutputEscaped); break;
184 100 : case Token::OutputUnescaped: pushOutput(tok->expression, Template::OutputUnescaped); break;
185 25 : case Token::Code: runCode(tok->expression, tok->type); break;
186 :
187 75 : case Token::ControlCase: return renderControlToken(tok, Template::ControlCase, false); break;
188 200 : case Token::ControlWhen: return renderControlToken(tok, Template::ControlWhen, true); break;
189 75 : case Token::ControlDefault: return renderControlToken(tok, Template::ControlDefault, false); break;
190 :
191 1450 : case Token::ControlIf: return renderControlToken(tok, Template::ControlIf, false); break;
192 25 : case Token::ControlUnless: return renderControlToken(tok, Template::ControlUnless, false); break;
193 150 : case Token::ControlElseIf: return renderControlToken(tok, Template::ControlElseIf, false); break;
194 200 : case Token::ControlElse: return renderControlToken(tok, Template::ControlElse, false); break;
195 25 : case Token::ControlWhile: return renderControlToken(tok, Template::ControlWhile, false); break;
196 150 : case Token::ControlMixin: return renderControlToken(tok, Template::ControlMixin, false); break;
197 :
198 400 : case Token::ControlEach:
199 400 : if (tok->child && tok->child->next) {
200 400 : flushBuffer();
201 400 : auto var = tok->child->data;
202 400 : _current->chunks.emplace_back(new Template::Chunk{Template::ControlEach, String(var.data(), var.size()), tok->expression});
203 400 : pushChunk(_current->chunks.back());
204 400 : auto ret = renderTokenTree(tok->child);
205 400 : popChunk();
206 400 : return ret;
207 : }
208 0 : break;
209 75 : case Token::ControlEachPair:
210 75 : if (tok->child && tok->child->next && tok->child->next->next) {
211 75 : flushBuffer();
212 75 : StringStream str;
213 75 : str << tok->child->data << " " << tok->child->next->data;
214 75 : _current->chunks.emplace_back(new Template::Chunk{Template::ControlEachPair, str.str(), tok->expression});
215 75 : pushChunk(_current->chunks.back());
216 75 : auto ret = renderTokenTree(tok->child);
217 75 : popChunk();
218 75 : return ret;
219 75 : }
220 0 : break;
221 475 : case Token::Include:
222 475 : flushBuffer();
223 475 : _current->chunks.emplace_back(new Template::Chunk{Template::Include, String(tok->data.data(), tok->data.size()), nullptr, _indentation});
224 475 : _includes.emplace_back(StringView(_current->chunks.back()->value));
225 475 : return true;
226 : break;
227 725 : case Token::Doctype:
228 725 : if (tok->data == "html") {
229 275 : _buffer << "<!DOCTYPE html>\n";
230 450 : } else if (tok->data == "xml") {
231 50 : _buffer << "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
232 400 : } else if (tok->data == "transitional") {
233 50 : _buffer << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
234 350 : } else if (tok->data == "strict") {
235 50 : _buffer << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
236 300 : } else if (tok->data == "frameset") {
237 50 : _buffer << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">\n";
238 250 : } else if (tok->data == "1.1") {
239 50 : _buffer << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n";
240 200 : } else if (tok->data == "basic") {
241 50 : _buffer << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd\">\n";
242 150 : } else if (tok->data == "mobile") {
243 50 : _buffer << "<!DOCTYPE html PUBLIC \"-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd\">\n";
244 100 : } else if (tok->data == "plist") {
245 50 : _buffer << "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
246 : } else {
247 50 : _buffer << "<!DOCTYPE " << tok->data << ">\n";
248 : }
249 725 : flushBuffer();
250 725 : return true;
251 : break;
252 4750 : default: break;
253 : }
254 14500 : return false;
255 : }
256 :
257 16775 : bool TemplateRender::renderTokenTree(Token *tok) {
258 16775 : bool ret = false;
259 47350 : while (tok != NULL) {
260 30575 : if (renderToken(tok)) {
261 11075 : ret = true;
262 : }
263 30575 : tok = tok->next;
264 : }
265 16775 : return ret;
266 : }
267 :
268 325 : bool TemplateRender::renderComment(Token *tok) {
269 325 : switch (tok->child->type) {
270 150 : case Token::CommentHtml:
271 150 : makeStartIndent(true);
272 150 : _buffer << "<!--";
273 150 : if (tok->child->next) {
274 75 : renderTokenTree(tok->child->next);
275 : }
276 150 : if (tok->next) {
277 75 : if (_pretty) { ++ _indentation; }
278 75 : renderTokenTree(tok->next);
279 75 : if (_pretty) { -- _indentation; }
280 75 : if (_pretty) {
281 75 : _buffer << "\n";
282 75 : for (size_t i = 0; i < _indentation; ++ i) {
283 0 : _buffer << '\t';
284 : }
285 : }
286 : }
287 150 : _buffer << "-->";
288 150 : return true;
289 : break;
290 175 : case Token::CommentTemplate:
291 175 : break;
292 0 : default:
293 0 : break;
294 : }
295 175 : return false;
296 : }
297 :
298 25 : bool TemplateRender::renderPlainText(Token *tok) {
299 25 : return renderTokenTree(tok->next);
300 : }
301 :
302 9425 : bool TemplateRender::renderLine(Token *tok, bool interpolated) {
303 9425 : switch (tok->child->type) {
304 9425 : case Token::Tag:
305 9425 : if (isCommand(tok->child->data)) {
306 0 : return false;
307 : } else {
308 9425 : if (renderTag(tok->child, interpolated ? nullptr : tok->next, interpolated)) {
309 6600 : return true;
310 : }
311 : }
312 2825 : break;
313 0 : default:
314 0 : break;
315 : }
316 2825 : return false;
317 : }
318 :
319 9425 : bool TemplateRender::renderTag(Token *tok, Token *nextTok, bool interpolated) {
320 9425 : auto shouldIndent = interpolated ? false : makeStartIndent(!isInlineTag(tok->data));
321 :
322 9425 : bool isOutput = tok->data.empty() && tok->next && tok->next->type == Token::TagTrailingEq;
323 :
324 9425 : Token *tagEval = nullptr;
325 :
326 9425 : if (!isOutput) {
327 9375 : flushBuffer();
328 :
329 9375 : _buffer << "<" << (tok->data.empty() ? StringView("div") : tok->data); // it's a tag
330 9375 : auto tagChunk = flushBuffer(Template::HtmlTag);
331 :
332 : // read attributes
333 9375 : tagEval = renderTagAttributes(tok->next);
334 9375 : if ((tagEval && tagEval->type == Token::TagTrailingSlash) || isSelfClosing(tok->data)) {
335 1050 : _buffer << "/>";
336 1050 : tagChunk->type = Template::HtmlInlineTag;
337 1050 : return true;
338 : }
339 :
340 8325 : _buffer << ">";
341 : } else {
342 50 : tagEval = tok->next->next;
343 : }
344 :
345 8375 : if (_pretty) { ++ _indentation; }
346 :
347 8375 : bool finalizeIndent = false;
348 8375 : if (tagEval) {
349 4900 : renderTokenTree(tagEval);
350 : }
351 :
352 8375 : if (nextTok) {
353 2900 : if (renderTokenTree(nextTok)) {
354 1850 : finalizeIndent = true;
355 : }
356 : }
357 :
358 8375 : if (_pretty) { -- _indentation; }
359 :
360 8375 : if (shouldIndent && finalizeIndent) {
361 1700 : _buffer << "\n";
362 4025 : for (size_t i = 0; i < _indentation; ++ i) {
363 2325 : _buffer << '\t';
364 : }
365 : }
366 :
367 8375 : if (!isOutput) {
368 8325 : flushBuffer();
369 8325 : _buffer << "</" << (tok->data.empty() ? StringView("div") : tok->data) << ">";
370 8325 : flushBuffer(Template::HtmlTag);
371 : }
372 :
373 8375 : return shouldIndent;
374 : }
375 :
376 9375 : Token * TemplateRender::renderTagAttributes(Token *tok) {
377 9375 : bool hasClasses = false;
378 9375 : StringStream classes;
379 9375 : StringView id;
380 :
381 4450 : auto writeAttrName = [] (Token *tok) -> String {
382 4450 : StringStream out;
383 9000 : while (tok && tok->type == Token::PlainText) {
384 4550 : out << tok->data;
385 4550 : tok = tok->next;
386 : }
387 8900 : return out.str();
388 4450 : };
389 :
390 9725 : auto pushAttribute = [&, this] (const StringView &name, Expression *expression, bool esc) {
391 4450 : if (!expression) {
392 50 : _buffer << " " << name;
393 50 : return;
394 : }
395 :
396 4400 : if (expression->isConst()) {
397 3575 : Context::printAttrVar(name, *expression, [&] (StringView str) {
398 17950 : _buffer << str;
399 17950 : }, esc);
400 : } else {
401 825 : flushBuffer();
402 2475 : _current->chunks.emplace_back(new Template::Chunk{esc ? Template::AttributeEscaped : Template::AttributeUnescaped,
403 1650 : String(name.data(), name.size()), expression});
404 : }
405 9375 : };
406 :
407 2800 : auto processAttrList = [&] (Token *tok) {
408 7250 : while (tok && (tok->type == Token::AttrPairEscaped || tok->type == Token::AttrPairUnescaped)) {
409 4450 : if (auto nameTok = tok->child) {
410 4450 : auto name = writeAttrName(nameTok->child);
411 4450 : if (!name.empty()) {
412 4450 : if (auto valueTok = nameTok->next) {
413 4400 : if (valueTok->expression) {
414 4400 : pushAttribute(name, valueTok->expression, tok->type == Token::AttrPairEscaped);
415 : }
416 : } else {
417 50 : pushAttribute(name, nullptr, tok->type == Token::AttrPairEscaped);
418 : }
419 : }
420 4450 : }
421 :
422 4450 : tok = tok->next;
423 : }
424 2800 : };
425 :
426 125 : auto processAttrExpr = [&, this] (Expression *expr) {
427 50 : if (expr) {
428 50 : if (expr->isConst()) {
429 25 : Context::printAttrExpr(*expr, [&] (StringView str) {
430 125 : _buffer << str;
431 125 : });
432 : } else {
433 25 : flushBuffer();
434 25 : _current->chunks.emplace_back(new Template::Chunk{Template::AttributeList,
435 25 : String(), expr});
436 : }
437 : }
438 50 : };
439 :
440 9375 : bool stop = false;
441 20375 : while (!stop && tok) {
442 11000 : switch (tok->type) {
443 2800 : case Token::TagClassNote:
444 2800 : if (!tok->data.empty()) {
445 2800 : if (hasClasses) { classes << " "; } else { hasClasses = true; }
446 2800 : classes << tok->data;
447 : }
448 2800 : break;
449 475 : case Token::TagIdNote:
450 475 : id = tok->data;
451 475 : break;
452 2800 : case Token::TagAttrList:
453 2800 : processAttrList(tok->child);
454 2800 : break;
455 50 : case Token::TagAttrExpr:
456 50 : processAttrExpr(tok->expression);
457 50 : break;
458 4875 : default:
459 4875 : stop = true;
460 4875 : break;
461 : }
462 :
463 11000 : if (!stop) {
464 6125 : tok = tok->next;
465 : }
466 : }
467 :
468 9375 : if (!id.empty()) {
469 475 : _buffer << " id=\"" << id << "\"";
470 : }
471 9375 : if (hasClasses) {
472 2700 : _buffer << " class=\"" << classes.weak() << "\"";
473 : }
474 :
475 9375 : return tok;
476 9375 : }
477 :
478 9400 : bool TemplateRender::makeStartIndent(bool validate) {
479 9400 : bool shouldIndent = _pretty && validate;
480 9400 : if (shouldIndent && _started) {
481 6300 : _buffer << "\n";
482 15100 : for (size_t i = 0; i < _indentation; ++ i) {
483 8800 : _buffer << '\t';
484 : }
485 6300 : } else {
486 3100 : _started = true;
487 : }
488 9400 : return shouldIndent;
489 : }
490 :
491 9425 : bool TemplateRender::isCommand(const StringView &r) const {
492 9425 : return false;
493 : }
494 :
495 9350 : bool TemplateRender::isSelfClosing(const StringView &r) const {
496 18700 : if (r == "area" || r == "base" || r == "br" || r == "col"
497 9325 : || r == "command" || r == "embed" || r == "hr" || r == "img"
498 9325 : || r == "input" || r == "keygen" || r == "link" || r == "meta"
499 18700 : || r == "param" || r == "source" || r == "track" || r == "wbr") {
500 1025 : return true;
501 : }
502 8325 : return false;
503 : }
504 :
505 9250 : bool TemplateRender::isInlineTag(const StringView &r) const {
506 17650 : if (r == "a" || r == "abbr" || r == "acronym" || r == "b"
507 8400 : || r == "br" || r == "code" || r == "em" || r == "font"
508 8175 : || r == "i" || r == "img" || r == "ins" || r == "kbd"
509 8175 : || r == "map" || r == "samp" || r == "small" || r == "span"
510 17650 : || r == "strong" || r == "sub" || r == "sup") {
511 2250 : return true;
512 : }
513 7000 : return false;
514 : }
515 :
516 2625 : bool TemplateRender::pushOutput(Expression *expr, Template::ChunkType type) {
517 2625 : if (!expr) {
518 0 : return false;
519 : }
520 :
521 2625 : if (expr->isConst()) {
522 350 : return Context::printConstExpr(*expr, [&] (StringView str) {
523 275 : _buffer << str;
524 175 : }, type == Template::OutputEscaped);
525 : } else {
526 2450 : flushBuffer();
527 4900 : _current->chunks.emplace_back(new Template::Chunk{type, String(), expr});
528 : }
529 2450 : return false;
530 : }
531 :
532 3200 : Template::Chunk *TemplateRender::runCode(Token *tok) {
533 3200 : Template::Chunk *ret = nullptr;
534 6400 : while (tok) {
535 3200 : if (runCode(tok->expression, tok->type)) {
536 3200 : ret = _current->chunks.back();
537 : }
538 3200 : tok = tok->next;
539 : }
540 3200 : return ret;
541 : }
542 :
543 3225 : bool TemplateRender::runCode(Expression *expr, Token::Type type) {
544 3225 : if (!expr) {
545 0 : return false;
546 : }
547 :
548 3225 : flushBuffer();
549 3225 : switch (type) {
550 25 : case Token::OutputEscaped:
551 50 : _current->chunks.emplace_back(new Template::Chunk{Template::OutputEscaped, String(), expr});
552 25 : break;
553 25 : case Token::OutputUnescaped:
554 50 : _current->chunks.emplace_back(new Template::Chunk{Template::OutputUnescaped, String(), expr});
555 25 : break;
556 3175 : default:
557 6350 : _current->chunks.emplace_back(new Template::Chunk{Template::Code, String(), expr});
558 3175 : break;
559 : }
560 3225 : return true;
561 : }
562 :
563 58925 : Template::Chunk * TemplateRender::flushBuffer(Template::ChunkType type) {
564 58925 : if (!_buffer.empty() && _current) {
565 34875 : auto c = new Template::Chunk{type, _buffer.str(), nullptr};
566 34875 : _current->chunks.emplace_back(c);
567 34875 : _buffer.clear();
568 34875 : return c;
569 : }
570 24050 : return nullptr;
571 : }
572 :
573 475 : void TemplateRender::end() { }
574 :
575 6025 : bool TemplateRender::pushChunk(Template::Chunk *c) {
576 6025 : if (_stackSize == _chunkStack.size()) {
577 0 : return false;
578 : }
579 :
580 6025 : flushBuffer();
581 6025 : _chunkStack[_stackSize] = _current;
582 6025 : ++ _stackSize;
583 6025 : _current = c;
584 6025 : return true;
585 : }
586 :
587 6025 : bool TemplateRender::popChunk() {
588 6025 : if (_stackSize > 0) {
589 6025 : flushBuffer();
590 6025 : -- _stackSize;
591 6025 : _current = _chunkStack[_stackSize];
592 6025 : return true;
593 : }
594 0 : return false;
595 : }
596 :
597 475 : TemplateRender::Vector<StringView> &TemplateRender::extractIncludes() {
598 475 : return _includes;
599 : }
600 :
601 75 : Template::Options Template::Options::getDefault() {
602 75 : return Options();
603 : }
604 :
605 75 : Template::Options Template::Options::getPretty() {
606 75 : return Options().setFlags({ Pretty });
607 : }
608 :
609 100 : Template::Options &Template::Options::setFlags(std::initializer_list<Flags> &&il) {
610 200 : for (auto &it : il) { flags.set(toInt(it)); }
611 100 : return *this;
612 : }
613 :
614 25 : Template::Options &Template::Options::clearFlags(std::initializer_list<Flags> &&il) {
615 50 : for (auto &it : il) { flags.reset(toInt(it)); }
616 25 : return *this;
617 : }
618 :
619 39375 : bool Template::Options::hasFlag(Flags f) const {
620 39375 : return flags.test(toInt(f));
621 : }
622 :
623 50 : Template *Template::read(const StringView &str, const Options &opts, const Callback<void(StringView)> &err) {
624 50 : auto p = memory::pool::create(memory::pool::acquire());
625 50 : return read(p, str, opts, err);
626 : }
627 :
628 525 : Template *Template::read(memory::pool_t *p, const StringView &str, const Options &opts, const Callback<void(StringView)> &err) {
629 525 : memory::pool::push(p);
630 525 : auto ret = new (p) Template(p, str, opts, err);
631 525 : memory::pool::pop();
632 525 : return ret;
633 : }
634 :
635 525 : Template::Template(memory::pool_t *p, const StringView &str, const Options &opts, const Callback<void(StringView)> &err)
636 525 : : _pool(p), _lexer(str, err), _opts(opts) {
637 525 : if (_lexer) {
638 475 : TemplateRender renderer(&_root, opts.hasFlag(Options::Pretty));
639 475 : renderer.renderToken(&_lexer.root);
640 475 : renderer.flushBuffer();
641 475 : renderer.end();
642 475 : _includes = move(renderer.extractIncludes());
643 475 : }
644 525 : }
645 :
646 25 : bool Template::run(Context &ctx, const OutStream &out) const {
647 25 : return run(ctx, out, _opts);
648 : }
649 :
650 475 : bool Template::run(Context &ctx, const OutStream &out, const Options &opts) const {
651 475 : RunContext rctx;
652 475 : rctx.tagStack.reserve(8);
653 475 : rctx.opts = opts;
654 950 : return run(ctx, out, rctx);
655 475 : }
656 :
657 1175 : bool Template::run(Context &ctx, const OutStream &out, RunContext &rctx) const {
658 1175 : rctx.templateStack.emplace_back(this);
659 1175 : auto ret = runChunk(_root, ctx, out, rctx);
660 1175 : if (ret) {
661 1950 : while (!rctx.tagStack.empty() && rctx.tagStack.back()->type == VirtualTag) {
662 775 : out << rctx.tagStack.back()->value;
663 775 : rctx.tagStack.pop_back();
664 : }
665 1175 : if (!rctx.tagStack.empty()) {
666 25 : return false;
667 : }
668 : }
669 1150 : rctx.templateStack.pop_back();
670 1150 : return ret;
671 : }
672 :
673 8325 : static void Template_describeChunk(const Template::OutStream &stream, const Template::Chunk &chunk, size_t depth) {
674 27225 : for (size_t i = 0; i < depth; ++ i) { stream << " "; }
675 8325 : switch (chunk.type) {
676 75 : case Template::Block:
677 75 : stream << "<block> of " << chunk.chunks.size() << "\n";
678 75 : break;
679 2950 : case Template::HtmlTag: stream << "<html-tag> " << chunk.value << "\n"; break;
680 175 : case Template::HtmlInlineTag: stream << "<html-inline-tag> " << chunk.value << "\n"; break;
681 3400 : case Template::HtmlEntity: stream << "<html-entity>\n"; break;
682 425 : case Template::OutputEscaped: stream << "<escaped output expression>\n"; break;
683 50 : case Template::OutputUnescaped: stream << "<unescaped output expression>\n"; break;
684 125 : case Template::AttributeEscaped: stream << "<escaped attribute expression>\n"; break;
685 25 : case Template::AttributeUnescaped: stream << "<unescaped attribute expression>\n"; break;
686 25 : case Template::AttributeList: stream << "<attribute list>\n"; break;
687 425 : case Template::Code:
688 425 : stream << "<code>";
689 425 : if (auto n = chunk.chunks.size()) {
690 0 : stream << " of " << n;
691 : }
692 425 : stream << "\n";
693 425 : break;
694 50 : case Template::ControlCase: stream << "<case>\n"; break;
695 125 : case Template::ControlWhen: stream << "<when>\n"; break;
696 50 : case Template::ControlDefault: stream << "<default>\n"; break;
697 25 : case Template::ControlIf: stream << "<if>\n"; break;
698 25 : case Template::ControlUnless: stream << "<unless>\n"; break;
699 50 : case Template::ControlElse: stream << "<else>\n"; break;
700 25 : case Template::ControlElseIf: stream << "<elseif>\n"; break;
701 :
702 75 : case Template::ControlEach: stream << "<each> " << chunk.value << "\n"; break;
703 25 : case Template::ControlEachPair: stream << "<each> " << chunk.value << "\n"; break;
704 25 : case Template::ControlWhile: stream << "<while>\n"; break;
705 75 : case Template::Include: stream << "<include> " << chunk.value << "\n"; break;
706 50 : case Template::ControlMixin: stream << "<mixin> " << chunk.value << "\n"; break;
707 50 : case Template::MixinCall: stream << "<mixin-call> " << chunk.value << "\n"; break;
708 0 : case Template::VirtualTag: stream << "<virtual-tag> " << chunk.value << "\n"; break;
709 : }
710 16575 : for (auto &it : chunk.chunks) {
711 8250 : Template_describeChunk(stream, *it, depth + 1);
712 : }
713 8325 : }
714 :
715 75 : void Template::describe(const OutStream &stream, bool tokens) const {
716 75 : stream << "\n";
717 75 : if (tokens) {
718 75 : stream << "Tokens:\n";
719 75 : _lexer.root.describe(stream);
720 75 : stream << "\n";
721 : }
722 75 : Template_describeChunk(stream, _root, 0);
723 75 : stream << "\n";
724 75 : }
725 :
726 850 : static void Template_readMixinArgs(Vector<Expression *> &vars, Expression *expr) {
727 850 : if (expr) {
728 575 : if (expr->op == Expression::Comma) {
729 25 : Template_readMixinArgs(vars, expr->left);
730 25 : Template_readMixinArgs(vars, expr->right);
731 : } else {
732 550 : vars.emplace_back(expr);
733 : }
734 : }
735 850 : }
736 :
737 8050 : bool Template::runChunk(const Chunk &chunk, Context &exec, const Callback<void(StringView)> &out, RunContext &tagStack) const {
738 : auto onError = [&] (const StringView &err) SP_COVERAGE_TRIVIAL {
739 : out << "<!-- " << "Context error: " << err << " -->";
740 8050 : };
741 :
742 5350 : auto runIf = [&] (auto &it) -> bool {
743 5350 : Context::VarScope scope;
744 5350 : exec.pushVarScope(scope);
745 :
746 5350 : bool success = false;
747 5350 : bool r = true;
748 5350 : bool allowElseIf = (*it)->type == ControlIf;
749 :
750 22550 : auto tryExec = [&, this] () -> bool {
751 11250 : if (auto var = exec.exec(*(*it)->expr, out, true)) {
752 5625 : auto &v = var.readValue();
753 5625 : auto val = (v.getType() == Value::Type::DICTIONARY || v.getType() == Value::Type::ARRAY) ? !v.empty() : v.asBool();
754 5625 : if ((!allowElseIf && !val) || val) {
755 3400 : if (!runChunk(**it, exec, out, tagStack)) {
756 0 : r = false;
757 : }
758 3400 : return true;
759 : } else {
760 2225 : ++ it;
761 : }
762 : } else {
763 0 : ++ it;
764 0 : r = false;
765 : }
766 2225 : return false;
767 : };
768 :
769 :
770 5350 : if ((*it)->type == ControlIf || (*it)->type == ControlUnless) {
771 5350 : if (tryExec()) {
772 3250 : success = true;
773 3250 : ++ it;
774 : }
775 : }
776 :
777 5350 : if (!success && allowElseIf) {
778 2225 : while (it != chunk.chunks.end() && (*it)->type == ControlElseIf) {
779 275 : if (tryExec()) {
780 150 : success = true;
781 150 : ++ it;
782 150 : break;
783 : }
784 : }
785 : }
786 :
787 5350 : if (!success && it != chunk.chunks.end() && (*it)->type == ControlElse) {
788 325 : if (!runChunk(**it, exec, out, tagStack)) {
789 0 : success = true;
790 0 : r = false;
791 : }
792 325 : ++ it;
793 : }
794 :
795 6525 : while (it != chunk.chunks.end() && ((*it)->type == ControlElse || (allowElseIf && (*it)->type == ControlElseIf))) {
796 1175 : ++ it;
797 : }
798 :
799 5350 : exec.popVarScope();
800 :
801 5350 : return r;
802 5350 : };
803 :
804 800 : auto runEachBody = [&] (auto &it, const auto &cb) -> bool {
805 800 : bool r = true;
806 800 : Context::VarScope scope;
807 800 : exec.pushVarScope(scope);
808 :
809 800 : auto next = it + 1;
810 800 : bool hasElse = (next != chunk.chunks.end() && (*next)->type == ControlElse);
811 800 : bool runElse = false;
812 :
813 1600 : if (auto var = exec.exec(*(*it)->expr, out)) {
814 8100 : auto runWithVar = [&, this] (const Value &val, bool isConst) -> bool {
815 800 : if (val.isArray()) {
816 675 : size_t i = 0;
817 675 : if (val.size() > 0) {
818 2325 : for (auto &v_it : val.asArray()) {
819 1675 : cb(Value(uint32_t(i)), &v_it, isConst);
820 2500 : if (!runChunk(**it, exec, out, tagStack) && _opts.hasFlag(Options::StopOnError)) { return false; }
821 1675 : scope.namedVars.clear();
822 1675 : ++ i;
823 : }
824 : } else {
825 25 : runElse = true;
826 : }
827 125 : } else if (val.isDictionary()) {
828 100 : if (val.size() > 0) {
829 550 : for (auto &v_it : val.asDict()) {
830 450 : cb(Value(v_it.first), &v_it.second, isConst);
831 450 : if (!runChunk(**it, exec, out, tagStack) && _opts.hasFlag(Options::StopOnError)) { return false; }
832 450 : scope.namedVars.clear();
833 : }
834 : } else {
835 0 : runElse = true;
836 : }
837 : } else {
838 25 : if (!hasElse) {
839 25 : if (val) {
840 25 : cb(Value(0), &val, isConst);
841 25 : if (!runChunk(**it, exec, out, tagStack) && _opts.hasFlag(Options::StopOnError)) { return false; }
842 : }
843 : } else {
844 0 : runElse = true;
845 : }
846 : }
847 800 : return true;
848 : };
849 :
850 800 : if (Value * mut = var.getMutable()) {
851 475 : r = runWithVar(*mut, false);
852 325 : } else if (const Value &rv = var.readValue()) {
853 325 : r = runWithVar(rv, true);
854 : }
855 : } else {
856 0 : r = false;
857 0 : runElse = true;
858 : }
859 :
860 800 : if (hasElse) {
861 75 : ++ it;
862 75 : if (runElse) {
863 25 : if (!runChunk(**it, exec, out, tagStack) && _opts.hasFlag(Options::StopOnError)) { return false; }
864 : }
865 : }
866 :
867 800 : exec.popVarScope();
868 800 : ++ it;
869 800 : return r;
870 8850 : };
871 :
872 700 : auto runEach = [&] (auto &it) -> bool {
873 700 : StringView varName((*it)->value);
874 700 : if (varName.empty()) {
875 0 : return false;
876 : }
877 :
878 700 : return runEachBody(it, [&] (Value &&it, const Value *val, bool isConst) {
879 2400 : exec.set(varName, isConst, val);
880 700 : });
881 8050 : };
882 :
883 100 : auto runEachPair = [&] (auto &it) -> bool {
884 100 : StringView varFirst;
885 100 : StringView varSecond;
886 :
887 100 : string::split((*it)->value, " ", [&] (const StringView &val) {
888 200 : if (varFirst.empty()) {
889 100 : varFirst = val;
890 : } else {
891 100 : varSecond = val;
892 : }
893 : });
894 :
895 100 : if (varFirst.empty() || varSecond.empty()) {
896 0 : return false;
897 : }
898 :
899 100 : return runEachBody(it, [&] (Value &&it, const Value *val, bool isConst) {
900 550 : exec.set(varFirst, isConst, val);
901 450 : exec.set(varSecond, move(it));
902 100 : });
903 8050 : };
904 :
905 125 : auto runWhile = [&, this] (const Chunk &ch) {
906 25 : Context::VarScope scope;
907 : while (true) {
908 125 : if (auto var = exec.exec(*ch.expr, out)) {
909 125 : if (var.readValue().asBool()) {
910 100 : scope.namedVars.clear();
911 100 : scope.mixins.clear();
912 100 : exec.pushVarScope(scope);
913 100 : if (!runChunk(ch, exec, out, tagStack)) {
914 0 : exec.popVarScope();
915 0 : return false;
916 : }
917 100 : exec.popVarScope();
918 : } else {
919 25 : return true;
920 : }
921 : } else {
922 0 : break;
923 125 : }
924 100 : }
925 0 : return false;
926 25 : };
927 :
928 1600 : auto runMixinChunk = [&, this] (const Chunk &ch) -> bool {
929 800 : auto mixin = exec.getMixin(ch.value);
930 800 : if (!mixin) {
931 0 : onError(string::toString<memory::PoolInterface>("Mixin with name ", ch.value, " is not found"));
932 0 : if (_opts.hasFlag(Options::StopOnError)) { return false; }
933 0 : return true;
934 : }
935 :
936 800 : Vector<Expression *> vars;
937 800 : Template_readMixinArgs(vars, ch.expr);
938 :
939 800 : if (vars.size() < mixin->required) {
940 0 : onError(string::toString<memory::PoolInterface>("Not enough arguments for mixin: ", ch.value));
941 0 : if (_opts.hasFlag(Options::StopOnError)) { return false; }
942 : }
943 :
944 800 : Context::VarScope scope;
945 :
946 1425 : for (size_t i = 0; i < mixin->args.size(); ++ i) {
947 625 : auto &it = mixin->args[i];
948 625 : auto n = scope.namedVars.emplace(it.first.str<memory::PoolInterface>(), VarStorage()).first;
949 625 : if (i < vars.size()) {
950 550 : n->second.assign(exec.exec(*vars.at(i), out));
951 75 : } else if (it.second) {
952 75 : n->second.assign(exec.exec(*it.second, out));
953 : } else {
954 0 : onError(string::toString<memory::PoolInterface>("Invalid argument for ", it.first));
955 0 : if (_opts.hasFlag(Options::StopOnError)) { return false; }
956 : }
957 : }
958 :
959 800 : exec.pushVarScope(scope);
960 :
961 800 : if (!runChunk(*mixin->chunk, exec, out, tagStack) && _opts.hasFlag(Options::StopOnError)) { return false; }
962 :
963 800 : exec.popVarScope();
964 800 : return true;
965 800 : };
966 :
967 8050 : auto it = chunk.chunks.begin();
968 97675 : while (it != chunk.chunks.end()) {
969 89625 : auto &c = **it;
970 89625 : switch (c.type) {
971 36950 : case HtmlTag:
972 36950 : if (StringView(c.value).starts_with("</")) {
973 18500 : while (!tagStack.tagStack.empty() && tagStack.tagStack.back()->type == VirtualTag) {
974 25 : auto name = StringView(tagStack.tagStack.back()->value, 5).str<memory::PoolInterface>();
975 25 : string::apply_tolower_c(name);
976 25 : out << tagStack.tagStack.back()->value;
977 25 : if (name == "</body") {
978 0 : tagStack.withinBody = false;
979 : }
980 25 : tagStack.tagStack.pop_back();
981 25 : }
982 18475 : if (tagStack.tagStack.empty()) {
983 0 : return false;
984 : }
985 18475 : auto name = StringView(tagStack.tagStack.back()->value, 5).str<memory::PoolInterface>();
986 18475 : string::apply_tolower_c(name);
987 18475 : if (name == "<head") {
988 425 : tagStack.withinHead = false;
989 18050 : } else if (name == "<body") {
990 100 : tagStack.withinBody = false;
991 : }
992 18475 : out << c.value;
993 18475 : tagStack.tagStack.pop_back();
994 18475 : if (tagStack.opts.hasFlag(Options::LineFeeds) && !tagStack.tagStack.empty()) {
995 0 : out << "\n";
996 : }
997 36950 : } else if (!StringView(c.value).ends_with("/>")) {
998 18475 : if (tagStack.opts.hasFlag(Options::LineFeeds) && !tagStack.tagStack.empty()) {
999 0 : out << "\n";
1000 : }
1001 18475 : auto name = StringView(c.value, 5).str<memory::PoolInterface>();
1002 18475 : string::apply_tolower_c(name);
1003 18475 : if (tagStack.tagStack.empty() && name != "<html") {
1004 425 : out << "<html>";
1005 850 : tagStack.tagStack.push_back(new Template::Chunk{VirtualTag, String("</html>"), nullptr});
1006 : }
1007 18475 : if (name == "<html") {
1008 50 : tagStack.tagStack.push_back(&c);
1009 : } else {
1010 18425 : if (name == "<head") {
1011 425 : tagStack.withinHead = true;
1012 18000 : } else if (!tagStack.withinHead) {
1013 17200 : if (name == "<body") {
1014 100 : tagStack.withinBody = true;
1015 17100 : } else if (!tagStack.withinBody) {
1016 375 : out << "<body>";
1017 750 : tagStack.tagStack.push_back(new Template::Chunk{VirtualTag, String("</body>"), nullptr});
1018 375 : tagStack.withinBody = true;
1019 : }
1020 : }
1021 18425 : tagStack.tagStack.push_back(&c);
1022 : }
1023 18475 : out << c.value;
1024 18475 : } else {
1025 0 : if (tagStack.opts.hasFlag(Options::LineFeeds) && !tagStack.tagStack.empty()) {
1026 0 : out << "\n";
1027 : }
1028 0 : out << c.value;
1029 : }
1030 36950 : ++ it;
1031 36950 : break;
1032 1150 : case HtmlInlineTag: {
1033 1150 : auto name = StringView(c.value, 5).str<memory::PoolInterface>();
1034 1150 : string::apply_tolower_c(name);
1035 1150 : if (tagStack.tagStack.empty() && name != "<html") {
1036 0 : out << "<html>";
1037 0 : tagStack.tagStack.push_back(new Template::Chunk{VirtualTag, String("</html>"), nullptr});
1038 : }
1039 1150 : if (name != "<head" && !tagStack.withinHead) {
1040 300 : if (name != "<body" && !tagStack.withinBody) {
1041 0 : out << "<body>";
1042 0 : tagStack.tagStack.push_back(new Template::Chunk{VirtualTag, String("</body>"), nullptr});
1043 0 : tagStack.withinBody = true;
1044 : }
1045 : }
1046 :
1047 1150 : if (tagStack.opts.hasFlag(Options::LineFeeds) && !tagStack.tagStack.empty()) {
1048 0 : out << "\n";
1049 : }
1050 1150 : out << c.value;
1051 1150 : ++ it;
1052 1150 : break;
1053 1150 : }
1054 32350 : case HtmlEntity:
1055 32350 : out << c.value;
1056 32350 : ++ it;
1057 32350 : break;
1058 7175 : case OutputEscaped:
1059 : case OutputUnescaped:
1060 7175 : if (!exec.print(*c.expr, out, c.type == OutputEscaped) && _opts.hasFlag(Options::StopOnError)) { return false; }
1061 7175 : ++ it;
1062 7175 : break;
1063 1550 : case AttributeEscaped:
1064 : case AttributeUnescaped:
1065 1550 : if (!exec.printAttr(c.value, *c.expr, out, c.type == AttributeEscaped) && _opts.hasFlag(Options::StopOnError)) { return false; }
1066 1550 : ++ it;
1067 1550 : break;
1068 25 : case AttributeList:
1069 25 : if (!exec.printAttrExprList(*c.expr, out) && _opts.hasFlag(Options::StopOnError)) { return false; }
1070 25 : ++ it;
1071 25 : break;
1072 0 : case Block:
1073 : case ControlWhen:
1074 : case ControlDefault:
1075 0 : runChunk(c, exec, out, tagStack);
1076 0 : ++ it;
1077 0 : break;
1078 1875 : case Code:
1079 1875 : if (!exec.exec(*c.expr, out) && _opts.hasFlag(Options::StopOnError)) { return false; }
1080 1875 : ++ it;
1081 1875 : break;
1082 75 : case ControlCase:
1083 75 : if (!runCase(c, exec, out, tagStack) && _opts.hasFlag(Options::StopOnError)) { return false; }
1084 75 : ++ it;
1085 75 : break;
1086 5325 : case ControlIf:
1087 5325 : if (!runIf(it) && _opts.hasFlag(Options::StopOnError)) { return false; }
1088 5325 : break;
1089 25 : case ControlUnless:
1090 25 : if (!runIf(it) && _opts.hasFlag(Options::StopOnError)) { return false; }
1091 25 : break;
1092 700 : case ControlEach:
1093 700 : if (!runEach(it) && _opts.hasFlag(Options::StopOnError)) { return false; }
1094 700 : break;
1095 100 : case ControlEachPair:
1096 100 : if (!runEachPair(it) && _opts.hasFlag(Options::StopOnError)) { return false; }
1097 100 : break;
1098 25 : case ControlWhile:
1099 25 : if (!runWhile(**it) && _opts.hasFlag(Options::StopOnError)) { return false; }
1100 25 : ++ it;
1101 25 : break;
1102 725 : case Include:
1103 725 : if (_opts.hasFlag(Options::Pretty)) {
1104 700 : String stream;
1105 1400 : if (!exec.runInclude((*it)->value, [&] (StringView str) {
1106 25 : stream.append(str.data(), str.size());
1107 1400 : }, tagStack) && _opts.hasFlag(Options::StopOnError)) { return false; }
1108 700 : pushWithPrettyFilter(stream, (*it)->indent, out);
1109 700 : } else {
1110 25 : if (!exec.runInclude((*it)->value, out, tagStack) && _opts.hasFlag(Options::StopOnError)) { return false; }
1111 : }
1112 725 : ++ it;
1113 725 : break;
1114 775 : case ControlMixin:
1115 775 : ++ it;
1116 775 : if (c.expr->op == Expression::Call && c.expr->left->isToken) {
1117 500 : if (!exec.setMixin(c.expr->left->value.getString(), &c)) {
1118 0 : onError(string::toString<memory::PoolInterface>("Invalid mixin declaration: ", c.expr->left->value.getString()));
1119 : }
1120 275 : } else if (c.expr->op == Expression::NoOp && c.expr->isToken) {
1121 275 : if (!exec.setMixin(c.expr->value.getString(), &c)) {
1122 0 : onError(string::toString<memory::PoolInterface>("Invalid mixin declaration: ", c.expr->value.getString()));
1123 : }
1124 : }
1125 775 : break;
1126 800 : case MixinCall:
1127 800 : if (!runMixinChunk(c)) {
1128 0 : return false;
1129 : }
1130 800 : ++ it;
1131 800 : break;
1132 0 : case ControlElseIf:
1133 : case ControlElse:
1134 : case VirtualTag:
1135 : // should not be in this context
1136 0 : return false;
1137 : break;
1138 : }
1139 : }
1140 :
1141 8050 : return true;
1142 : }
1143 :
1144 75 : bool Template::runCase(const Chunk &chunk, Context &exec, const OutStream &out, RunContext &tagStack) const {
1145 100 : auto runWhenChunk = [&, this] (auto it) -> bool {
1146 50 : if ((*it)->chunks.size() > 0) {
1147 25 : return runChunk(**it, exec, out, tagStack);
1148 : } else {
1149 25 : ++ it;
1150 25 : while (it != chunk.chunks.end() && (*it)->type == Template::ControlWhen) {
1151 25 : if ((*it)->chunks.size() > 0) {
1152 25 : return runChunk(**it, exec, out, tagStack);
1153 : }
1154 : }
1155 : }
1156 0 : return false;
1157 75 : };
1158 :
1159 100 : auto perform = [&, this] () -> bool {
1160 75 : if (auto var = exec.exec(*chunk.expr, out)) {
1161 75 : if (auto val = var.readValue()) {
1162 75 : const Template::Chunk *def = nullptr;
1163 75 : auto it = chunk.chunks.begin();
1164 225 : while (it != chunk.chunks.end()) {
1165 200 : switch ((*it)->type) {
1166 175 : case Template::ControlWhen:
1167 175 : if (auto v = exec.exec(*(*it)->expr, out)) {
1168 175 : auto &v2 = v.readValue();
1169 175 : if (val == v2) {
1170 50 : return runWhenChunk(it);
1171 : }
1172 : } else {
1173 0 : return false;
1174 175 : }
1175 125 : break;
1176 25 : case Template::ControlDefault: def = *it; break;
1177 0 : default: break;
1178 : }
1179 150 : ++ it;
1180 : }
1181 25 : if (def) {
1182 25 : return runChunk(*def, exec, out, tagStack);
1183 : } else {
1184 0 : return true;
1185 : }
1186 75 : }
1187 75 : }
1188 0 : return false;
1189 75 : };
1190 :
1191 75 : Context::VarScope scope;
1192 75 : exec.pushVarScope(scope);
1193 75 : auto ret = perform();
1194 75 : exec.popVarScope();
1195 75 : return ret;
1196 75 : }
1197 :
1198 700 : void Template::pushWithPrettyFilter(StringView r, size_t indent, const Callback<void(StringView)> &out) const {
1199 700 : out << '\n';
1200 925 : while (!r.empty()) {
1201 675 : for (size_t i = 0; i < indent; ++ i) {
1202 450 : out << '\t';
1203 : }
1204 225 : out << r.readUntil<StringView::Chars<'\r', '\n'>>();
1205 225 : out << r.readChars<StringView::Chars<'\r'>>();
1206 225 : if (r.is('\n')) {
1207 225 : out << '\n';
1208 225 : ++ r;
1209 : }
1210 : }
1211 700 : }
1212 :
1213 : }
|