LCOV - code coverage report
Current view: top level - extra/webserver/pug - SPPugTemplate.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 746 813 91.8 %
Date: 2024-05-12 00:16:13 Functions: 61 61 100.0 %

          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             : }

Generated by: LCOV version 1.14