| //===-- Mustache.cpp ------------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| #include "llvm/Support/Mustache.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include <sstream> |
| |
| using namespace llvm; |
| using namespace llvm::mustache; |
| |
| namespace { |
| |
| using Accessor = SmallVector<std::string>; |
| |
| static bool isFalsey(const json::Value &V) { |
| return V.getAsNull() || (V.getAsBoolean() && !V.getAsBoolean().value()) || |
| (V.getAsArray() && V.getAsArray()->empty()); |
| } |
| |
| static Accessor splitMustacheString(StringRef Str) { |
| // We split the mustache string into an accessor. |
| // For example: |
| // "a.b.c" would be split into {"a", "b", "c"} |
| // We make an exception for a single dot which |
| // refers to the current context. |
| Accessor Tokens; |
| if (Str == ".") { |
| Tokens.emplace_back(Str); |
| return Tokens; |
| } |
| while (!Str.empty()) { |
| StringRef Part; |
| std::tie(Part, Str) = Str.split("."); |
| Tokens.emplace_back(Part.trim()); |
| } |
| return Tokens; |
| } |
| } // namespace |
| |
| namespace llvm::mustache { |
| |
| class Token { |
| public: |
| enum class Type { |
| Text, |
| Variable, |
| Partial, |
| SectionOpen, |
| SectionClose, |
| InvertSectionOpen, |
| UnescapeVariable, |
| Comment, |
| }; |
| |
| Token(std::string Str) |
| : TokenType(Type::Text), RawBody(std::move(Str)), TokenBody(RawBody), |
| AccessorValue({}), Indentation(0) {}; |
| |
| Token(std::string RawBody, std::string TokenBody, char Identifier) |
| : RawBody(std::move(RawBody)), TokenBody(std::move(TokenBody)), |
| Indentation(0) { |
| TokenType = getTokenType(Identifier); |
| if (TokenType == Type::Comment) |
| return; |
| StringRef AccessorStr(this->TokenBody); |
| if (TokenType != Type::Variable) |
| AccessorStr = AccessorStr.substr(1); |
| AccessorValue = splitMustacheString(StringRef(AccessorStr).trim()); |
| } |
| |
| Accessor getAccessor() const { return AccessorValue; } |
| |
| Type getType() const { return TokenType; } |
| |
| void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; } |
| |
| size_t getIndentation() const { return Indentation; } |
| |
| static Type getTokenType(char Identifier) { |
| switch (Identifier) { |
| case '#': |
| return Type::SectionOpen; |
| case '/': |
| return Type::SectionClose; |
| case '^': |
| return Type::InvertSectionOpen; |
| case '!': |
| return Type::Comment; |
| case '>': |
| return Type::Partial; |
| case '&': |
| return Type::UnescapeVariable; |
| default: |
| return Type::Variable; |
| } |
| } |
| |
| Type TokenType; |
| // RawBody is the original string that was tokenized. |
| std::string RawBody; |
| // TokenBody is the original string with the identifier removed. |
| std::string TokenBody; |
| Accessor AccessorValue; |
| size_t Indentation; |
| }; |
| |
| class ASTNode { |
| public: |
| enum Type { |
| Root, |
| Text, |
| Partial, |
| Variable, |
| UnescapeVariable, |
| Section, |
| InvertSection, |
| }; |
| |
| ASTNode(llvm::StringMap<AstPtr> &Partials, llvm::StringMap<Lambda> &Lambdas, |
| llvm::StringMap<SectionLambda> &SectionLambdas, |
| llvm::DenseMap<char, std::string> &Escapes) |
| : Partials(Partials), Lambdas(Lambdas), SectionLambdas(SectionLambdas), |
| Escapes(Escapes), Ty(Type::Root), Parent(nullptr), |
| ParentContext(nullptr) {} |
| |
| ASTNode(std::string Body, ASTNode *Parent, llvm::StringMap<AstPtr> &Partials, |
| llvm::StringMap<Lambda> &Lambdas, |
| llvm::StringMap<SectionLambda> &SectionLambdas, |
| llvm::DenseMap<char, std::string> &Escapes) |
| : Partials(Partials), Lambdas(Lambdas), SectionLambdas(SectionLambdas), |
| Escapes(Escapes), Ty(Type::Text), Body(std::move(Body)), Parent(Parent), |
| ParentContext(nullptr) {} |
| |
| // Constructor for Section/InvertSection/Variable/UnescapeVariable Nodes |
| ASTNode(Type Ty, Accessor Accessor, ASTNode *Parent, |
| llvm::StringMap<AstPtr> &Partials, llvm::StringMap<Lambda> &Lambdas, |
| llvm::StringMap<SectionLambda> &SectionLambdas, |
| llvm::DenseMap<char, std::string> &Escapes) |
| : Partials(Partials), Lambdas(Lambdas), SectionLambdas(SectionLambdas), |
| Escapes(Escapes), Ty(Ty), Parent(Parent), |
| AccessorValue(std::move(Accessor)), ParentContext(nullptr) {} |
| |
| void addChild(AstPtr Child) { Children.emplace_back(std::move(Child)); }; |
| |
| void setRawBody(std::string NewBody) { RawBody = std::move(NewBody); }; |
| |
| void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; }; |
| |
| void render(const llvm::json::Value &Data, llvm::raw_ostream &OS); |
| |
| private: |
| void renderLambdas(const llvm::json::Value &Contexts, llvm::raw_ostream &OS, |
| Lambda &L); |
| |
| void renderSectionLambdas(const llvm::json::Value &Contexts, |
| llvm::raw_ostream &OS, SectionLambda &L); |
| |
| void renderPartial(const llvm::json::Value &Contexts, llvm::raw_ostream &OS, |
| ASTNode *Partial); |
| |
| void renderChild(const llvm::json::Value &Context, llvm::raw_ostream &OS); |
| |
| const llvm::json::Value *findContext(); |
| |
| StringMap<AstPtr> &Partials; |
| StringMap<Lambda> &Lambdas; |
| StringMap<SectionLambda> &SectionLambdas; |
| DenseMap<char, std::string> &Escapes; |
| Type Ty; |
| size_t Indentation = 0; |
| std::string RawBody; |
| std::string Body; |
| ASTNode *Parent; |
| // TODO: switch implementation to SmallVector<T> |
| std::vector<AstPtr> Children; |
| const Accessor AccessorValue; |
| const llvm::json::Value *ParentContext; |
| }; |
| |
| // A wrapper for arena allocator for ASTNodes |
| AstPtr createRootNode(llvm::StringMap<AstPtr> &Partials, |
| llvm::StringMap<Lambda> &Lambdas, |
| llvm::StringMap<SectionLambda> &SectionLambdas, |
| llvm::DenseMap<char, std::string> &Escapes) { |
| return std::make_unique<ASTNode>(Partials, Lambdas, SectionLambdas, Escapes); |
| } |
| |
| AstPtr createNode(ASTNode::Type T, Accessor A, ASTNode *Parent, |
| llvm::StringMap<AstPtr> &Partials, |
| llvm::StringMap<Lambda> &Lambdas, |
| llvm::StringMap<SectionLambda> &SectionLambdas, |
| llvm::DenseMap<char, std::string> &Escapes) { |
| return std::make_unique<ASTNode>(T, std::move(A), Parent, Partials, Lambdas, |
| SectionLambdas, Escapes); |
| } |
| |
| AstPtr createTextNode(std::string Body, ASTNode *Parent, |
| llvm::StringMap<AstPtr> &Partials, |
| llvm::StringMap<Lambda> &Lambdas, |
| llvm::StringMap<SectionLambda> &SectionLambdas, |
| llvm::DenseMap<char, std::string> &Escapes) { |
| return std::make_unique<ASTNode>(std::move(Body), Parent, Partials, Lambdas, |
| SectionLambdas, Escapes); |
| } |
| |
| // Function to check if there is meaningful text behind. |
| // We determine if a token has meaningful text behind |
| // if the right of previous token contains anything that is |
| // not a newline. |
| // For example: |
| // "Stuff {{#Section}}" (returns true) |
| // vs |
| // "{{#Section}} \n" (returns false) |
| // We make an exception for when previous token is empty |
| // and the current token is the second token. |
| // For example: |
| // "{{#Section}}" |
| bool hasTextBehind(size_t Idx, const ArrayRef<Token> &Tokens) { |
| if (Idx == 0) |
| return true; |
| |
| size_t PrevIdx = Idx - 1; |
| if (Tokens[PrevIdx].getType() != Token::Type::Text) |
| return true; |
| |
| const Token &PrevToken = Tokens[PrevIdx]; |
| StringRef TokenBody = StringRef(PrevToken.RawBody).rtrim(" \r\t\v"); |
| return !TokenBody.ends_with("\n") && !(TokenBody.empty() && Idx == 1); |
| } |
| |
| // Function to check if there's no meaningful text ahead. |
| // We determine if a token has text ahead if the left of previous |
| // token does not start with a newline. |
| bool hasTextAhead(size_t Idx, const ArrayRef<Token> &Tokens) { |
| if (Idx >= Tokens.size() - 1) |
| return true; |
| |
| size_t NextIdx = Idx + 1; |
| if (Tokens[NextIdx].getType() != Token::Type::Text) |
| return true; |
| |
| const Token &NextToken = Tokens[NextIdx]; |
| StringRef TokenBody = StringRef(NextToken.RawBody).ltrim(" "); |
| return !TokenBody.starts_with("\r\n") && !TokenBody.starts_with("\n"); |
| } |
| |
| bool requiresCleanUp(Token::Type T) { |
| // We must clean up all the tokens that could contain child nodes. |
| return T == Token::Type::SectionOpen || T == Token::Type::InvertSectionOpen || |
| T == Token::Type::SectionClose || T == Token::Type::Comment || |
| T == Token::Type::Partial; |
| } |
| |
| // Adjust next token body if there is no text ahead. |
| // For example: |
| // The template string |
| // "{{! Comment }} \nLine 2" |
| // would be considered as no text ahead and should be rendered as |
| // " Line 2" |
| void stripTokenAhead(SmallVectorImpl<Token> &Tokens, size_t Idx) { |
| Token &NextToken = Tokens[Idx + 1]; |
| StringRef NextTokenBody = NextToken.TokenBody; |
| // Cut off the leading newline which could be \n or \r\n. |
| if (NextTokenBody.starts_with("\r\n")) |
| NextToken.TokenBody = NextTokenBody.substr(2).str(); |
| else if (NextTokenBody.starts_with("\n")) |
| NextToken.TokenBody = NextTokenBody.substr(1).str(); |
| } |
| |
| // Adjust previous token body if there no text behind. |
| // For example: |
| // The template string |
| // " \t{{#section}}A{{/section}}" |
| // would be considered as having no text ahead and would be render as |
| // "A" |
| // The exception for this is partial tag which requires us to |
| // keep track of the indentation once it's rendered. |
| void stripTokenBefore(SmallVectorImpl<Token> &Tokens, size_t Idx, |
| Token &CurrentToken, Token::Type CurrentType) { |
| Token &PrevToken = Tokens[Idx - 1]; |
| StringRef PrevTokenBody = PrevToken.TokenBody; |
| StringRef Unindented = PrevTokenBody.rtrim(" \r\t\v"); |
| size_t Indentation = PrevTokenBody.size() - Unindented.size(); |
| if (CurrentType != Token::Type::Partial) |
| PrevToken.TokenBody = Unindented.str(); |
| CurrentToken.setIndentation(Indentation); |
| } |
| |
| // Simple tokenizer that splits the template into tokens. |
| // The mustache spec allows {{{ }}} to unescape variables, |
| // but we don't support that here. An unescape variable |
| // is represented only by {{& variable}}. |
| SmallVector<Token> tokenize(StringRef Template) { |
| SmallVector<Token> Tokens; |
| StringLiteral Open("{{"); |
| StringLiteral Close("}}"); |
| size_t Start = 0; |
| size_t DelimiterStart = Template.find(Open); |
| if (DelimiterStart == StringRef::npos) { |
| Tokens.emplace_back(Template.str()); |
| return Tokens; |
| } |
| while (DelimiterStart != StringRef::npos) { |
| if (DelimiterStart != Start) |
| Tokens.emplace_back(Template.substr(Start, DelimiterStart - Start).str()); |
| size_t DelimiterEnd = Template.find(Close, DelimiterStart); |
| if (DelimiterEnd == StringRef::npos) |
| break; |
| |
| // Extract the Interpolated variable without delimiters. |
| size_t InterpolatedStart = DelimiterStart + Open.size(); |
| size_t InterpolatedEnd = DelimiterEnd - DelimiterStart - Close.size(); |
| std::string Interpolated = |
| Template.substr(InterpolatedStart, InterpolatedEnd).str(); |
| std::string RawBody = Open.str() + Interpolated + Close.str(); |
| Tokens.emplace_back(RawBody, Interpolated, Interpolated[0]); |
| Start = DelimiterEnd + Close.size(); |
| DelimiterStart = Template.find(Open, Start); |
| } |
| |
| if (Start < Template.size()) |
| Tokens.emplace_back(Template.substr(Start).str()); |
| |
| // Fix up white spaces for: |
| // - open sections |
| // - inverted sections |
| // - close sections |
| // - comments |
| // |
| // This loop attempts to find standalone tokens and tries to trim out |
| // the surrounding whitespace. |
| // For example: |
| // if you have the template string |
| // {{#section}} \n Example \n{{/section}} |
| // The output should would be |
| // For example: |
| // \n Example \n |
| size_t LastIdx = Tokens.size() - 1; |
| for (size_t Idx = 0, End = Tokens.size(); Idx < End; ++Idx) { |
| Token &CurrentToken = Tokens[Idx]; |
| Token::Type CurrentType = CurrentToken.getType(); |
| // Check if token type requires cleanup. |
| bool RequiresCleanUp = requiresCleanUp(CurrentType); |
| |
| if (!RequiresCleanUp) |
| continue; |
| |
| // We adjust the token body if there's no text behind or ahead. |
| // A token is considered to have no text ahead if the right of the previous |
| // token is a newline followed by spaces. |
| // A token is considered to have no text behind if the left of the next |
| // token is spaces followed by a newline. |
| // eg. |
| // "Line 1\n {{#section}} \n Line 2 \n {{/section}} \n Line 3" |
| bool HasTextBehind = hasTextBehind(Idx, Tokens); |
| bool HasTextAhead = hasTextAhead(Idx, Tokens); |
| |
| if ((!HasTextAhead && !HasTextBehind) || (!HasTextAhead && Idx == 0)) |
| stripTokenAhead(Tokens, Idx); |
| |
| if ((!HasTextBehind && !HasTextAhead) || (!HasTextBehind && Idx == LastIdx)) |
| stripTokenBefore(Tokens, Idx, CurrentToken, CurrentType); |
| } |
| return Tokens; |
| } |
| |
| // Custom stream to escape strings. |
| class EscapeStringStream : public raw_ostream { |
| public: |
| explicit EscapeStringStream(llvm::raw_ostream &WrappedStream, |
| DenseMap<char, std::string> &Escape) |
| : Escape(Escape), WrappedStream(WrappedStream) { |
| SetUnbuffered(); |
| } |
| |
| protected: |
| void write_impl(const char *Ptr, size_t Size) override { |
| llvm::StringRef Data(Ptr, Size); |
| for (char C : Data) { |
| auto It = Escape.find(C); |
| if (It != Escape.end()) |
| WrappedStream << It->getSecond(); |
| else |
| WrappedStream << C; |
| } |
| } |
| |
| uint64_t current_pos() const override { return WrappedStream.tell(); } |
| |
| private: |
| DenseMap<char, std::string> &Escape; |
| llvm::raw_ostream &WrappedStream; |
| }; |
| |
| // Custom stream to add indentation used to for rendering partials. |
| class AddIndentationStringStream : public raw_ostream { |
| public: |
| explicit AddIndentationStringStream(llvm::raw_ostream &WrappedStream, |
| size_t Indentation) |
| : Indentation(Indentation), WrappedStream(WrappedStream) { |
| SetUnbuffered(); |
| } |
| |
| protected: |
| void write_impl(const char *Ptr, size_t Size) override { |
| llvm::StringRef Data(Ptr, Size); |
| SmallString<0> Indent; |
| Indent.resize(Indentation, ' '); |
| for (char C : Data) { |
| WrappedStream << C; |
| if (C == '\n') |
| WrappedStream << Indent; |
| } |
| } |
| |
| uint64_t current_pos() const override { return WrappedStream.tell(); } |
| |
| private: |
| size_t Indentation; |
| llvm::raw_ostream &WrappedStream; |
| }; |
| |
| class Parser { |
| public: |
| Parser(StringRef TemplateStr) : TemplateStr(TemplateStr) {} |
| |
| AstPtr parse(llvm::StringMap<AstPtr> &Partials, |
| llvm::StringMap<Lambda> &Lambdas, |
| llvm::StringMap<SectionLambda> &SectionLambdas, |
| llvm::DenseMap<char, std::string> &Escapes); |
| |
| private: |
| void parseMustache(ASTNode *Parent, llvm::StringMap<AstPtr> &Partials, |
| llvm::StringMap<Lambda> &Lambdas, |
| llvm::StringMap<SectionLambda> &SectionLambdas, |
| llvm::DenseMap<char, std::string> &Escapes); |
| |
| SmallVector<Token> Tokens; |
| size_t CurrentPtr; |
| StringRef TemplateStr; |
| }; |
| |
| AstPtr Parser::parse(llvm::StringMap<AstPtr> &Partials, |
| llvm::StringMap<Lambda> &Lambdas, |
| llvm::StringMap<SectionLambda> &SectionLambdas, |
| llvm::DenseMap<char, std::string> &Escapes) { |
| Tokens = tokenize(TemplateStr); |
| CurrentPtr = 0; |
| AstPtr RootNode = createRootNode(Partials, Lambdas, SectionLambdas, Escapes); |
| parseMustache(RootNode.get(), Partials, Lambdas, SectionLambdas, Escapes); |
| return RootNode; |
| } |
| |
| void Parser::parseMustache(ASTNode *Parent, llvm::StringMap<AstPtr> &Partials, |
| llvm::StringMap<Lambda> &Lambdas, |
| llvm::StringMap<SectionLambda> &SectionLambdas, |
| llvm::DenseMap<char, std::string> &Escapes) { |
| |
| while (CurrentPtr < Tokens.size()) { |
| Token CurrentToken = Tokens[CurrentPtr]; |
| CurrentPtr++; |
| Accessor A = CurrentToken.getAccessor(); |
| AstPtr CurrentNode; |
| |
| switch (CurrentToken.getType()) { |
| case Token::Type::Text: { |
| CurrentNode = createTextNode(std::move(CurrentToken.TokenBody), Parent, |
| Partials, Lambdas, SectionLambdas, Escapes); |
| Parent->addChild(std::move(CurrentNode)); |
| break; |
| } |
| case Token::Type::Variable: { |
| CurrentNode = createNode(ASTNode::Variable, std::move(A), Parent, |
| Partials, Lambdas, SectionLambdas, Escapes); |
| Parent->addChild(std::move(CurrentNode)); |
| break; |
| } |
| case Token::Type::UnescapeVariable: { |
| CurrentNode = createNode(ASTNode::UnescapeVariable, std::move(A), Parent, |
| Partials, Lambdas, SectionLambdas, Escapes); |
| Parent->addChild(std::move(CurrentNode)); |
| break; |
| } |
| case Token::Type::Partial: { |
| CurrentNode = createNode(ASTNode::Partial, std::move(A), Parent, Partials, |
| Lambdas, SectionLambdas, Escapes); |
| CurrentNode->setIndentation(CurrentToken.getIndentation()); |
| Parent->addChild(std::move(CurrentNode)); |
| break; |
| } |
| case Token::Type::SectionOpen: { |
| CurrentNode = createNode(ASTNode::Section, A, Parent, Partials, Lambdas, |
| SectionLambdas, Escapes); |
| size_t Start = CurrentPtr; |
| parseMustache(CurrentNode.get(), Partials, Lambdas, SectionLambdas, |
| Escapes); |
| const size_t End = CurrentPtr - 1; |
| std::string RawBody; |
| for (std::size_t I = Start; I < End; I++) |
| RawBody += Tokens[I].RawBody; |
| CurrentNode->setRawBody(std::move(RawBody)); |
| Parent->addChild(std::move(CurrentNode)); |
| break; |
| } |
| case Token::Type::InvertSectionOpen: { |
| CurrentNode = createNode(ASTNode::InvertSection, A, Parent, Partials, |
| Lambdas, SectionLambdas, Escapes); |
| size_t Start = CurrentPtr; |
| parseMustache(CurrentNode.get(), Partials, Lambdas, SectionLambdas, |
| Escapes); |
| const size_t End = CurrentPtr - 1; |
| std::string RawBody; |
| for (size_t Idx = Start; Idx < End; Idx++) |
| RawBody += Tokens[Idx].RawBody; |
| CurrentNode->setRawBody(std::move(RawBody)); |
| Parent->addChild(std::move(CurrentNode)); |
| break; |
| } |
| case Token::Type::Comment: |
| break; |
| case Token::Type::SectionClose: |
| return; |
| } |
| } |
| } |
| void toMustacheString(const json::Value &Data, raw_ostream &OS) { |
| switch (Data.kind()) { |
| case json::Value::Null: |
| return; |
| case json::Value::Number: { |
| auto Num = *Data.getAsNumber(); |
| std::ostringstream SS; |
| SS << Num; |
| OS << SS.str(); |
| return; |
| } |
| case json::Value::String: { |
| auto Str = *Data.getAsString(); |
| OS << Str.str(); |
| return; |
| } |
| |
| case json::Value::Array: { |
| auto Arr = *Data.getAsArray(); |
| if (Arr.empty()) |
| return; |
| [[fallthrough]]; |
| } |
| case json::Value::Object: |
| case json::Value::Boolean: { |
| llvm::json::OStream JOS(OS, 2); |
| JOS.value(Data); |
| break; |
| } |
| } |
| } |
| |
| void ASTNode::render(const json::Value &Data, raw_ostream &OS) { |
| ParentContext = &Data; |
| const json::Value *ContextPtr = Ty == Root ? ParentContext : findContext(); |
| const json::Value &Context = ContextPtr ? *ContextPtr : nullptr; |
| |
| switch (Ty) { |
| case Root: |
| renderChild(Data, OS); |
| return; |
| case Text: |
| OS << Body; |
| return; |
| case Partial: { |
| auto Partial = Partials.find(AccessorValue[0]); |
| if (Partial != Partials.end()) |
| renderPartial(Data, OS, Partial->getValue().get()); |
| return; |
| } |
| case Variable: { |
| auto Lambda = Lambdas.find(AccessorValue[0]); |
| if (Lambda != Lambdas.end()) |
| renderLambdas(Data, OS, Lambda->getValue()); |
| else { |
| EscapeStringStream ES(OS, Escapes); |
| toMustacheString(Context, ES); |
| } |
| return; |
| } |
| case UnescapeVariable: { |
| auto Lambda = Lambdas.find(AccessorValue[0]); |
| if (Lambda != Lambdas.end()) |
| renderLambdas(Data, OS, Lambda->getValue()); |
| else |
| toMustacheString(Context, OS); |
| return; |
| } |
| case Section: { |
| // Sections are not rendered if the context is falsey. |
| auto SectionLambda = SectionLambdas.find(AccessorValue[0]); |
| bool IsLambda = SectionLambda != SectionLambdas.end(); |
| if (isFalsey(Context) && !IsLambda) |
| return; |
| |
| if (IsLambda) { |
| renderSectionLambdas(Data, OS, SectionLambda->getValue()); |
| return; |
| } |
| |
| if (Context.getAsArray()) { |
| const json::Array *Arr = Context.getAsArray(); |
| for (const json::Value &V : *Arr) |
| renderChild(V, OS); |
| return; |
| } |
| renderChild(Context, OS); |
| return; |
| } |
| case InvertSection: { |
| bool IsLambda = |
| SectionLambdas.find(AccessorValue[0]) != SectionLambdas.end(); |
| if (!isFalsey(Context) || IsLambda) |
| return; |
| renderChild(Context, OS); |
| return; |
| } |
| } |
| llvm_unreachable("Invalid ASTNode type"); |
| } |
| |
| const json::Value *ASTNode::findContext() { |
| // The mustache spec allows for dot notation to access nested values |
| // a single dot refers to the current context. |
| // We attempt to find the JSON context in the current node, if it is not |
| // found, then we traverse the parent nodes to find the context until we |
| // reach the root node or the context is found. |
| if (AccessorValue.empty()) |
| return nullptr; |
| if (AccessorValue[0] == ".") |
| return ParentContext; |
| |
| const json::Object *CurrentContext = ParentContext->getAsObject(); |
| StringRef CurrentAccessor = AccessorValue[0]; |
| ASTNode *CurrentParent = Parent; |
| |
| while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) { |
| if (CurrentParent->Ty != Root) { |
| CurrentContext = CurrentParent->ParentContext->getAsObject(); |
| CurrentParent = CurrentParent->Parent; |
| continue; |
| } |
| return nullptr; |
| } |
| const json::Value *Context = nullptr; |
| for (auto [Idx, Acc] : enumerate(AccessorValue)) { |
| const json::Value *CurrentValue = CurrentContext->get(Acc); |
| if (!CurrentValue) |
| return nullptr; |
| if (Idx < AccessorValue.size() - 1) { |
| CurrentContext = CurrentValue->getAsObject(); |
| if (!CurrentContext) |
| return nullptr; |
| } else |
| Context = CurrentValue; |
| } |
| return Context; |
| } |
| |
| void ASTNode::renderChild(const json::Value &Contexts, llvm::raw_ostream &OS) { |
| for (AstPtr &Child : Children) |
| Child->render(Contexts, OS); |
| } |
| |
| void ASTNode::renderPartial(const json::Value &Contexts, llvm::raw_ostream &OS, |
| ASTNode *Partial) { |
| AddIndentationStringStream IS(OS, Indentation); |
| Partial->render(Contexts, IS); |
| } |
| |
| void ASTNode::renderLambdas(const json::Value &Contexts, llvm::raw_ostream &OS, |
| Lambda &L) { |
| json::Value LambdaResult = L(); |
| std::string LambdaStr; |
| raw_string_ostream Output(LambdaStr); |
| toMustacheString(LambdaResult, Output); |
| Parser P = Parser(LambdaStr); |
| AstPtr LambdaNode = P.parse(Partials, Lambdas, SectionLambdas, Escapes); |
| |
| EscapeStringStream ES(OS, Escapes); |
| if (Ty == Variable) { |
| LambdaNode->render(Contexts, ES); |
| return; |
| } |
| LambdaNode->render(Contexts, OS); |
| } |
| |
| void ASTNode::renderSectionLambdas(const json::Value &Contexts, |
| llvm::raw_ostream &OS, SectionLambda &L) { |
| json::Value Return = L(RawBody); |
| if (isFalsey(Return)) |
| return; |
| std::string LambdaStr; |
| raw_string_ostream Output(LambdaStr); |
| toMustacheString(Return, Output); |
| Parser P = Parser(LambdaStr); |
| AstPtr LambdaNode = P.parse(Partials, Lambdas, SectionLambdas, Escapes); |
| LambdaNode->render(Contexts, OS); |
| } |
| |
| void Template::render(const json::Value &Data, llvm::raw_ostream &OS) { |
| Tree->render(Data, OS); |
| } |
| |
| void Template::registerPartial(std::string Name, std::string Partial) { |
| Parser P = Parser(Partial); |
| AstPtr PartialTree = P.parse(Partials, Lambdas, SectionLambdas, Escapes); |
| Partials.insert(std::make_pair(Name, std::move(PartialTree))); |
| } |
| |
| void Template::registerLambda(std::string Name, Lambda L) { Lambdas[Name] = L; } |
| |
| void Template::registerLambda(std::string Name, SectionLambda L) { |
| SectionLambdas[Name] = L; |
| } |
| |
| void Template::overrideEscapeCharacters(DenseMap<char, std::string> E) { |
| Escapes = std::move(E); |
| } |
| |
| Template::Template(StringRef TemplateStr) { |
| Parser P = Parser(TemplateStr); |
| Tree = P.parse(Partials, Lambdas, SectionLambdas, Escapes); |
| // The default behavior is to escape html entities. |
| DenseMap<char, std::string> HtmlEntities = {{'&', "&"}, |
| {'<', "<"}, |
| {'>', ">"}, |
| {'"', """}, |
| {'\'', "'"}}; |
| overrideEscapeCharacters(HtmlEntities); |
| } |
| |
| Template::Template(Template &&Other) noexcept |
| : Partials(std::move(Other.Partials)), Lambdas(std::move(Other.Lambdas)), |
| SectionLambdas(std::move(Other.SectionLambdas)), |
| Escapes(std::move(Other.Escapes)), Tree(std::move(Other.Tree)) {} |
| |
| Template::~Template() = default; |
| |
| Template &Template::operator=(Template &&Other) noexcept { |
| if (this != &Other) { |
| Partials = std::move(Other.Partials); |
| Lambdas = std::move(Other.Lambdas); |
| SectionLambdas = std::move(Other.SectionLambdas); |
| Escapes = std::move(Other.Escapes); |
| Tree = std::move(Other.Tree); |
| Other.Tree = nullptr; |
| } |
| return *this; |
| } |
| } // namespace llvm::mustache |