//===-- HTMLGenerator.cpp - HTML Generator ----------------------*- C++ -*-===// // // 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 "Generators.h" #include "Representation.h" #include "support/File.h" #include "clang/Basic/Version.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/JSON.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include #include #include using namespace llvm; namespace clang { namespace doc { namespace { class HTMLTag { public: // Any other tag can be added if required enum TagType { TAG_A, TAG_DIV, TAG_FOOTER, TAG_H1, TAG_H2, TAG_H3, TAG_HEADER, TAG_LI, TAG_LINK, TAG_MAIN, TAG_META, TAG_OL, TAG_P, TAG_SCRIPT, TAG_SPAN, TAG_TITLE, TAG_UL, TAG_TABLE, TAG_THEAD, TAG_TBODY, TAG_TR, TAG_TD, TAG_TH }; HTMLTag() = default; constexpr HTMLTag(TagType Value) : Value(Value) {} operator TagType() const { return Value; } operator bool() = delete; bool isSelfClosing() const; StringRef toString() const; private: TagType Value; }; enum NodeType { NODE_TEXT, NODE_TAG, }; struct HTMLNode { HTMLNode(NodeType Type) : Type(Type) {} virtual ~HTMLNode() = default; virtual void render(llvm::raw_ostream &OS, int IndentationLevel) = 0; NodeType Type; // Type of node }; struct TextNode : public HTMLNode { TextNode(const Twine &Text) : HTMLNode(NodeType::NODE_TEXT), Text(Text.str()) {} std::string Text; // Content of node void render(llvm::raw_ostream &OS, int IndentationLevel) override; }; struct TagNode : public HTMLNode { TagNode(HTMLTag Tag) : HTMLNode(NodeType::NODE_TAG), Tag(Tag) {} TagNode(HTMLTag Tag, const Twine &Text) : TagNode(Tag) { Children.emplace_back(std::make_unique(Text.str())); } HTMLTag Tag; // Name of HTML Tag (p, div, h1) std::vector> Children; // List of child nodes std::vector> Attributes; // List of key-value attributes for tag void render(llvm::raw_ostream &OS, int IndentationLevel) override; }; constexpr const char *kDoctypeDecl = ""; struct HTMLFile { std::vector> Children; // List of child nodes void render(llvm::raw_ostream &OS) { OS << kDoctypeDecl << "\n"; for (const auto &C : Children) { C->render(OS, 0); OS << "\n"; } } }; } // namespace bool HTMLTag::isSelfClosing() const { switch (Value) { case HTMLTag::TAG_META: case HTMLTag::TAG_LINK: return true; case HTMLTag::TAG_A: case HTMLTag::TAG_DIV: case HTMLTag::TAG_FOOTER: case HTMLTag::TAG_H1: case HTMLTag::TAG_H2: case HTMLTag::TAG_H3: case HTMLTag::TAG_HEADER: case HTMLTag::TAG_LI: case HTMLTag::TAG_MAIN: case HTMLTag::TAG_OL: case HTMLTag::TAG_P: case HTMLTag::TAG_SCRIPT: case HTMLTag::TAG_SPAN: case HTMLTag::TAG_TITLE: case HTMLTag::TAG_UL: case HTMLTag::TAG_TABLE: case HTMLTag::TAG_THEAD: case HTMLTag::TAG_TBODY: case HTMLTag::TAG_TR: case HTMLTag::TAG_TD: case HTMLTag::TAG_TH: return false; } llvm_unreachable("Unhandled HTMLTag::TagType"); } StringRef HTMLTag::toString() const { switch (Value) { case HTMLTag::TAG_A: return "a"; case HTMLTag::TAG_DIV: return "div"; case HTMLTag::TAG_FOOTER: return "footer"; case HTMLTag::TAG_H1: return "h1"; case HTMLTag::TAG_H2: return "h2"; case HTMLTag::TAG_H3: return "h3"; case HTMLTag::TAG_HEADER: return "header"; case HTMLTag::TAG_LI: return "li"; case HTMLTag::TAG_LINK: return "link"; case HTMLTag::TAG_MAIN: return "main"; case HTMLTag::TAG_META: return "meta"; case HTMLTag::TAG_OL: return "ol"; case HTMLTag::TAG_P: return "p"; case HTMLTag::TAG_SCRIPT: return "script"; case HTMLTag::TAG_SPAN: return "span"; case HTMLTag::TAG_TITLE: return "title"; case HTMLTag::TAG_UL: return "ul"; case HTMLTag::TAG_TABLE: return "table"; case HTMLTag::TAG_THEAD: return "thead"; case HTMLTag::TAG_TBODY: return "tbody"; case HTMLTag::TAG_TR: return "tr"; case HTMLTag::TAG_TD: return "td"; case HTMLTag::TAG_TH: return "th"; } llvm_unreachable("Unhandled HTMLTag::TagType"); } void TextNode::render(llvm::raw_ostream &OS, int IndentationLevel) { OS.indent(IndentationLevel * 2); printHTMLEscaped(Text, OS); } void TagNode::render(llvm::raw_ostream &OS, int IndentationLevel) { // Children nodes are rendered in the same line if all of them are text nodes bool InlineChildren = true; for (const auto &C : Children) if (C->Type == NodeType::NODE_TAG) { InlineChildren = false; break; } OS.indent(IndentationLevel * 2); OS << "<" << Tag.toString(); for (const auto &A : Attributes) OS << " " << A.first << "=\"" << A.second << "\""; if (Tag.isSelfClosing()) { OS << "/>"; return; } OS << ">"; if (!InlineChildren) OS << "\n"; bool NewLineRendered = true; for (const auto &C : Children) { int ChildrenIndentation = InlineChildren || !NewLineRendered ? 0 : IndentationLevel + 1; C->render(OS, ChildrenIndentation); if (!InlineChildren && (C == Children.back() || (C->Type != NodeType::NODE_TEXT || (&C + 1)->get()->Type != NodeType::NODE_TEXT))) { OS << "\n"; NewLineRendered = true; } else NewLineRendered = false; } if (!InlineChildren) OS.indent(IndentationLevel * 2); OS << ""; } template ::value>> static void appendVector(std::vector &&New, std::vector &Original) { std::move(New.begin(), New.end(), std::back_inserter(Original)); } // HTML generation static std::vector> genStylesheetsHTML(StringRef InfoPath, const ClangDocContext &CDCtx) { std::vector> Out; for (const auto &FilePath : CDCtx.UserStylesheets) { auto LinkNode = std::make_unique(HTMLTag::TAG_LINK); LinkNode->Attributes.emplace_back("rel", "stylesheet"); SmallString<128> StylesheetPath = computeRelativePath("", InfoPath); llvm::sys::path::append(StylesheetPath, llvm::sys::path::filename(FilePath)); // Paths in HTML must be in posix-style llvm::sys::path::native(StylesheetPath, llvm::sys::path::Style::posix); LinkNode->Attributes.emplace_back("href", std::string(StylesheetPath)); Out.emplace_back(std::move(LinkNode)); } return Out; } static std::vector> genJsScriptsHTML(StringRef InfoPath, const ClangDocContext &CDCtx) { std::vector> Out; // index_json.js is part of every generated HTML file SmallString<128> IndexJSONPath = computeRelativePath("", InfoPath); auto IndexJSONNode = std::make_unique(HTMLTag::TAG_SCRIPT); llvm::sys::path::append(IndexJSONPath, "index_json.js"); llvm::sys::path::native(IndexJSONPath, llvm::sys::path::Style::posix); IndexJSONNode->Attributes.emplace_back("src", std::string(IndexJSONPath)); Out.emplace_back(std::move(IndexJSONNode)); for (const auto &FilePath : CDCtx.JsScripts) { SmallString<128> ScriptPath = computeRelativePath("", InfoPath); auto ScriptNode = std::make_unique(HTMLTag::TAG_SCRIPT); llvm::sys::path::append(ScriptPath, llvm::sys::path::filename(FilePath)); // Paths in HTML must be in posix-style llvm::sys::path::native(ScriptPath, llvm::sys::path::Style::posix); ScriptNode->Attributes.emplace_back("src", std::string(ScriptPath)); Out.emplace_back(std::move(ScriptNode)); } return Out; } static std::unique_ptr genLink(const Twine &Text, const Twine &Link) { auto LinkNode = std::make_unique(HTMLTag::TAG_A, Text); LinkNode->Attributes.emplace_back("href", Link.str()); return LinkNode; } static std::unique_ptr genReference(const Reference &Type, StringRef CurrentDirectory, std::optional JumpToSection = std::nullopt) { if (Type.Path.empty()) { if (!JumpToSection) return std::make_unique(Type.Name); return genLink(Type.Name, "#" + *JumpToSection); } llvm::SmallString<64> Path = Type.getRelativeFilePath(CurrentDirectory); llvm::sys::path::append(Path, Type.getFileBaseName() + ".html"); // Paths in HTML must be in posix-style llvm::sys::path::native(Path, llvm::sys::path::Style::posix); if (JumpToSection) Path += ("#" + *JumpToSection).str(); return genLink(Type.Name, Path); } static std::vector> genReferenceList(const llvm::SmallVectorImpl &Refs, const StringRef &CurrentDirectory) { std::vector> Out; for (const auto &R : Refs) { if (&R != Refs.begin()) Out.emplace_back(std::make_unique(", ")); Out.emplace_back(genReference(R, CurrentDirectory)); } return Out; } static std::vector> genHTML(const EnumInfo &I, const ClangDocContext &CDCtx); static std::vector> genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx, StringRef ParentInfoDir); static std::unique_ptr genHTML(const std::vector &C); static std::vector> genEnumsBlock(const std::vector &Enums, const ClangDocContext &CDCtx) { if (Enums.empty()) return {}; std::vector> Out; Out.emplace_back(std::make_unique(HTMLTag::TAG_H2, "Enums")); Out.back()->Attributes.emplace_back("id", "Enums"); Out.emplace_back(std::make_unique(HTMLTag::TAG_DIV)); auto &DivBody = Out.back(); for (const auto &E : Enums) { std::vector> Nodes = genHTML(E, CDCtx); appendVector(std::move(Nodes), DivBody->Children); } return Out; } static std::unique_ptr genEnumMembersBlock(const llvm::SmallVector &Members) { if (Members.empty()) return nullptr; auto List = std::make_unique(HTMLTag::TAG_TBODY); for (const auto &M : Members) { auto TRNode = std::make_unique(HTMLTag::TAG_TR); TRNode->Children.emplace_back( std::make_unique(HTMLTag::TAG_TD, M.Name)); // Use user supplied value if it exists, otherwise use the value if (!M.ValueExpr.empty()) { TRNode->Children.emplace_back( std::make_unique(HTMLTag::TAG_TD, M.ValueExpr)); } else { TRNode->Children.emplace_back( std::make_unique(HTMLTag::TAG_TD, M.Value)); } if (!M.Description.empty()) { auto TD = std::make_unique(HTMLTag::TAG_TD); TD->Children.emplace_back(genHTML(M.Description)); TRNode->Children.emplace_back(std::move(TD)); } List->Children.emplace_back(std::move(TRNode)); } return List; } static std::vector> genFunctionsBlock(const std::vector &Functions, const ClangDocContext &CDCtx, StringRef ParentInfoDir) { if (Functions.empty()) return {}; std::vector> Out; Out.emplace_back(std::make_unique(HTMLTag::TAG_H2, "Functions")); Out.back()->Attributes.emplace_back("id", "Functions"); Out.emplace_back(std::make_unique(HTMLTag::TAG_DIV)); auto &DivBody = Out.back(); for (const auto &F : Functions) { std::vector> Nodes = genHTML(F, CDCtx, ParentInfoDir); appendVector(std::move(Nodes), DivBody->Children); } return Out; } static std::vector> genRecordMembersBlock(const llvm::SmallVector &Members, StringRef ParentInfoDir) { if (Members.empty()) return {}; std::vector> Out; Out.emplace_back(std::make_unique(HTMLTag::TAG_H2, "Members")); Out.back()->Attributes.emplace_back("id", "Members"); Out.emplace_back(std::make_unique(HTMLTag::TAG_UL)); auto &ULBody = Out.back(); for (const auto &M : Members) { std::string Access = getAccessSpelling(M.Access).str(); if (Access != "") Access = Access + " "; auto LIBody = std::make_unique(HTMLTag::TAG_LI); auto MemberDecl = std::make_unique(HTMLTag::TAG_DIV); MemberDecl->Children.emplace_back(std::make_unique(Access)); MemberDecl->Children.emplace_back(genReference(M.Type, ParentInfoDir)); MemberDecl->Children.emplace_back(std::make_unique(" " + M.Name)); if (!M.Description.empty()) LIBody->Children.emplace_back(genHTML(M.Description)); LIBody->Children.emplace_back(std::move(MemberDecl)); ULBody->Children.emplace_back(std::move(LIBody)); } return Out; } static std::vector> genReferencesBlock(const std::vector &References, llvm::StringRef Title, StringRef ParentPath) { if (References.empty()) return {}; std::vector> Out; Out.emplace_back(std::make_unique(HTMLTag::TAG_H2, Title)); Out.back()->Attributes.emplace_back("id", std::string(Title)); Out.emplace_back(std::make_unique(HTMLTag::TAG_UL)); auto &ULBody = Out.back(); for (const auto &R : References) { auto LiNode = std::make_unique(HTMLTag::TAG_LI); LiNode->Children.emplace_back(genReference(R, ParentPath)); ULBody->Children.emplace_back(std::move(LiNode)); } return Out; } static std::unique_ptr writeSourceFileRef(const ClangDocContext &CDCtx, const Location &L) { if (!L.IsFileInRootDir && !CDCtx.RepositoryUrl) return std::make_unique( HTMLTag::TAG_P, "Defined at line " + std::to_string(L.LineNumber) + " of file " + L.Filename); SmallString<128> FileURL(CDCtx.RepositoryUrl.value_or("")); llvm::sys::path::append( FileURL, llvm::sys::path::Style::posix, // If we're on Windows, the file name will be in the wrong format, and // append won't convert the full path being appended to the correct // format, so we need to do that here. llvm::sys::path::convert_to_slash( L.Filename, // The style here is the current style of the path, not the one we're // targeting. If the string is already in the posix style, it will do // nothing. llvm::sys::path::Style::windows)); auto Node = std::make_unique(HTMLTag::TAG_P); Node->Children.emplace_back(std::make_unique("Defined at line ")); auto LocNumberNode = std::make_unique(HTMLTag::TAG_A, std::to_string(L.LineNumber)); // The links to a specific line in the source code use the github / // googlesource notation so it won't work for all hosting pages. LocNumberNode->Attributes.emplace_back( "href", formatv("{0}#{1}{2}", FileURL, CDCtx.RepositoryLinePrefix.value_or(""), L.LineNumber)); Node->Children.emplace_back(std::move(LocNumberNode)); Node->Children.emplace_back(std::make_unique(" of file ")); auto LocFileNode = std::make_unique( HTMLTag::TAG_A, llvm::sys::path::filename(FileURL)); LocFileNode->Attributes.emplace_back("href", std::string(FileURL)); Node->Children.emplace_back(std::move(LocFileNode)); return Node; } static void maybeWriteSourceFileRef(std::vector> &Out, const ClangDocContext &CDCtx, const std::optional &DefLoc) { if (DefLoc) Out.emplace_back(writeSourceFileRef(CDCtx, *DefLoc)); } static std::vector> genHTML(const Index &Index, StringRef InfoPath, bool IsOutermostList); // Generates a list of child nodes for the HTML head tag // It contains a meta node, link nodes to import CSS files, and script nodes to // import JS files static std::vector> genFileHeadNodes(StringRef Title, StringRef InfoPath, const ClangDocContext &CDCtx) { std::vector> Out; auto MetaNode = std::make_unique(HTMLTag::TAG_META); MetaNode->Attributes.emplace_back("charset", "utf-8"); Out.emplace_back(std::move(MetaNode)); Out.emplace_back(std::make_unique(HTMLTag::TAG_TITLE, Title)); std::vector> StylesheetsNodes = genStylesheetsHTML(InfoPath, CDCtx); appendVector(std::move(StylesheetsNodes), Out); std::vector> JsNodes = genJsScriptsHTML(InfoPath, CDCtx); appendVector(std::move(JsNodes), Out); return Out; } // Generates a header HTML node that can be used for any file // It contains the project name static std::unique_ptr genFileHeaderNode(StringRef ProjectName) { auto HeaderNode = std::make_unique(HTMLTag::TAG_HEADER, ProjectName); HeaderNode->Attributes.emplace_back("id", "project-title"); return HeaderNode; } // Generates a main HTML node that has all the main content of an info file // It contains both indexes and the info's documented information // This function should only be used for the info files (not for the file that // only has the general index) static std::unique_ptr genInfoFileMainNode( StringRef InfoPath, std::vector> &MainContentInnerNodes, const Index &InfoIndex) { auto MainNode = std::make_unique(HTMLTag::TAG_MAIN); auto LeftSidebarNode = std::make_unique(HTMLTag::TAG_DIV); LeftSidebarNode->Attributes.emplace_back("id", "sidebar-left"); LeftSidebarNode->Attributes.emplace_back("path", std::string(InfoPath)); LeftSidebarNode->Attributes.emplace_back( "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left"); auto MainContentNode = std::make_unique(HTMLTag::TAG_DIV); MainContentNode->Attributes.emplace_back("id", "main-content"); MainContentNode->Attributes.emplace_back( "class", "col-xs-12 col-sm-9 col-md-8 main-content"); appendVector(std::move(MainContentInnerNodes), MainContentNode->Children); auto RightSidebarNode = std::make_unique(HTMLTag::TAG_DIV); RightSidebarNode->Attributes.emplace_back("id", "sidebar-right"); RightSidebarNode->Attributes.emplace_back( "class", "col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right"); std::vector> InfoIndexHTML = genHTML(InfoIndex, InfoPath, true); appendVector(std::move(InfoIndexHTML), RightSidebarNode->Children); MainNode->Children.emplace_back(std::move(LeftSidebarNode)); MainNode->Children.emplace_back(std::move(MainContentNode)); MainNode->Children.emplace_back(std::move(RightSidebarNode)); return MainNode; } // Generates a footer HTML node that can be used for any file // It contains clang-doc's version static std::unique_ptr genFileFooterNode() { auto FooterNode = std::make_unique(HTMLTag::TAG_FOOTER); auto SpanNode = std::make_unique( HTMLTag::TAG_SPAN, clang::getClangToolFullVersion("clang-doc")); SpanNode->Attributes.emplace_back("class", "no-break"); FooterNode->Children.emplace_back(std::move(SpanNode)); return FooterNode; } // Generates a complete HTMLFile for an Info static HTMLFile genInfoFile(StringRef Title, StringRef InfoPath, std::vector> &MainContentNodes, const Index &InfoIndex, const ClangDocContext &CDCtx) { HTMLFile F; std::vector> HeadNodes = genFileHeadNodes(Title, InfoPath, CDCtx); std::unique_ptr HeaderNode = genFileHeaderNode(CDCtx.ProjectName); std::unique_ptr MainNode = genInfoFileMainNode(InfoPath, MainContentNodes, InfoIndex); std::unique_ptr FooterNode = genFileFooterNode(); appendVector(std::move(HeadNodes), F.Children); F.Children.emplace_back(std::move(HeaderNode)); F.Children.emplace_back(std::move(MainNode)); F.Children.emplace_back(std::move(FooterNode)); return F; } template ::value>> static Index genInfoIndexItem(const std::vector &Infos, StringRef Title) { Index Idx(Title, Title); for (const auto &C : Infos) Idx.Children.emplace_back(C.extractName(), llvm::toHex(llvm::toStringRef(C.USR))); return Idx; } static std::vector> genHTML(const Index &Index, StringRef InfoPath, bool IsOutermostList) { std::vector> Out; if (!Index.Name.empty()) { Out.emplace_back(std::make_unique(HTMLTag::TAG_SPAN)); auto &SpanBody = Out.back(); if (!Index.JumpToSection) SpanBody->Children.emplace_back(genReference(Index, InfoPath)); else SpanBody->Children.emplace_back( genReference(Index, InfoPath, Index.JumpToSection->str())); } if (Index.Children.empty()) return Out; // Only the outermost list should use ol, the others should use ul HTMLTag ListHTMLTag = IsOutermostList ? HTMLTag::TAG_OL : HTMLTag::TAG_UL; Out.emplace_back(std::make_unique(ListHTMLTag)); const auto &UlBody = Out.back(); for (const auto &C : Index.Children) { auto LiBody = std::make_unique(HTMLTag::TAG_LI); std::vector> Nodes = genHTML(C, InfoPath, false); appendVector(std::move(Nodes), LiBody->Children); UlBody->Children.emplace_back(std::move(LiBody)); } return Out; } static std::unique_ptr genHTML(const CommentInfo &I) { if (I.Kind == "FullComment") { auto FullComment = std::make_unique(HTMLTag::TAG_DIV); for (const auto &Child : I.Children) { std::unique_ptr Node = genHTML(*Child); if (Node) FullComment->Children.emplace_back(std::move(Node)); } return std::move(FullComment); } if (I.Kind == "ParagraphComment") { auto ParagraphComment = std::make_unique(HTMLTag::TAG_P); for (const auto &Child : I.Children) { std::unique_ptr Node = genHTML(*Child); if (Node) ParagraphComment->Children.emplace_back(std::move(Node)); } if (ParagraphComment->Children.empty()) return nullptr; return std::move(ParagraphComment); } if (I.Kind == "BlockCommandComment") { auto BlockComment = std::make_unique(HTMLTag::TAG_DIV); BlockComment->Children.emplace_back( std::make_unique(HTMLTag::TAG_DIV, I.Name)); for (const auto &Child : I.Children) { std::unique_ptr Node = genHTML(*Child); if (Node) BlockComment->Children.emplace_back(std::move(Node)); } if (BlockComment->Children.empty()) return nullptr; return std::move(BlockComment); } if (I.Kind == "TextComment") { if (I.Text == "") return nullptr; return std::make_unique(I.Text); } return nullptr; } static std::unique_ptr genHTML(const std::vector &C) { auto CommentBlock = std::make_unique(HTMLTag::TAG_DIV); for (const auto &Child : C) { if (std::unique_ptr Node = genHTML(Child)) CommentBlock->Children.emplace_back(std::move(Node)); } return CommentBlock; } static std::vector> genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) { std::vector> Out; std::string EnumType = I.Scoped ? "enum class " : "enum "; // Determine if enum members have comments attached bool HasComments = std::any_of( I.Members.begin(), I.Members.end(), [](const EnumValueInfo &M) { return !M.Description.empty(); }); std::unique_ptr Table = std::make_unique(HTMLTag::TAG_TABLE); std::unique_ptr THead = std::make_unique(HTMLTag::TAG_THEAD); std::unique_ptr TRow = std::make_unique(HTMLTag::TAG_TR); std::unique_ptr TD = std::make_unique(HTMLTag::TAG_TH, EnumType + I.Name); // Span 3 columns if enum has comments TD->Attributes.emplace_back("colspan", HasComments ? "3" : "2"); Table->Attributes.emplace_back("id", llvm::toHex(llvm::toStringRef(I.USR))); TRow->Children.emplace_back(std::move(TD)); THead->Children.emplace_back(std::move(TRow)); Table->Children.emplace_back(std::move(THead)); if (std::unique_ptr Node = genEnumMembersBlock(I.Members)) Table->Children.emplace_back(std::move(Node)); Out.emplace_back(std::move(Table)); maybeWriteSourceFileRef(Out, CDCtx, I.DefLoc); if (!I.Description.empty()) Out.emplace_back(genHTML(I.Description)); return Out; } static std::vector> genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx, StringRef ParentInfoDir) { std::vector> Out; Out.emplace_back(std::make_unique(HTMLTag::TAG_H3, I.Name)); // USR is used as id for functions instead of name to disambiguate function // overloads. Out.back()->Attributes.emplace_back("id", llvm::toHex(llvm::toStringRef(I.USR))); Out.emplace_back(std::make_unique(HTMLTag::TAG_P)); auto &FunctionHeader = Out.back(); std::string Access = getAccessSpelling(I.Access).str(); if (Access != "") FunctionHeader->Children.emplace_back( std::make_unique(Access + " ")); if (I.ReturnType.Type.Name != "") { FunctionHeader->Children.emplace_back( genReference(I.ReturnType.Type, ParentInfoDir)); FunctionHeader->Children.emplace_back(std::make_unique(" ")); } FunctionHeader->Children.emplace_back( std::make_unique(I.Name + "(")); for (const auto &P : I.Params) { if (&P != I.Params.begin()) FunctionHeader->Children.emplace_back(std::make_unique(", ")); FunctionHeader->Children.emplace_back(genReference(P.Type, ParentInfoDir)); FunctionHeader->Children.emplace_back( std::make_unique(" " + P.Name)); } FunctionHeader->Children.emplace_back(std::make_unique(")")); maybeWriteSourceFileRef(Out, CDCtx, I.DefLoc); if (!I.Description.empty()) Out.emplace_back(genHTML(I.Description)); return Out; } static std::vector> genHTML(const NamespaceInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx, std::string &InfoTitle) { std::vector> Out; if (I.Name.str() == "") InfoTitle = "Global Namespace"; else InfoTitle = ("namespace " + I.Name).str(); Out.emplace_back(std::make_unique(HTMLTag::TAG_H1, InfoTitle)); if (!I.Description.empty()) Out.emplace_back(genHTML(I.Description)); llvm::SmallString<64> BasePath = I.getRelativeFilePath(""); std::vector> ChildNamespaces = genReferencesBlock(I.Children.Namespaces, "Namespaces", BasePath); appendVector(std::move(ChildNamespaces), Out); std::vector> ChildRecords = genReferencesBlock(I.Children.Records, "Records", BasePath); appendVector(std::move(ChildRecords), Out); std::vector> ChildFunctions = genFunctionsBlock(I.Children.Functions, CDCtx, BasePath); appendVector(std::move(ChildFunctions), Out); std::vector> ChildEnums = genEnumsBlock(I.Children.Enums, CDCtx); appendVector(std::move(ChildEnums), Out); if (!I.Children.Namespaces.empty()) InfoIndex.Children.emplace_back("Namespaces", "Namespaces"); if (!I.Children.Records.empty()) InfoIndex.Children.emplace_back("Records", "Records"); if (!I.Children.Functions.empty()) InfoIndex.Children.emplace_back( genInfoIndexItem(I.Children.Functions, "Functions")); if (!I.Children.Enums.empty()) InfoIndex.Children.emplace_back( genInfoIndexItem(I.Children.Enums, "Enums")); return Out; } static std::vector> genHTML(const RecordInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx, std::string &InfoTitle) { std::vector> Out; InfoTitle = (getTagType(I.TagType) + " " + I.Name).str(); Out.emplace_back(std::make_unique(HTMLTag::TAG_H1, InfoTitle)); maybeWriteSourceFileRef(Out, CDCtx, I.DefLoc); if (!I.Description.empty()) Out.emplace_back(genHTML(I.Description)); std::vector> Parents = genReferenceList(I.Parents, I.Path); std::vector> VParents = genReferenceList(I.VirtualParents, I.Path); if (!Parents.empty() || !VParents.empty()) { Out.emplace_back(std::make_unique(HTMLTag::TAG_P)); auto &PBody = Out.back(); PBody->Children.emplace_back(std::make_unique("Inherits from ")); if (Parents.empty()) appendVector(std::move(VParents), PBody->Children); else if (VParents.empty()) appendVector(std::move(Parents), PBody->Children); else { appendVector(std::move(Parents), PBody->Children); PBody->Children.emplace_back(std::make_unique(", ")); appendVector(std::move(VParents), PBody->Children); } } std::vector> Members = genRecordMembersBlock(I.Members, I.Path); appendVector(std::move(Members), Out); std::vector> ChildRecords = genReferencesBlock(I.Children.Records, "Records", I.Path); appendVector(std::move(ChildRecords), Out); std::vector> ChildFunctions = genFunctionsBlock(I.Children.Functions, CDCtx, I.Path); appendVector(std::move(ChildFunctions), Out); std::vector> ChildEnums = genEnumsBlock(I.Children.Enums, CDCtx); appendVector(std::move(ChildEnums), Out); if (!I.Members.empty()) InfoIndex.Children.emplace_back("Members", "Members"); if (!I.Children.Records.empty()) InfoIndex.Children.emplace_back("Records", "Records"); if (!I.Children.Functions.empty()) InfoIndex.Children.emplace_back( genInfoIndexItem(I.Children.Functions, "Functions")); if (!I.Children.Enums.empty()) InfoIndex.Children.emplace_back( genInfoIndexItem(I.Children.Enums, "Enums")); return Out; } static std::vector> genHTML(const TypedefInfo &I, const ClangDocContext &CDCtx, std::string &InfoTitle) { // TODO support typedefs in HTML. return {}; } /// Generator for HTML documentation. class HTMLGenerator : public Generator { public: static const char *Format; llvm::Error generateDocs(StringRef RootDir, llvm::StringMap> Infos, const ClangDocContext &CDCtx) override; llvm::Error createResources(ClangDocContext &CDCtx) override; llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, const ClangDocContext &CDCtx) override; }; const char *HTMLGenerator::Format = "html"; llvm::Error HTMLGenerator::generateDocs(StringRef RootDir, llvm::StringMap> Infos, const ClangDocContext &CDCtx) { // Track which directories we already tried to create. llvm::StringSet<> CreatedDirs; // Collect all output by file name and create the nexessary directories. llvm::StringMap> FileToInfos; for (const auto &Group : Infos) { doc::Info *Info = Group.getValue().get(); llvm::SmallString<128> Path; llvm::sys::path::native(RootDir, Path); llvm::sys::path::append(Path, Info->getRelativeFilePath("")); if (!CreatedDirs.contains(Path)) { if (std::error_code Err = llvm::sys::fs::create_directories(Path); Err != std::error_code()) { return llvm::createStringError(Err, "Failed to create directory '%s'.", Path.c_str()); } CreatedDirs.insert(Path); } llvm::sys::path::append(Path, Info->getFileBaseName() + ".html"); FileToInfos[Path].push_back(Info); } for (const auto &Group : FileToInfos) { std::error_code FileErr; llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr, llvm::sys::fs::OF_Text); if (FileErr) { return llvm::createStringError(FileErr, "Error opening file '%s'", Group.getKey().str().c_str()); } // TODO: https://github.com/llvm/llvm-project/issues/59073 // If there are multiple Infos for this file name (for example, template // specializations), this will generate multiple complete web pages (with // and , etc.) concatenated together. This generator needs // some refactoring to be able to output the headers separately from the // contents. for (const auto &Info : Group.getValue()) { if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) { return Err; } } } return llvm::Error::success(); } llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, const ClangDocContext &CDCtx) { std::string InfoTitle; std::vector<std::unique_ptr<TagNode>> MainContentNodes; Index InfoIndex; switch (I->IT) { case InfoType::IT_namespace: MainContentNodes = genHTML(*static_cast<clang::doc::NamespaceInfo *>(I), InfoIndex, CDCtx, InfoTitle); break; case InfoType::IT_record: MainContentNodes = genHTML(*static_cast<clang::doc::RecordInfo *>(I), InfoIndex, CDCtx, InfoTitle); break; case InfoType::IT_enum: MainContentNodes = genHTML(*static_cast<clang::doc::EnumInfo *>(I), CDCtx); break; case InfoType::IT_function: MainContentNodes = genHTML(*static_cast<clang::doc::FunctionInfo *>(I), CDCtx, ""); break; case InfoType::IT_typedef: MainContentNodes = genHTML(*static_cast<clang::doc::TypedefInfo *>(I), CDCtx, InfoTitle); break; case InfoType::IT_default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "unexpected info type"); } HTMLFile F = genInfoFile(InfoTitle, I->getRelativeFilePath(""), MainContentNodes, InfoIndex, CDCtx); F.render(OS); return llvm::Error::success(); } static std::string getRefType(InfoType IT) { switch (IT) { case InfoType::IT_default: return "default"; case InfoType::IT_namespace: return "namespace"; case InfoType::IT_record: return "record"; case InfoType::IT_function: return "function"; case InfoType::IT_enum: return "enum"; case InfoType::IT_typedef: return "typedef"; } llvm_unreachable("Unknown InfoType"); } static llvm::Error serializeIndex(ClangDocContext &CDCtx) { std::error_code OK; std::error_code FileErr; llvm::SmallString<128> FilePath; llvm::sys::path::native(CDCtx.OutDirectory, FilePath); llvm::sys::path::append(FilePath, "index_json.js"); llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_Text); if (FileErr != OK) { return llvm::createStringError(llvm::inconvertibleErrorCode(), "error creating index file: " + FileErr.message()); } llvm::SmallString<128> RootPath(CDCtx.OutDirectory); if (llvm::sys::path::is_relative(RootPath)) { llvm::sys::fs::make_absolute(RootPath); } // Replace the escaped characters with a forward slash. It shouldn't matter // when rendering the webpage in a web browser. This helps to prevent the // JavaScript from escaping characters incorrectly, and introducing bad paths // in the URLs. std::string RootPathEscaped = RootPath.str().str(); std::replace(RootPathEscaped.begin(), RootPathEscaped.end(), '\\', '/'); OS << "var RootPath = \"" << RootPathEscaped << "\";\n"; llvm::SmallString<128> Base(CDCtx.Base); std::string BaseEscaped = Base.str().str(); std::replace(BaseEscaped.begin(), BaseEscaped.end(), '\\', '/'); OS << "var Base = \"" << BaseEscaped << "\";\n"; CDCtx.Idx.sort(); llvm::json::OStream J(OS, 2); std::function<void(Index)> IndexToJSON = [&](const Index &I) { J.object([&] { J.attribute("USR", toHex(llvm::toStringRef(I.USR))); J.attribute("Name", I.Name); J.attribute("RefType", getRefType(I.RefType)); J.attribute("Path", I.getRelativeFilePath("")); J.attributeArray("Children", [&] { for (const Index &C : I.Children) IndexToJSON(C); }); }); }; OS << "async function LoadIndex() {\nreturn"; IndexToJSON(CDCtx.Idx); OS << ";\n}"; return llvm::Error::success(); } // Generates a main HTML node that has the main content of the file that shows // only the general index // It contains the general index with links to all the generated files static std::unique_ptr<TagNode> genIndexFileMainNode() { auto MainNode = std::make_unique<TagNode>(HTMLTag::TAG_MAIN); auto LeftSidebarNode = std::make_unique<TagNode>(HTMLTag::TAG_DIV); LeftSidebarNode->Attributes.emplace_back("id", "sidebar-left"); LeftSidebarNode->Attributes.emplace_back("path", ""); LeftSidebarNode->Attributes.emplace_back( "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left"); LeftSidebarNode->Attributes.emplace_back("style", "flex: 0 100%;"); MainNode->Children.emplace_back(std::move(LeftSidebarNode)); return MainNode; } static llvm::Error genIndex(const ClangDocContext &CDCtx) { std::error_code FileErr, OK; llvm::SmallString<128> IndexPath; llvm::sys::path::native(CDCtx.OutDirectory, IndexPath); llvm::sys::path::append(IndexPath, "index.html"); llvm::raw_fd_ostream IndexOS(IndexPath, FileErr, llvm::sys::fs::OF_Text); if (FileErr != OK) { return llvm::createStringError(llvm::inconvertibleErrorCode(), "error creating main index: " + FileErr.message()); } HTMLFile F; std::vector<std::unique_ptr<TagNode>> HeadNodes = genFileHeadNodes("Index", "", CDCtx); std::unique_ptr<TagNode> HeaderNode = genFileHeaderNode(CDCtx.ProjectName); std::unique_ptr<TagNode> MainNode = genIndexFileMainNode(); std::unique_ptr<TagNode> FooterNode = genFileFooterNode(); appendVector(std::move(HeadNodes), F.Children); F.Children.emplace_back(std::move(HeaderNode)); F.Children.emplace_back(std::move(MainNode)); F.Children.emplace_back(std::move(FooterNode)); F.render(IndexOS); return llvm::Error::success(); } llvm::Error HTMLGenerator::createResources(ClangDocContext &CDCtx) { auto Err = serializeIndex(CDCtx); if (Err) return Err; Err = genIndex(CDCtx); if (Err) return Err; for (const auto &FilePath : CDCtx.UserStylesheets) { Err = copyFile(FilePath, CDCtx.OutDirectory); if (Err) return Err; } for (const auto &FilePath : CDCtx.JsScripts) { Err = copyFile(FilePath, CDCtx.OutDirectory); if (Err) return Err; } return llvm::Error::success(); } static GeneratorRegistry::Add<HTMLGenerator> HTML(HTMLGenerator::Format, "Generator for HTML output."); // This anchor is used to force the linker to link in the generated object // file and thus register the generator. volatile int HTMLGeneratorAnchorSource = 0; } // namespace doc } // namespace clang