1 //===-- Mustache.cpp ------------------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 #include "llvm/Support/Mustache.h" 9 #include "llvm/ADT/SmallVector.h" 10 #include "llvm/Support/raw_ostream.h" 11 #include <sstream> 12 13 using namespace llvm; 14 using namespace llvm::mustache; 15 16 namespace { 17 18 using Accessor = SmallVector<std::string>; 19 20 static bool isFalsey(const json::Value &V) { 21 return V.getAsNull() || (V.getAsBoolean() && !V.getAsBoolean().value()) || 22 (V.getAsArray() && V.getAsArray()->empty()); 23 } 24 25 static bool isContextFalsey(const json::Value *V) { 26 // A missing context (represented by a nullptr) is defined as falsey. 27 if (!V) 28 return true; 29 return isFalsey(*V); 30 } 31 32 static Accessor splitMustacheString(StringRef Str) { 33 // We split the mustache string into an accessor. 34 // For example: 35 // "a.b.c" would be split into {"a", "b", "c"} 36 // We make an exception for a single dot which 37 // refers to the current context. 38 Accessor Tokens; 39 if (Str == ".") { 40 Tokens.emplace_back(Str); 41 return Tokens; 42 } 43 while (!Str.empty()) { 44 StringRef Part; 45 std::tie(Part, Str) = Str.split("."); 46 Tokens.emplace_back(Part.trim()); 47 } 48 return Tokens; 49 } 50 } // namespace 51 52 namespace llvm::mustache { 53 54 class Token { 55 public: 56 enum class Type { 57 Text, 58 Variable, 59 Partial, 60 SectionOpen, 61 SectionClose, 62 InvertSectionOpen, 63 UnescapeVariable, 64 Comment, 65 }; 66 67 Token(std::string Str) 68 : TokenType(Type::Text), RawBody(std::move(Str)), TokenBody(RawBody), 69 AccessorValue({}), Indentation(0) {}; 70 71 Token(std::string RawBody, std::string TokenBody, char Identifier) 72 : RawBody(std::move(RawBody)), TokenBody(std::move(TokenBody)), 73 Indentation(0) { 74 TokenType = getTokenType(Identifier); 75 if (TokenType == Type::Comment) 76 return; 77 StringRef AccessorStr(this->TokenBody); 78 if (TokenType != Type::Variable) 79 AccessorStr = AccessorStr.substr(1); 80 AccessorValue = splitMustacheString(StringRef(AccessorStr).trim()); 81 } 82 83 Accessor getAccessor() const { return AccessorValue; } 84 85 Type getType() const { return TokenType; } 86 87 void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; } 88 89 size_t getIndentation() const { return Indentation; } 90 91 static Type getTokenType(char Identifier) { 92 switch (Identifier) { 93 case '#': 94 return Type::SectionOpen; 95 case '/': 96 return Type::SectionClose; 97 case '^': 98 return Type::InvertSectionOpen; 99 case '!': 100 return Type::Comment; 101 case '>': 102 return Type::Partial; 103 case '&': 104 return Type::UnescapeVariable; 105 default: 106 return Type::Variable; 107 } 108 } 109 110 Type TokenType; 111 // RawBody is the original string that was tokenized. 112 std::string RawBody; 113 // TokenBody is the original string with the identifier removed. 114 std::string TokenBody; 115 Accessor AccessorValue; 116 size_t Indentation; 117 }; 118 119 using EscapeMap = DenseMap<char, std::string>; 120 121 class ASTNode { 122 public: 123 enum Type { 124 Root, 125 Text, 126 Partial, 127 Variable, 128 UnescapeVariable, 129 Section, 130 InvertSection, 131 }; 132 133 ASTNode(llvm::StringMap<AstPtr> &Partials, llvm::StringMap<Lambda> &Lambdas, 134 llvm::StringMap<SectionLambda> &SectionLambdas, EscapeMap &Escapes) 135 : Partials(Partials), Lambdas(Lambdas), SectionLambdas(SectionLambdas), 136 Escapes(Escapes), Ty(Type::Root), Parent(nullptr), 137 ParentContext(nullptr) {} 138 139 ASTNode(std::string Body, ASTNode *Parent, llvm::StringMap<AstPtr> &Partials, 140 llvm::StringMap<Lambda> &Lambdas, 141 llvm::StringMap<SectionLambda> &SectionLambdas, EscapeMap &Escapes) 142 : Partials(Partials), Lambdas(Lambdas), SectionLambdas(SectionLambdas), 143 Escapes(Escapes), Ty(Type::Text), Body(std::move(Body)), Parent(Parent), 144 ParentContext(nullptr) {} 145 146 // Constructor for Section/InvertSection/Variable/UnescapeVariable Nodes 147 ASTNode(Type Ty, Accessor Accessor, ASTNode *Parent, 148 llvm::StringMap<AstPtr> &Partials, llvm::StringMap<Lambda> &Lambdas, 149 llvm::StringMap<SectionLambda> &SectionLambdas, EscapeMap &Escapes) 150 : Partials(Partials), Lambdas(Lambdas), SectionLambdas(SectionLambdas), 151 Escapes(Escapes), Ty(Ty), Parent(Parent), 152 AccessorValue(std::move(Accessor)), ParentContext(nullptr) {} 153 154 void addChild(AstPtr Child) { Children.emplace_back(std::move(Child)); }; 155 156 void setRawBody(std::string NewBody) { RawBody = std::move(NewBody); }; 157 158 void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; }; 159 160 void render(const llvm::json::Value &Data, llvm::raw_ostream &OS); 161 162 private: 163 void renderLambdas(const llvm::json::Value &Contexts, llvm::raw_ostream &OS, 164 Lambda &L); 165 166 void renderSectionLambdas(const llvm::json::Value &Contexts, 167 llvm::raw_ostream &OS, SectionLambda &L); 168 169 void renderPartial(const llvm::json::Value &Contexts, llvm::raw_ostream &OS, 170 ASTNode *Partial); 171 172 void renderChild(const llvm::json::Value &Context, llvm::raw_ostream &OS); 173 174 const llvm::json::Value *findContext(); 175 176 StringMap<AstPtr> &Partials; 177 StringMap<Lambda> &Lambdas; 178 StringMap<SectionLambda> &SectionLambdas; 179 EscapeMap &Escapes; 180 Type Ty; 181 size_t Indentation = 0; 182 std::string RawBody; 183 std::string Body; 184 ASTNode *Parent; 185 // TODO: switch implementation to SmallVector<T> 186 std::vector<AstPtr> Children; 187 const Accessor AccessorValue; 188 const llvm::json::Value *ParentContext; 189 }; 190 191 // A wrapper for arena allocator for ASTNodes 192 AstPtr createRootNode(llvm::StringMap<AstPtr> &Partials, 193 llvm::StringMap<Lambda> &Lambdas, 194 llvm::StringMap<SectionLambda> &SectionLambdas, 195 EscapeMap &Escapes) { 196 return std::make_unique<ASTNode>(Partials, Lambdas, SectionLambdas, Escapes); 197 } 198 199 AstPtr createNode(ASTNode::Type T, Accessor A, ASTNode *Parent, 200 llvm::StringMap<AstPtr> &Partials, 201 llvm::StringMap<Lambda> &Lambdas, 202 llvm::StringMap<SectionLambda> &SectionLambdas, 203 EscapeMap &Escapes) { 204 return std::make_unique<ASTNode>(T, std::move(A), Parent, Partials, Lambdas, 205 SectionLambdas, Escapes); 206 } 207 208 AstPtr createTextNode(std::string Body, ASTNode *Parent, 209 llvm::StringMap<AstPtr> &Partials, 210 llvm::StringMap<Lambda> &Lambdas, 211 llvm::StringMap<SectionLambda> &SectionLambdas, 212 EscapeMap &Escapes) { 213 return std::make_unique<ASTNode>(std::move(Body), Parent, Partials, Lambdas, 214 SectionLambdas, Escapes); 215 } 216 217 // Function to check if there is meaningful text behind. 218 // We determine if a token has meaningful text behind 219 // if the right of previous token contains anything that is 220 // not a newline. 221 // For example: 222 // "Stuff {{#Section}}" (returns true) 223 // vs 224 // "{{#Section}} \n" (returns false) 225 // We make an exception for when previous token is empty 226 // and the current token is the second token. 227 // For example: 228 // "{{#Section}}" 229 bool hasTextBehind(size_t Idx, const ArrayRef<Token> &Tokens) { 230 if (Idx == 0) 231 return true; 232 233 size_t PrevIdx = Idx - 1; 234 if (Tokens[PrevIdx].getType() != Token::Type::Text) 235 return true; 236 237 const Token &PrevToken = Tokens[PrevIdx]; 238 StringRef TokenBody = StringRef(PrevToken.RawBody).rtrim(" \r\t\v"); 239 return !TokenBody.ends_with("\n") && !(TokenBody.empty() && Idx == 1); 240 } 241 242 // Function to check if there's no meaningful text ahead. 243 // We determine if a token has text ahead if the left of previous 244 // token does not start with a newline. 245 bool hasTextAhead(size_t Idx, const ArrayRef<Token> &Tokens) { 246 if (Idx >= Tokens.size() - 1) 247 return true; 248 249 size_t NextIdx = Idx + 1; 250 if (Tokens[NextIdx].getType() != Token::Type::Text) 251 return true; 252 253 const Token &NextToken = Tokens[NextIdx]; 254 StringRef TokenBody = StringRef(NextToken.RawBody).ltrim(" "); 255 return !TokenBody.starts_with("\r\n") && !TokenBody.starts_with("\n"); 256 } 257 258 bool requiresCleanUp(Token::Type T) { 259 // We must clean up all the tokens that could contain child nodes. 260 return T == Token::Type::SectionOpen || T == Token::Type::InvertSectionOpen || 261 T == Token::Type::SectionClose || T == Token::Type::Comment || 262 T == Token::Type::Partial; 263 } 264 265 // Adjust next token body if there is no text ahead. 266 // For example: 267 // The template string 268 // "{{! Comment }} \nLine 2" 269 // would be considered as no text ahead and should be rendered as 270 // " Line 2" 271 void stripTokenAhead(SmallVectorImpl<Token> &Tokens, size_t Idx) { 272 Token &NextToken = Tokens[Idx + 1]; 273 StringRef NextTokenBody = NextToken.TokenBody; 274 // Cut off the leading newline which could be \n or \r\n. 275 if (NextTokenBody.starts_with("\r\n")) 276 NextToken.TokenBody = NextTokenBody.substr(2).str(); 277 else if (NextTokenBody.starts_with("\n")) 278 NextToken.TokenBody = NextTokenBody.substr(1).str(); 279 } 280 281 // Adjust previous token body if there no text behind. 282 // For example: 283 // The template string 284 // " \t{{#section}}A{{/section}}" 285 // would be considered as having no text ahead and would be render as 286 // "A" 287 // The exception for this is partial tag which requires us to 288 // keep track of the indentation once it's rendered. 289 void stripTokenBefore(SmallVectorImpl<Token> &Tokens, size_t Idx, 290 Token &CurrentToken, Token::Type CurrentType) { 291 Token &PrevToken = Tokens[Idx - 1]; 292 StringRef PrevTokenBody = PrevToken.TokenBody; 293 StringRef Unindented = PrevTokenBody.rtrim(" \r\t\v"); 294 size_t Indentation = PrevTokenBody.size() - Unindented.size(); 295 if (CurrentType != Token::Type::Partial) 296 PrevToken.TokenBody = Unindented.str(); 297 CurrentToken.setIndentation(Indentation); 298 } 299 300 // Simple tokenizer that splits the template into tokens. 301 // The mustache spec allows {{{ }}} to unescape variables, 302 // but we don't support that here. An unescape variable 303 // is represented only by {{& variable}}. 304 SmallVector<Token> tokenize(StringRef Template) { 305 SmallVector<Token> Tokens; 306 StringLiteral Open("{{"); 307 StringLiteral Close("}}"); 308 size_t Start = 0; 309 size_t DelimiterStart = Template.find(Open); 310 if (DelimiterStart == StringRef::npos) { 311 Tokens.emplace_back(Template.str()); 312 return Tokens; 313 } 314 while (DelimiterStart != StringRef::npos) { 315 if (DelimiterStart != Start) 316 Tokens.emplace_back(Template.substr(Start, DelimiterStart - Start).str()); 317 size_t DelimiterEnd = Template.find(Close, DelimiterStart); 318 if (DelimiterEnd == StringRef::npos) 319 break; 320 321 // Extract the Interpolated variable without delimiters. 322 size_t InterpolatedStart = DelimiterStart + Open.size(); 323 size_t InterpolatedEnd = DelimiterEnd - DelimiterStart - Close.size(); 324 std::string Interpolated = 325 Template.substr(InterpolatedStart, InterpolatedEnd).str(); 326 std::string RawBody = Open.str() + Interpolated + Close.str(); 327 Tokens.emplace_back(RawBody, Interpolated, Interpolated[0]); 328 Start = DelimiterEnd + Close.size(); 329 DelimiterStart = Template.find(Open, Start); 330 } 331 332 if (Start < Template.size()) 333 Tokens.emplace_back(Template.substr(Start).str()); 334 335 // Fix up white spaces for: 336 // - open sections 337 // - inverted sections 338 // - close sections 339 // - comments 340 // 341 // This loop attempts to find standalone tokens and tries to trim out 342 // the surrounding whitespace. 343 // For example: 344 // if you have the template string 345 // {{#section}} \n Example \n{{/section}} 346 // The output should would be 347 // For example: 348 // \n Example \n 349 size_t LastIdx = Tokens.size() - 1; 350 for (size_t Idx = 0, End = Tokens.size(); Idx < End; ++Idx) { 351 Token &CurrentToken = Tokens[Idx]; 352 Token::Type CurrentType = CurrentToken.getType(); 353 // Check if token type requires cleanup. 354 bool RequiresCleanUp = requiresCleanUp(CurrentType); 355 356 if (!RequiresCleanUp) 357 continue; 358 359 // We adjust the token body if there's no text behind or ahead. 360 // A token is considered to have no text ahead if the right of the previous 361 // token is a newline followed by spaces. 362 // A token is considered to have no text behind if the left of the next 363 // token is spaces followed by a newline. 364 // eg. 365 // "Line 1\n {{#section}} \n Line 2 \n {{/section}} \n Line 3" 366 bool HasTextBehind = hasTextBehind(Idx, Tokens); 367 bool HasTextAhead = hasTextAhead(Idx, Tokens); 368 369 if ((!HasTextAhead && !HasTextBehind) || (!HasTextAhead && Idx == 0)) 370 stripTokenAhead(Tokens, Idx); 371 372 if ((!HasTextBehind && !HasTextAhead) || (!HasTextBehind && Idx == LastIdx)) 373 stripTokenBefore(Tokens, Idx, CurrentToken, CurrentType); 374 } 375 return Tokens; 376 } 377 378 // Custom stream to escape strings. 379 class EscapeStringStream : public raw_ostream { 380 public: 381 explicit EscapeStringStream(llvm::raw_ostream &WrappedStream, 382 EscapeMap &Escape) 383 : Escape(Escape), WrappedStream(WrappedStream) { 384 SetUnbuffered(); 385 } 386 387 protected: 388 void write_impl(const char *Ptr, size_t Size) override { 389 llvm::StringRef Data(Ptr, Size); 390 for (char C : Data) { 391 auto It = Escape.find(C); 392 if (It != Escape.end()) 393 WrappedStream << It->getSecond(); 394 else 395 WrappedStream << C; 396 } 397 } 398 399 uint64_t current_pos() const override { return WrappedStream.tell(); } 400 401 private: 402 EscapeMap &Escape; 403 llvm::raw_ostream &WrappedStream; 404 }; 405 406 // Custom stream to add indentation used to for rendering partials. 407 class AddIndentationStringStream : public raw_ostream { 408 public: 409 explicit AddIndentationStringStream(llvm::raw_ostream &WrappedStream, 410 size_t Indentation) 411 : Indentation(Indentation), WrappedStream(WrappedStream) { 412 SetUnbuffered(); 413 } 414 415 protected: 416 void write_impl(const char *Ptr, size_t Size) override { 417 llvm::StringRef Data(Ptr, Size); 418 SmallString<0> Indent; 419 Indent.resize(Indentation, ' '); 420 for (char C : Data) { 421 WrappedStream << C; 422 if (C == '\n') 423 WrappedStream << Indent; 424 } 425 } 426 427 uint64_t current_pos() const override { return WrappedStream.tell(); } 428 429 private: 430 size_t Indentation; 431 llvm::raw_ostream &WrappedStream; 432 }; 433 434 class Parser { 435 public: 436 Parser(StringRef TemplateStr) : TemplateStr(TemplateStr) {} 437 438 AstPtr parse(llvm::StringMap<AstPtr> &Partials, 439 llvm::StringMap<Lambda> &Lambdas, 440 llvm::StringMap<SectionLambda> &SectionLambdas, 441 EscapeMap &Escapes); 442 443 private: 444 void parseMustache(ASTNode *Parent, llvm::StringMap<AstPtr> &Partials, 445 llvm::StringMap<Lambda> &Lambdas, 446 llvm::StringMap<SectionLambda> &SectionLambdas, 447 EscapeMap &Escapes); 448 449 SmallVector<Token> Tokens; 450 size_t CurrentPtr; 451 StringRef TemplateStr; 452 }; 453 454 AstPtr Parser::parse(llvm::StringMap<AstPtr> &Partials, 455 llvm::StringMap<Lambda> &Lambdas, 456 llvm::StringMap<SectionLambda> &SectionLambdas, 457 EscapeMap &Escapes) { 458 Tokens = tokenize(TemplateStr); 459 CurrentPtr = 0; 460 AstPtr RootNode = createRootNode(Partials, Lambdas, SectionLambdas, Escapes); 461 parseMustache(RootNode.get(), Partials, Lambdas, SectionLambdas, Escapes); 462 return RootNode; 463 } 464 465 void Parser::parseMustache(ASTNode *Parent, llvm::StringMap<AstPtr> &Partials, 466 llvm::StringMap<Lambda> &Lambdas, 467 llvm::StringMap<SectionLambda> &SectionLambdas, 468 EscapeMap &Escapes) { 469 470 while (CurrentPtr < Tokens.size()) { 471 Token CurrentToken = Tokens[CurrentPtr]; 472 CurrentPtr++; 473 Accessor A = CurrentToken.getAccessor(); 474 AstPtr CurrentNode; 475 476 switch (CurrentToken.getType()) { 477 case Token::Type::Text: { 478 CurrentNode = createTextNode(std::move(CurrentToken.TokenBody), Parent, 479 Partials, Lambdas, SectionLambdas, Escapes); 480 Parent->addChild(std::move(CurrentNode)); 481 break; 482 } 483 case Token::Type::Variable: { 484 CurrentNode = createNode(ASTNode::Variable, std::move(A), Parent, 485 Partials, Lambdas, SectionLambdas, Escapes); 486 Parent->addChild(std::move(CurrentNode)); 487 break; 488 } 489 case Token::Type::UnescapeVariable: { 490 CurrentNode = createNode(ASTNode::UnescapeVariable, std::move(A), Parent, 491 Partials, Lambdas, SectionLambdas, Escapes); 492 Parent->addChild(std::move(CurrentNode)); 493 break; 494 } 495 case Token::Type::Partial: { 496 CurrentNode = createNode(ASTNode::Partial, std::move(A), Parent, Partials, 497 Lambdas, SectionLambdas, Escapes); 498 CurrentNode->setIndentation(CurrentToken.getIndentation()); 499 Parent->addChild(std::move(CurrentNode)); 500 break; 501 } 502 case Token::Type::SectionOpen: { 503 CurrentNode = createNode(ASTNode::Section, A, Parent, Partials, Lambdas, 504 SectionLambdas, Escapes); 505 size_t Start = CurrentPtr; 506 parseMustache(CurrentNode.get(), Partials, Lambdas, SectionLambdas, 507 Escapes); 508 const size_t End = CurrentPtr - 1; 509 std::string RawBody; 510 for (std::size_t I = Start; I < End; I++) 511 RawBody += Tokens[I].RawBody; 512 CurrentNode->setRawBody(std::move(RawBody)); 513 Parent->addChild(std::move(CurrentNode)); 514 break; 515 } 516 case Token::Type::InvertSectionOpen: { 517 CurrentNode = createNode(ASTNode::InvertSection, A, Parent, Partials, 518 Lambdas, SectionLambdas, Escapes); 519 size_t Start = CurrentPtr; 520 parseMustache(CurrentNode.get(), Partials, Lambdas, SectionLambdas, 521 Escapes); 522 const size_t End = CurrentPtr - 1; 523 std::string RawBody; 524 for (size_t Idx = Start; Idx < End; Idx++) 525 RawBody += Tokens[Idx].RawBody; 526 CurrentNode->setRawBody(std::move(RawBody)); 527 Parent->addChild(std::move(CurrentNode)); 528 break; 529 } 530 case Token::Type::Comment: 531 break; 532 case Token::Type::SectionClose: 533 return; 534 } 535 } 536 } 537 void toMustacheString(const json::Value &Data, raw_ostream &OS) { 538 switch (Data.kind()) { 539 case json::Value::Null: 540 return; 541 case json::Value::Number: { 542 auto Num = *Data.getAsNumber(); 543 std::ostringstream SS; 544 SS << Num; 545 OS << SS.str(); 546 return; 547 } 548 case json::Value::String: { 549 auto Str = *Data.getAsString(); 550 OS << Str.str(); 551 return; 552 } 553 554 case json::Value::Array: { 555 auto Arr = *Data.getAsArray(); 556 if (Arr.empty()) 557 return; 558 [[fallthrough]]; 559 } 560 case json::Value::Object: 561 case json::Value::Boolean: { 562 llvm::json::OStream JOS(OS, 2); 563 JOS.value(Data); 564 break; 565 } 566 } 567 } 568 569 void ASTNode::render(const json::Value &CurrentCtx, raw_ostream &OS) { 570 // Set the parent context to the incoming context so that we 571 // can walk up the context tree correctly in findContext(). 572 ParentContext = &CurrentCtx; 573 const json::Value *ContextPtr = Ty == Root ? ParentContext : findContext(); 574 575 switch (Ty) { 576 case Root: 577 renderChild(CurrentCtx, OS); 578 return; 579 case Text: 580 OS << Body; 581 return; 582 case Partial: { 583 auto Partial = Partials.find(AccessorValue[0]); 584 if (Partial != Partials.end()) 585 renderPartial(CurrentCtx, OS, Partial->getValue().get()); 586 return; 587 } 588 case Variable: { 589 auto Lambda = Lambdas.find(AccessorValue[0]); 590 if (Lambda != Lambdas.end()) { 591 renderLambdas(CurrentCtx, OS, Lambda->getValue()); 592 } else if (ContextPtr) { 593 EscapeStringStream ES(OS, Escapes); 594 toMustacheString(*ContextPtr, ES); 595 } 596 return; 597 } 598 case UnescapeVariable: { 599 auto Lambda = Lambdas.find(AccessorValue[0]); 600 if (Lambda != Lambdas.end()) { 601 renderLambdas(CurrentCtx, OS, Lambda->getValue()); 602 } else if (ContextPtr) { 603 toMustacheString(*ContextPtr, OS); 604 } 605 return; 606 } 607 case Section: { 608 auto SectionLambda = SectionLambdas.find(AccessorValue[0]); 609 bool IsLambda = SectionLambda != SectionLambdas.end(); 610 611 if (IsLambda) { 612 renderSectionLambdas(CurrentCtx, OS, SectionLambda->getValue()); 613 return; 614 } 615 616 if (isContextFalsey(ContextPtr)) 617 return; 618 619 if (const json::Array *Arr = ContextPtr->getAsArray()) { 620 for (const json::Value &V : *Arr) 621 renderChild(V, OS); 622 return; 623 } 624 renderChild(*ContextPtr, OS); 625 return; 626 } 627 case InvertSection: { 628 bool IsLambda = SectionLambdas.contains(AccessorValue[0]); 629 if (isContextFalsey(ContextPtr) && !IsLambda) { 630 // The context for the children remains unchanged from the parent's, so 631 // we pass this node's original incoming context. 632 renderChild(CurrentCtx, OS); 633 } 634 return; 635 } 636 } 637 llvm_unreachable("Invalid ASTNode type"); 638 } 639 640 const json::Value *ASTNode::findContext() { 641 // The mustache spec allows for dot notation to access nested values 642 // a single dot refers to the current context. 643 // We attempt to find the JSON context in the current node, if it is not 644 // found, then we traverse the parent nodes to find the context until we 645 // reach the root node or the context is found. 646 if (AccessorValue.empty()) 647 return nullptr; 648 if (AccessorValue[0] == ".") 649 return ParentContext; 650 651 const json::Object *CurrentContext = ParentContext->getAsObject(); 652 StringRef CurrentAccessor = AccessorValue[0]; 653 ASTNode *CurrentParent = Parent; 654 655 while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) { 656 if (CurrentParent->Ty != Root) { 657 CurrentContext = CurrentParent->ParentContext->getAsObject(); 658 CurrentParent = CurrentParent->Parent; 659 continue; 660 } 661 return nullptr; 662 } 663 const json::Value *Context = nullptr; 664 for (auto [Idx, Acc] : enumerate(AccessorValue)) { 665 const json::Value *CurrentValue = CurrentContext->get(Acc); 666 if (!CurrentValue) 667 return nullptr; 668 if (Idx < AccessorValue.size() - 1) { 669 CurrentContext = CurrentValue->getAsObject(); 670 if (!CurrentContext) 671 return nullptr; 672 } else { 673 Context = CurrentValue; 674 } 675 } 676 return Context; 677 } 678 679 void ASTNode::renderChild(const json::Value &Contexts, llvm::raw_ostream &OS) { 680 for (AstPtr &Child : Children) 681 Child->render(Contexts, OS); 682 } 683 684 void ASTNode::renderPartial(const json::Value &Contexts, llvm::raw_ostream &OS, 685 ASTNode *Partial) { 686 AddIndentationStringStream IS(OS, Indentation); 687 Partial->render(Contexts, IS); 688 } 689 690 void ASTNode::renderLambdas(const json::Value &Contexts, llvm::raw_ostream &OS, 691 Lambda &L) { 692 json::Value LambdaResult = L(); 693 std::string LambdaStr; 694 raw_string_ostream Output(LambdaStr); 695 toMustacheString(LambdaResult, Output); 696 Parser P = Parser(LambdaStr); 697 AstPtr LambdaNode = P.parse(Partials, Lambdas, SectionLambdas, Escapes); 698 699 EscapeStringStream ES(OS, Escapes); 700 if (Ty == Variable) { 701 LambdaNode->render(Contexts, ES); 702 return; 703 } 704 LambdaNode->render(Contexts, OS); 705 } 706 707 void ASTNode::renderSectionLambdas(const json::Value &Contexts, 708 llvm::raw_ostream &OS, SectionLambda &L) { 709 json::Value Return = L(RawBody); 710 if (isFalsey(Return)) 711 return; 712 std::string LambdaStr; 713 raw_string_ostream Output(LambdaStr); 714 toMustacheString(Return, Output); 715 Parser P = Parser(LambdaStr); 716 AstPtr LambdaNode = P.parse(Partials, Lambdas, SectionLambdas, Escapes); 717 LambdaNode->render(Contexts, OS); 718 } 719 720 void Template::render(const json::Value &Data, llvm::raw_ostream &OS) { 721 Tree->render(Data, OS); 722 } 723 724 void Template::registerPartial(std::string Name, std::string Partial) { 725 Parser P = Parser(Partial); 726 AstPtr PartialTree = P.parse(Partials, Lambdas, SectionLambdas, Escapes); 727 Partials.insert(std::make_pair(Name, std::move(PartialTree))); 728 } 729 730 void Template::registerLambda(std::string Name, Lambda L) { Lambdas[Name] = L; } 731 732 void Template::registerLambda(std::string Name, SectionLambda L) { 733 SectionLambdas[Name] = L; 734 } 735 736 void Template::overrideEscapeCharacters(EscapeMap E) { Escapes = std::move(E); } 737 738 Template::Template(StringRef TemplateStr) { 739 Parser P = Parser(TemplateStr); 740 Tree = P.parse(Partials, Lambdas, SectionLambdas, Escapes); 741 // The default behavior is to escape html entities. 742 const EscapeMap HtmlEntities = {{'&', "&"}, 743 {'<', "<"}, 744 {'>', ">"}, 745 {'"', """}, 746 {'\'', "'"}}; 747 overrideEscapeCharacters(HtmlEntities); 748 } 749 750 Template::Template(Template &&Other) noexcept 751 : Partials(std::move(Other.Partials)), Lambdas(std::move(Other.Lambdas)), 752 SectionLambdas(std::move(Other.SectionLambdas)), 753 Escapes(std::move(Other.Escapes)), Tree(std::move(Other.Tree)) {} 754 755 Template::~Template() = default; 756 757 Template &Template::operator=(Template &&Other) noexcept { 758 if (this != &Other) { 759 Partials = std::move(Other.Partials); 760 Lambdas = std::move(Other.Lambdas); 761 SectionLambdas = std::move(Other.SectionLambdas); 762 Escapes = std::move(Other.Escapes); 763 Tree = std::move(Other.Tree); 764 Other.Tree = nullptr; 765 } 766 return *this; 767 } 768 } // namespace llvm::mustache 769