1 //===-- HTMLGenerator.cpp - HTML Generator ----------------------*- C++ -*-===// 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 9 #include "Generators.h" 10 #include "Representation.h" 11 #include "clang/Basic/Version.h" 12 #include "llvm/ADT/StringExtras.h" 13 #include "llvm/ADT/StringRef.h" 14 #include "llvm/ADT/StringSet.h" 15 #include "llvm/Support/FileSystem.h" 16 #include "llvm/Support/JSON.h" 17 #include "llvm/Support/Path.h" 18 #include "llvm/Support/raw_ostream.h" 19 #include <algorithm> 20 #include <optional> 21 #include <string> 22 23 using namespace llvm; 24 25 namespace clang { 26 namespace doc { 27 28 namespace { 29 30 class HTMLTag { 31 public: 32 // Any other tag can be added if required 33 enum TagType { 34 TAG_A, 35 TAG_DIV, 36 TAG_FOOTER, 37 TAG_H1, 38 TAG_H2, 39 TAG_H3, 40 TAG_HEADER, 41 TAG_LI, 42 TAG_LINK, 43 TAG_MAIN, 44 TAG_META, 45 TAG_OL, 46 TAG_P, 47 TAG_SCRIPT, 48 TAG_SPAN, 49 TAG_TITLE, 50 TAG_UL, 51 }; 52 53 HTMLTag() = default; 54 constexpr HTMLTag(TagType Value) : Value(Value) {} 55 56 operator TagType() const { return Value; } 57 operator bool() = delete; 58 59 bool isSelfClosing() const; 60 StringRef toString() const; 61 62 private: 63 TagType Value; 64 }; 65 66 enum NodeType { 67 NODE_TEXT, 68 NODE_TAG, 69 }; 70 71 struct HTMLNode { 72 HTMLNode(NodeType Type) : Type(Type) {} 73 virtual ~HTMLNode() = default; 74 75 virtual void render(llvm::raw_ostream &OS, int IndentationLevel) = 0; 76 NodeType Type; // Type of node 77 }; 78 79 struct TextNode : public HTMLNode { 80 TextNode(const Twine &Text) 81 : HTMLNode(NodeType::NODE_TEXT), Text(Text.str()) {} 82 83 std::string Text; // Content of node 84 void render(llvm::raw_ostream &OS, int IndentationLevel) override; 85 }; 86 87 struct TagNode : public HTMLNode { 88 TagNode(HTMLTag Tag) : HTMLNode(NodeType::NODE_TAG), Tag(Tag) {} 89 TagNode(HTMLTag Tag, const Twine &Text) : TagNode(Tag) { 90 Children.emplace_back(std::make_unique<TextNode>(Text.str())); 91 } 92 93 HTMLTag Tag; // Name of HTML Tag (p, div, h1) 94 std::vector<std::unique_ptr<HTMLNode>> Children; // List of child nodes 95 std::vector<std::pair<std::string, std::string>> 96 Attributes; // List of key-value attributes for tag 97 98 void render(llvm::raw_ostream &OS, int IndentationLevel) override; 99 }; 100 101 constexpr const char *kDoctypeDecl = "<!DOCTYPE html>"; 102 103 struct HTMLFile { 104 std::vector<std::unique_ptr<HTMLNode>> Children; // List of child nodes 105 void render(llvm::raw_ostream &OS) { 106 OS << kDoctypeDecl << "\n"; 107 for (const auto &C : Children) { 108 C->render(OS, 0); 109 OS << "\n"; 110 } 111 } 112 }; 113 114 } // namespace 115 116 bool HTMLTag::isSelfClosing() const { 117 switch (Value) { 118 case HTMLTag::TAG_META: 119 case HTMLTag::TAG_LINK: 120 return true; 121 case HTMLTag::TAG_A: 122 case HTMLTag::TAG_DIV: 123 case HTMLTag::TAG_FOOTER: 124 case HTMLTag::TAG_H1: 125 case HTMLTag::TAG_H2: 126 case HTMLTag::TAG_H3: 127 case HTMLTag::TAG_HEADER: 128 case HTMLTag::TAG_LI: 129 case HTMLTag::TAG_MAIN: 130 case HTMLTag::TAG_OL: 131 case HTMLTag::TAG_P: 132 case HTMLTag::TAG_SCRIPT: 133 case HTMLTag::TAG_SPAN: 134 case HTMLTag::TAG_TITLE: 135 case HTMLTag::TAG_UL: 136 return false; 137 } 138 llvm_unreachable("Unhandled HTMLTag::TagType"); 139 } 140 141 StringRef HTMLTag::toString() const { 142 switch (Value) { 143 case HTMLTag::TAG_A: 144 return "a"; 145 case HTMLTag::TAG_DIV: 146 return "div"; 147 case HTMLTag::TAG_FOOTER: 148 return "footer"; 149 case HTMLTag::TAG_H1: 150 return "h1"; 151 case HTMLTag::TAG_H2: 152 return "h2"; 153 case HTMLTag::TAG_H3: 154 return "h3"; 155 case HTMLTag::TAG_HEADER: 156 return "header"; 157 case HTMLTag::TAG_LI: 158 return "li"; 159 case HTMLTag::TAG_LINK: 160 return "link"; 161 case HTMLTag::TAG_MAIN: 162 return "main"; 163 case HTMLTag::TAG_META: 164 return "meta"; 165 case HTMLTag::TAG_OL: 166 return "ol"; 167 case HTMLTag::TAG_P: 168 return "p"; 169 case HTMLTag::TAG_SCRIPT: 170 return "script"; 171 case HTMLTag::TAG_SPAN: 172 return "span"; 173 case HTMLTag::TAG_TITLE: 174 return "title"; 175 case HTMLTag::TAG_UL: 176 return "ul"; 177 } 178 llvm_unreachable("Unhandled HTMLTag::TagType"); 179 } 180 181 void TextNode::render(llvm::raw_ostream &OS, int IndentationLevel) { 182 OS.indent(IndentationLevel * 2); 183 printHTMLEscaped(Text, OS); 184 } 185 186 void TagNode::render(llvm::raw_ostream &OS, int IndentationLevel) { 187 // Children nodes are rendered in the same line if all of them are text nodes 188 bool InlineChildren = true; 189 for (const auto &C : Children) 190 if (C->Type == NodeType::NODE_TAG) { 191 InlineChildren = false; 192 break; 193 } 194 OS.indent(IndentationLevel * 2); 195 OS << "<" << Tag.toString(); 196 for (const auto &A : Attributes) 197 OS << " " << A.first << "=\"" << A.second << "\""; 198 if (Tag.isSelfClosing()) { 199 OS << "/>"; 200 return; 201 } 202 OS << ">"; 203 if (!InlineChildren) 204 OS << "\n"; 205 bool NewLineRendered = true; 206 for (const auto &C : Children) { 207 int ChildrenIndentation = 208 InlineChildren || !NewLineRendered ? 0 : IndentationLevel + 1; 209 C->render(OS, ChildrenIndentation); 210 if (!InlineChildren && (C == Children.back() || 211 (C->Type != NodeType::NODE_TEXT || 212 (&C + 1)->get()->Type != NodeType::NODE_TEXT))) { 213 OS << "\n"; 214 NewLineRendered = true; 215 } else 216 NewLineRendered = false; 217 } 218 if (!InlineChildren) 219 OS.indent(IndentationLevel * 2); 220 OS << "</" << Tag.toString() << ">"; 221 } 222 223 template <typename Derived, typename Base, 224 typename = std::enable_if<std::is_base_of<Derived, Base>::value>> 225 static void appendVector(std::vector<Derived> &&New, 226 std::vector<Base> &Original) { 227 std::move(New.begin(), New.end(), std::back_inserter(Original)); 228 } 229 230 // Compute the relative path from an Origin directory to a Destination directory 231 static SmallString<128> computeRelativePath(StringRef Destination, 232 StringRef Origin) { 233 // If Origin is empty, the relative path to the Destination is its complete 234 // path. 235 if (Origin.empty()) 236 return Destination; 237 238 // The relative path is an empty path if both directories are the same. 239 if (Destination == Origin) 240 return {}; 241 242 // These iterators iterate through each of their parent directories 243 llvm::sys::path::const_iterator FileI = llvm::sys::path::begin(Destination); 244 llvm::sys::path::const_iterator FileE = llvm::sys::path::end(Destination); 245 llvm::sys::path::const_iterator DirI = llvm::sys::path::begin(Origin); 246 llvm::sys::path::const_iterator DirE = llvm::sys::path::end(Origin); 247 // Advance both iterators until the paths differ. Example: 248 // Destination = A/B/C/D 249 // Origin = A/B/E/F 250 // FileI will point to C and DirI to E. The directories behind them is the 251 // directory they share (A/B). 252 while (FileI != FileE && DirI != DirE && *FileI == *DirI) { 253 ++FileI; 254 ++DirI; 255 } 256 SmallString<128> Result; // This will hold the resulting path. 257 // Result has to go up one directory for each of the remaining directories in 258 // Origin 259 while (DirI != DirE) { 260 llvm::sys::path::append(Result, ".."); 261 ++DirI; 262 } 263 // Result has to append each of the remaining directories in Destination 264 while (FileI != FileE) { 265 llvm::sys::path::append(Result, *FileI); 266 ++FileI; 267 } 268 return Result; 269 } 270 271 // HTML generation 272 273 static std::vector<std::unique_ptr<TagNode>> 274 genStylesheetsHTML(StringRef InfoPath, const ClangDocContext &CDCtx) { 275 std::vector<std::unique_ptr<TagNode>> Out; 276 for (const auto &FilePath : CDCtx.UserStylesheets) { 277 auto LinkNode = std::make_unique<TagNode>(HTMLTag::TAG_LINK); 278 LinkNode->Attributes.emplace_back("rel", "stylesheet"); 279 SmallString<128> StylesheetPath = computeRelativePath("", InfoPath); 280 llvm::sys::path::append(StylesheetPath, 281 llvm::sys::path::filename(FilePath)); 282 // Paths in HTML must be in posix-style 283 llvm::sys::path::native(StylesheetPath, llvm::sys::path::Style::posix); 284 LinkNode->Attributes.emplace_back("href", std::string(StylesheetPath)); 285 Out.emplace_back(std::move(LinkNode)); 286 } 287 return Out; 288 } 289 290 static std::vector<std::unique_ptr<TagNode>> 291 genJsScriptsHTML(StringRef InfoPath, const ClangDocContext &CDCtx) { 292 std::vector<std::unique_ptr<TagNode>> Out; 293 294 // index_json.js is part of every generated HTML file 295 SmallString<128> IndexJSONPath = computeRelativePath("", InfoPath); 296 auto IndexJSONNode = std::make_unique<TagNode>(HTMLTag::TAG_SCRIPT); 297 llvm::sys::path::append(IndexJSONPath, "index_json.js"); 298 llvm::sys::path::native(IndexJSONPath, llvm::sys::path::Style::posix); 299 IndexJSONNode->Attributes.emplace_back("src", std::string(IndexJSONPath)); 300 Out.emplace_back(std::move(IndexJSONNode)); 301 302 for (const auto &FilePath : CDCtx.JsScripts) { 303 SmallString<128> ScriptPath = computeRelativePath("", InfoPath); 304 auto ScriptNode = std::make_unique<TagNode>(HTMLTag::TAG_SCRIPT); 305 llvm::sys::path::append(ScriptPath, llvm::sys::path::filename(FilePath)); 306 // Paths in HTML must be in posix-style 307 llvm::sys::path::native(ScriptPath, llvm::sys::path::Style::posix); 308 ScriptNode->Attributes.emplace_back("src", std::string(ScriptPath)); 309 Out.emplace_back(std::move(ScriptNode)); 310 } 311 return Out; 312 } 313 314 static std::unique_ptr<TagNode> genLink(const Twine &Text, const Twine &Link) { 315 auto LinkNode = std::make_unique<TagNode>(HTMLTag::TAG_A, Text); 316 LinkNode->Attributes.emplace_back("href", Link.str()); 317 return LinkNode; 318 } 319 320 static std::unique_ptr<HTMLNode> 321 genReference(const Reference &Type, StringRef CurrentDirectory, 322 std::optional<StringRef> JumpToSection = std::nullopt) { 323 if (Type.Path.empty()) { 324 if (!JumpToSection) 325 return std::make_unique<TextNode>(Type.Name); 326 return genLink(Type.Name, "#" + *JumpToSection); 327 } 328 llvm::SmallString<64> Path = Type.getRelativeFilePath(CurrentDirectory); 329 llvm::sys::path::append(Path, Type.getFileBaseName() + ".html"); 330 331 // Paths in HTML must be in posix-style 332 llvm::sys::path::native(Path, llvm::sys::path::Style::posix); 333 if (JumpToSection) 334 Path += ("#" + *JumpToSection).str(); 335 return genLink(Type.Name, Path); 336 } 337 338 static std::vector<std::unique_ptr<HTMLNode>> 339 genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs, 340 const StringRef &CurrentDirectory) { 341 std::vector<std::unique_ptr<HTMLNode>> Out; 342 for (const auto &R : Refs) { 343 if (&R != Refs.begin()) 344 Out.emplace_back(std::make_unique<TextNode>(", ")); 345 Out.emplace_back(genReference(R, CurrentDirectory)); 346 } 347 return Out; 348 } 349 350 static std::vector<std::unique_ptr<TagNode>> 351 genHTML(const EnumInfo &I, const ClangDocContext &CDCtx); 352 static std::vector<std::unique_ptr<TagNode>> 353 genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx, 354 StringRef ParentInfoDir); 355 356 static std::vector<std::unique_ptr<TagNode>> 357 genEnumsBlock(const std::vector<EnumInfo> &Enums, 358 const ClangDocContext &CDCtx) { 359 if (Enums.empty()) 360 return {}; 361 362 std::vector<std::unique_ptr<TagNode>> Out; 363 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H2, "Enums")); 364 Out.back()->Attributes.emplace_back("id", "Enums"); 365 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_DIV)); 366 auto &DivBody = Out.back(); 367 for (const auto &E : Enums) { 368 std::vector<std::unique_ptr<TagNode>> Nodes = genHTML(E, CDCtx); 369 appendVector(std::move(Nodes), DivBody->Children); 370 } 371 return Out; 372 } 373 374 static std::unique_ptr<TagNode> 375 genEnumMembersBlock(const llvm::SmallVector<EnumValueInfo, 4> &Members) { 376 if (Members.empty()) 377 return nullptr; 378 379 auto List = std::make_unique<TagNode>(HTMLTag::TAG_UL); 380 for (const auto &M : Members) 381 List->Children.emplace_back( 382 std::make_unique<TagNode>(HTMLTag::TAG_LI, M.Name)); 383 return List; 384 } 385 386 static std::vector<std::unique_ptr<TagNode>> 387 genFunctionsBlock(const std::vector<FunctionInfo> &Functions, 388 const ClangDocContext &CDCtx, StringRef ParentInfoDir) { 389 if (Functions.empty()) 390 return {}; 391 392 std::vector<std::unique_ptr<TagNode>> Out; 393 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H2, "Functions")); 394 Out.back()->Attributes.emplace_back("id", "Functions"); 395 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_DIV)); 396 auto &DivBody = Out.back(); 397 for (const auto &F : Functions) { 398 std::vector<std::unique_ptr<TagNode>> Nodes = 399 genHTML(F, CDCtx, ParentInfoDir); 400 appendVector(std::move(Nodes), DivBody->Children); 401 } 402 return Out; 403 } 404 405 static std::vector<std::unique_ptr<TagNode>> 406 genRecordMembersBlock(const llvm::SmallVector<MemberTypeInfo, 4> &Members, 407 StringRef ParentInfoDir) { 408 if (Members.empty()) 409 return {}; 410 411 std::vector<std::unique_ptr<TagNode>> Out; 412 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H2, "Members")); 413 Out.back()->Attributes.emplace_back("id", "Members"); 414 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_UL)); 415 auto &ULBody = Out.back(); 416 for (const auto &M : Members) { 417 std::string Access = getAccessSpelling(M.Access).str(); 418 if (Access != "") 419 Access = Access + " "; 420 auto LIBody = std::make_unique<TagNode>(HTMLTag::TAG_LI); 421 LIBody->Children.emplace_back(std::make_unique<TextNode>(Access)); 422 LIBody->Children.emplace_back(genReference(M.Type, ParentInfoDir)); 423 LIBody->Children.emplace_back(std::make_unique<TextNode>(" " + M.Name)); 424 ULBody->Children.emplace_back(std::move(LIBody)); 425 } 426 return Out; 427 } 428 429 static std::vector<std::unique_ptr<TagNode>> 430 genReferencesBlock(const std::vector<Reference> &References, 431 llvm::StringRef Title, StringRef ParentPath) { 432 if (References.empty()) 433 return {}; 434 435 std::vector<std::unique_ptr<TagNode>> Out; 436 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H2, Title)); 437 Out.back()->Attributes.emplace_back("id", std::string(Title)); 438 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_UL)); 439 auto &ULBody = Out.back(); 440 for (const auto &R : References) { 441 auto LiNode = std::make_unique<TagNode>(HTMLTag::TAG_LI); 442 LiNode->Children.emplace_back(genReference(R, ParentPath)); 443 ULBody->Children.emplace_back(std::move(LiNode)); 444 } 445 return Out; 446 } 447 448 static std::unique_ptr<TagNode> 449 writeFileDefinition(const Location &L, 450 std::optional<StringRef> RepositoryUrl = std::nullopt) { 451 if (!L.IsFileInRootDir || !RepositoryUrl) 452 return std::make_unique<TagNode>( 453 HTMLTag::TAG_P, "Defined at line " + std::to_string(L.LineNumber) + 454 " of file " + L.Filename); 455 SmallString<128> FileURL(*RepositoryUrl); 456 llvm::sys::path::append(FileURL, llvm::sys::path::Style::posix, L.Filename); 457 auto Node = std::make_unique<TagNode>(HTMLTag::TAG_P); 458 Node->Children.emplace_back(std::make_unique<TextNode>("Defined at line ")); 459 auto LocNumberNode = 460 std::make_unique<TagNode>(HTMLTag::TAG_A, std::to_string(L.LineNumber)); 461 // The links to a specific line in the source code use the github / 462 // googlesource notation so it won't work for all hosting pages. 463 LocNumberNode->Attributes.emplace_back( 464 "href", (FileURL + "#" + std::to_string(L.LineNumber)).str()); 465 Node->Children.emplace_back(std::move(LocNumberNode)); 466 Node->Children.emplace_back(std::make_unique<TextNode>(" of file ")); 467 auto LocFileNode = std::make_unique<TagNode>( 468 HTMLTag::TAG_A, llvm::sys::path::filename(FileURL)); 469 LocFileNode->Attributes.emplace_back("href", std::string(FileURL)); 470 Node->Children.emplace_back(std::move(LocFileNode)); 471 return Node; 472 } 473 474 static std::vector<std::unique_ptr<TagNode>> 475 genHTML(const Index &Index, StringRef InfoPath, bool IsOutermostList); 476 477 // Generates a list of child nodes for the HTML head tag 478 // It contains a meta node, link nodes to import CSS files, and script nodes to 479 // import JS files 480 static std::vector<std::unique_ptr<TagNode>> 481 genFileHeadNodes(StringRef Title, StringRef InfoPath, 482 const ClangDocContext &CDCtx) { 483 std::vector<std::unique_ptr<TagNode>> Out; 484 auto MetaNode = std::make_unique<TagNode>(HTMLTag::TAG_META); 485 MetaNode->Attributes.emplace_back("charset", "utf-8"); 486 Out.emplace_back(std::move(MetaNode)); 487 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_TITLE, Title)); 488 std::vector<std::unique_ptr<TagNode>> StylesheetsNodes = 489 genStylesheetsHTML(InfoPath, CDCtx); 490 appendVector(std::move(StylesheetsNodes), Out); 491 std::vector<std::unique_ptr<TagNode>> JsNodes = 492 genJsScriptsHTML(InfoPath, CDCtx); 493 appendVector(std::move(JsNodes), Out); 494 return Out; 495 } 496 497 // Generates a header HTML node that can be used for any file 498 // It contains the project name 499 static std::unique_ptr<TagNode> genFileHeaderNode(StringRef ProjectName) { 500 auto HeaderNode = std::make_unique<TagNode>(HTMLTag::TAG_HEADER, ProjectName); 501 HeaderNode->Attributes.emplace_back("id", "project-title"); 502 return HeaderNode; 503 } 504 505 // Generates a main HTML node that has all the main content of an info file 506 // It contains both indexes and the info's documented information 507 // This function should only be used for the info files (not for the file that 508 // only has the general index) 509 static std::unique_ptr<TagNode> genInfoFileMainNode( 510 StringRef InfoPath, 511 std::vector<std::unique_ptr<TagNode>> &MainContentInnerNodes, 512 const Index &InfoIndex) { 513 auto MainNode = std::make_unique<TagNode>(HTMLTag::TAG_MAIN); 514 515 auto LeftSidebarNode = std::make_unique<TagNode>(HTMLTag::TAG_DIV); 516 LeftSidebarNode->Attributes.emplace_back("id", "sidebar-left"); 517 LeftSidebarNode->Attributes.emplace_back("path", std::string(InfoPath)); 518 LeftSidebarNode->Attributes.emplace_back( 519 "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left"); 520 521 auto MainContentNode = std::make_unique<TagNode>(HTMLTag::TAG_DIV); 522 MainContentNode->Attributes.emplace_back("id", "main-content"); 523 MainContentNode->Attributes.emplace_back( 524 "class", "col-xs-12 col-sm-9 col-md-8 main-content"); 525 appendVector(std::move(MainContentInnerNodes), MainContentNode->Children); 526 527 auto RightSidebarNode = std::make_unique<TagNode>(HTMLTag::TAG_DIV); 528 RightSidebarNode->Attributes.emplace_back("id", "sidebar-right"); 529 RightSidebarNode->Attributes.emplace_back( 530 "class", "col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right"); 531 std::vector<std::unique_ptr<TagNode>> InfoIndexHTML = 532 genHTML(InfoIndex, InfoPath, true); 533 appendVector(std::move(InfoIndexHTML), RightSidebarNode->Children); 534 535 MainNode->Children.emplace_back(std::move(LeftSidebarNode)); 536 MainNode->Children.emplace_back(std::move(MainContentNode)); 537 MainNode->Children.emplace_back(std::move(RightSidebarNode)); 538 539 return MainNode; 540 } 541 542 // Generates a footer HTML node that can be used for any file 543 // It contains clang-doc's version 544 static std::unique_ptr<TagNode> genFileFooterNode() { 545 auto FooterNode = std::make_unique<TagNode>(HTMLTag::TAG_FOOTER); 546 auto SpanNode = std::make_unique<TagNode>( 547 HTMLTag::TAG_SPAN, clang::getClangToolFullVersion("clang-doc")); 548 SpanNode->Attributes.emplace_back("class", "no-break"); 549 FooterNode->Children.emplace_back(std::move(SpanNode)); 550 return FooterNode; 551 } 552 553 // Generates a complete HTMLFile for an Info 554 static HTMLFile 555 genInfoFile(StringRef Title, StringRef InfoPath, 556 std::vector<std::unique_ptr<TagNode>> &MainContentNodes, 557 const Index &InfoIndex, const ClangDocContext &CDCtx) { 558 HTMLFile F; 559 560 std::vector<std::unique_ptr<TagNode>> HeadNodes = 561 genFileHeadNodes(Title, InfoPath, CDCtx); 562 std::unique_ptr<TagNode> HeaderNode = genFileHeaderNode(CDCtx.ProjectName); 563 std::unique_ptr<TagNode> MainNode = 564 genInfoFileMainNode(InfoPath, MainContentNodes, InfoIndex); 565 std::unique_ptr<TagNode> FooterNode = genFileFooterNode(); 566 567 appendVector(std::move(HeadNodes), F.Children); 568 F.Children.emplace_back(std::move(HeaderNode)); 569 F.Children.emplace_back(std::move(MainNode)); 570 F.Children.emplace_back(std::move(FooterNode)); 571 572 return F; 573 } 574 575 template <typename T, 576 typename = std::enable_if<std::is_base_of<T, Info>::value>> 577 static Index genInfoIndexItem(const std::vector<T> &Infos, StringRef Title) { 578 Index Idx(Title, Title); 579 for (const auto &C : Infos) 580 Idx.Children.emplace_back(C.extractName(), 581 llvm::toHex(llvm::toStringRef(C.USR))); 582 return Idx; 583 } 584 585 static std::vector<std::unique_ptr<TagNode>> 586 genHTML(const Index &Index, StringRef InfoPath, bool IsOutermostList) { 587 std::vector<std::unique_ptr<TagNode>> Out; 588 if (!Index.Name.empty()) { 589 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_SPAN)); 590 auto &SpanBody = Out.back(); 591 if (!Index.JumpToSection) 592 SpanBody->Children.emplace_back(genReference(Index, InfoPath)); 593 else 594 SpanBody->Children.emplace_back( 595 genReference(Index, InfoPath, Index.JumpToSection->str())); 596 } 597 if (Index.Children.empty()) 598 return Out; 599 // Only the outermost list should use ol, the others should use ul 600 HTMLTag ListHTMLTag = IsOutermostList ? HTMLTag::TAG_OL : HTMLTag::TAG_UL; 601 Out.emplace_back(std::make_unique<TagNode>(ListHTMLTag)); 602 const auto &UlBody = Out.back(); 603 for (const auto &C : Index.Children) { 604 auto LiBody = std::make_unique<TagNode>(HTMLTag::TAG_LI); 605 std::vector<std::unique_ptr<TagNode>> Nodes = genHTML(C, InfoPath, false); 606 appendVector(std::move(Nodes), LiBody->Children); 607 UlBody->Children.emplace_back(std::move(LiBody)); 608 } 609 return Out; 610 } 611 612 static std::unique_ptr<HTMLNode> genHTML(const CommentInfo &I) { 613 if (I.Kind == "FullComment") { 614 auto FullComment = std::make_unique<TagNode>(HTMLTag::TAG_DIV); 615 for (const auto &Child : I.Children) { 616 std::unique_ptr<HTMLNode> Node = genHTML(*Child); 617 if (Node) 618 FullComment->Children.emplace_back(std::move(Node)); 619 } 620 return std::move(FullComment); 621 } 622 623 if (I.Kind == "ParagraphComment") { 624 auto ParagraphComment = std::make_unique<TagNode>(HTMLTag::TAG_P); 625 for (const auto &Child : I.Children) { 626 std::unique_ptr<HTMLNode> Node = genHTML(*Child); 627 if (Node) 628 ParagraphComment->Children.emplace_back(std::move(Node)); 629 } 630 if (ParagraphComment->Children.empty()) 631 return nullptr; 632 return std::move(ParagraphComment); 633 } 634 635 if (I.Kind == "TextComment") { 636 if (I.Text == "") 637 return nullptr; 638 return std::make_unique<TextNode>(I.Text); 639 } 640 return nullptr; 641 } 642 643 static std::unique_ptr<TagNode> genHTML(const std::vector<CommentInfo> &C) { 644 auto CommentBlock = std::make_unique<TagNode>(HTMLTag::TAG_DIV); 645 for (const auto &Child : C) { 646 if (std::unique_ptr<HTMLNode> Node = genHTML(Child)) 647 CommentBlock->Children.emplace_back(std::move(Node)); 648 } 649 return CommentBlock; 650 } 651 652 static std::vector<std::unique_ptr<TagNode>> 653 genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) { 654 std::vector<std::unique_ptr<TagNode>> Out; 655 std::string EnumType = I.Scoped ? "enum class " : "enum "; 656 657 Out.emplace_back( 658 std::make_unique<TagNode>(HTMLTag::TAG_H3, EnumType + I.Name)); 659 Out.back()->Attributes.emplace_back("id", 660 llvm::toHex(llvm::toStringRef(I.USR))); 661 662 std::unique_ptr<TagNode> Node = genEnumMembersBlock(I.Members); 663 if (Node) 664 Out.emplace_back(std::move(Node)); 665 666 if (I.DefLoc) { 667 if (!CDCtx.RepositoryUrl) 668 Out.emplace_back(writeFileDefinition(*I.DefLoc)); 669 else 670 Out.emplace_back(writeFileDefinition( 671 *I.DefLoc, StringRef{*CDCtx.RepositoryUrl})); 672 } 673 674 std::string Description; 675 if (!I.Description.empty()) 676 Out.emplace_back(genHTML(I.Description)); 677 678 return Out; 679 } 680 681 static std::vector<std::unique_ptr<TagNode>> 682 genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx, 683 StringRef ParentInfoDir) { 684 std::vector<std::unique_ptr<TagNode>> Out; 685 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H3, I.Name)); 686 // USR is used as id for functions instead of name to disambiguate function 687 // overloads. 688 Out.back()->Attributes.emplace_back("id", 689 llvm::toHex(llvm::toStringRef(I.USR))); 690 691 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_P)); 692 auto &FunctionHeader = Out.back(); 693 694 std::string Access = getAccessSpelling(I.Access).str(); 695 if (Access != "") 696 FunctionHeader->Children.emplace_back( 697 std::make_unique<TextNode>(Access + " ")); 698 if (I.ReturnType.Type.Name != "") { 699 FunctionHeader->Children.emplace_back( 700 genReference(I.ReturnType.Type, ParentInfoDir)); 701 FunctionHeader->Children.emplace_back(std::make_unique<TextNode>(" ")); 702 } 703 FunctionHeader->Children.emplace_back( 704 std::make_unique<TextNode>(I.Name + "(")); 705 706 for (const auto &P : I.Params) { 707 if (&P != I.Params.begin()) 708 FunctionHeader->Children.emplace_back(std::make_unique<TextNode>(", ")); 709 FunctionHeader->Children.emplace_back(genReference(P.Type, ParentInfoDir)); 710 FunctionHeader->Children.emplace_back( 711 std::make_unique<TextNode>(" " + P.Name)); 712 } 713 FunctionHeader->Children.emplace_back(std::make_unique<TextNode>(")")); 714 715 if (I.DefLoc) { 716 if (!CDCtx.RepositoryUrl) 717 Out.emplace_back(writeFileDefinition(*I.DefLoc)); 718 else 719 Out.emplace_back(writeFileDefinition( 720 *I.DefLoc, StringRef{*CDCtx.RepositoryUrl})); 721 } 722 723 std::string Description; 724 if (!I.Description.empty()) 725 Out.emplace_back(genHTML(I.Description)); 726 727 return Out; 728 } 729 730 static std::vector<std::unique_ptr<TagNode>> 731 genHTML(const NamespaceInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx, 732 std::string &InfoTitle) { 733 std::vector<std::unique_ptr<TagNode>> Out; 734 if (I.Name.str() == "") 735 InfoTitle = "Global Namespace"; 736 else 737 InfoTitle = ("namespace " + I.Name).str(); 738 739 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H1, InfoTitle)); 740 741 std::string Description; 742 if (!I.Description.empty()) 743 Out.emplace_back(genHTML(I.Description)); 744 745 llvm::SmallString<64> BasePath = I.getRelativeFilePath(""); 746 747 std::vector<std::unique_ptr<TagNode>> ChildNamespaces = 748 genReferencesBlock(I.Children.Namespaces, "Namespaces", BasePath); 749 appendVector(std::move(ChildNamespaces), Out); 750 std::vector<std::unique_ptr<TagNode>> ChildRecords = 751 genReferencesBlock(I.Children.Records, "Records", BasePath); 752 appendVector(std::move(ChildRecords), Out); 753 754 std::vector<std::unique_ptr<TagNode>> ChildFunctions = 755 genFunctionsBlock(I.Children.Functions, CDCtx, BasePath); 756 appendVector(std::move(ChildFunctions), Out); 757 std::vector<std::unique_ptr<TagNode>> ChildEnums = 758 genEnumsBlock(I.Children.Enums, CDCtx); 759 appendVector(std::move(ChildEnums), Out); 760 761 if (!I.Children.Namespaces.empty()) 762 InfoIndex.Children.emplace_back("Namespaces", "Namespaces"); 763 if (!I.Children.Records.empty()) 764 InfoIndex.Children.emplace_back("Records", "Records"); 765 if (!I.Children.Functions.empty()) 766 InfoIndex.Children.emplace_back( 767 genInfoIndexItem(I.Children.Functions, "Functions")); 768 if (!I.Children.Enums.empty()) 769 InfoIndex.Children.emplace_back( 770 genInfoIndexItem(I.Children.Enums, "Enums")); 771 772 return Out; 773 } 774 775 static std::vector<std::unique_ptr<TagNode>> 776 genHTML(const RecordInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx, 777 std::string &InfoTitle) { 778 std::vector<std::unique_ptr<TagNode>> Out; 779 InfoTitle = (getTagType(I.TagType) + " " + I.Name).str(); 780 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H1, InfoTitle)); 781 782 if (I.DefLoc) { 783 if (!CDCtx.RepositoryUrl) 784 Out.emplace_back(writeFileDefinition(*I.DefLoc)); 785 else 786 Out.emplace_back(writeFileDefinition( 787 *I.DefLoc, StringRef{*CDCtx.RepositoryUrl})); 788 } 789 790 std::string Description; 791 if (!I.Description.empty()) 792 Out.emplace_back(genHTML(I.Description)); 793 794 std::vector<std::unique_ptr<HTMLNode>> Parents = 795 genReferenceList(I.Parents, I.Path); 796 std::vector<std::unique_ptr<HTMLNode>> VParents = 797 genReferenceList(I.VirtualParents, I.Path); 798 if (!Parents.empty() || !VParents.empty()) { 799 Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_P)); 800 auto &PBody = Out.back(); 801 PBody->Children.emplace_back(std::make_unique<TextNode>("Inherits from ")); 802 if (Parents.empty()) 803 appendVector(std::move(VParents), PBody->Children); 804 else if (VParents.empty()) 805 appendVector(std::move(Parents), PBody->Children); 806 else { 807 appendVector(std::move(Parents), PBody->Children); 808 PBody->Children.emplace_back(std::make_unique<TextNode>(", ")); 809 appendVector(std::move(VParents), PBody->Children); 810 } 811 } 812 813 std::vector<std::unique_ptr<TagNode>> Members = 814 genRecordMembersBlock(I.Members, I.Path); 815 appendVector(std::move(Members), Out); 816 std::vector<std::unique_ptr<TagNode>> ChildRecords = 817 genReferencesBlock(I.Children.Records, "Records", I.Path); 818 appendVector(std::move(ChildRecords), Out); 819 820 std::vector<std::unique_ptr<TagNode>> ChildFunctions = 821 genFunctionsBlock(I.Children.Functions, CDCtx, I.Path); 822 appendVector(std::move(ChildFunctions), Out); 823 std::vector<std::unique_ptr<TagNode>> ChildEnums = 824 genEnumsBlock(I.Children.Enums, CDCtx); 825 appendVector(std::move(ChildEnums), Out); 826 827 if (!I.Members.empty()) 828 InfoIndex.Children.emplace_back("Members", "Members"); 829 if (!I.Children.Records.empty()) 830 InfoIndex.Children.emplace_back("Records", "Records"); 831 if (!I.Children.Functions.empty()) 832 InfoIndex.Children.emplace_back( 833 genInfoIndexItem(I.Children.Functions, "Functions")); 834 if (!I.Children.Enums.empty()) 835 InfoIndex.Children.emplace_back( 836 genInfoIndexItem(I.Children.Enums, "Enums")); 837 838 return Out; 839 } 840 841 static std::vector<std::unique_ptr<TagNode>> 842 genHTML(const TypedefInfo &I, const ClangDocContext &CDCtx, 843 std::string &InfoTitle) { 844 // TODO support typedefs in HTML. 845 return {}; 846 } 847 848 /// Generator for HTML documentation. 849 class HTMLGenerator : public Generator { 850 public: 851 static const char *Format; 852 853 llvm::Error generateDocs(StringRef RootDir, 854 llvm::StringMap<std::unique_ptr<doc::Info>> Infos, 855 const ClangDocContext &CDCtx) override; 856 llvm::Error createResources(ClangDocContext &CDCtx) override; 857 llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, 858 const ClangDocContext &CDCtx) override; 859 }; 860 861 const char *HTMLGenerator::Format = "html"; 862 863 llvm::Error 864 HTMLGenerator::generateDocs(StringRef RootDir, 865 llvm::StringMap<std::unique_ptr<doc::Info>> Infos, 866 const ClangDocContext &CDCtx) { 867 // Track which directories we already tried to create. 868 llvm::StringSet<> CreatedDirs; 869 870 // Collect all output by file name and create the nexessary directories. 871 llvm::StringMap<std::vector<doc::Info *>> FileToInfos; 872 for (const auto &Group : Infos) { 873 doc::Info *Info = Group.getValue().get(); 874 875 llvm::SmallString<128> Path; 876 llvm::sys::path::native(RootDir, Path); 877 llvm::sys::path::append(Path, Info->getRelativeFilePath("")); 878 if (!CreatedDirs.contains(Path)) { 879 if (std::error_code Err = llvm::sys::fs::create_directories(Path); 880 Err != std::error_code()) { 881 return llvm::createStringError(Err, "Failed to create directory '%s'.", 882 Path.c_str()); 883 } 884 CreatedDirs.insert(Path); 885 } 886 887 llvm::sys::path::append(Path, Info->getFileBaseName() + ".html"); 888 FileToInfos[Path].push_back(Info); 889 } 890 891 for (const auto &Group : FileToInfos) { 892 std::error_code FileErr; 893 llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr, 894 llvm::sys::fs::OF_None); 895 if (FileErr) { 896 return llvm::createStringError(FileErr, "Error opening file '%s'", 897 Group.getKey().str().c_str()); 898 } 899 900 // TODO: https://github.com/llvm/llvm-project/issues/59073 901 // If there are multiple Infos for this file name (for example, template 902 // specializations), this will generate multiple complete web pages (with 903 // <DOCTYPE> and <title>, etc.) concatenated together. This generator needs 904 // some refactoring to be able to output the headers separately from the 905 // contents. 906 for (const auto &Info : Group.getValue()) { 907 if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) { 908 return Err; 909 } 910 } 911 } 912 913 return llvm::Error::success(); 914 } 915 916 llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, 917 const ClangDocContext &CDCtx) { 918 std::string InfoTitle; 919 std::vector<std::unique_ptr<TagNode>> MainContentNodes; 920 Index InfoIndex; 921 switch (I->IT) { 922 case InfoType::IT_namespace: 923 MainContentNodes = genHTML(*static_cast<clang::doc::NamespaceInfo *>(I), 924 InfoIndex, CDCtx, InfoTitle); 925 break; 926 case InfoType::IT_record: 927 MainContentNodes = genHTML(*static_cast<clang::doc::RecordInfo *>(I), 928 InfoIndex, CDCtx, InfoTitle); 929 break; 930 case InfoType::IT_enum: 931 MainContentNodes = genHTML(*static_cast<clang::doc::EnumInfo *>(I), CDCtx); 932 break; 933 case InfoType::IT_function: 934 MainContentNodes = 935 genHTML(*static_cast<clang::doc::FunctionInfo *>(I), CDCtx, ""); 936 break; 937 case InfoType::IT_typedef: 938 MainContentNodes = 939 genHTML(*static_cast<clang::doc::TypedefInfo *>(I), CDCtx, InfoTitle); 940 break; 941 case InfoType::IT_default: 942 return llvm::createStringError(llvm::inconvertibleErrorCode(), 943 "unexpected info type"); 944 } 945 946 HTMLFile F = genInfoFile(InfoTitle, I->getRelativeFilePath(""), 947 MainContentNodes, InfoIndex, CDCtx); 948 F.render(OS); 949 950 return llvm::Error::success(); 951 } 952 953 static std::string getRefType(InfoType IT) { 954 switch (IT) { 955 case InfoType::IT_default: 956 return "default"; 957 case InfoType::IT_namespace: 958 return "namespace"; 959 case InfoType::IT_record: 960 return "record"; 961 case InfoType::IT_function: 962 return "function"; 963 case InfoType::IT_enum: 964 return "enum"; 965 case InfoType::IT_typedef: 966 return "typedef"; 967 } 968 llvm_unreachable("Unknown InfoType"); 969 } 970 971 static llvm::Error serializeIndex(ClangDocContext &CDCtx) { 972 std::error_code OK; 973 std::error_code FileErr; 974 llvm::SmallString<128> FilePath; 975 llvm::sys::path::native(CDCtx.OutDirectory, FilePath); 976 llvm::sys::path::append(FilePath, "index_json.js"); 977 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None); 978 if (FileErr != OK) { 979 return llvm::createStringError(llvm::inconvertibleErrorCode(), 980 "error creating index file: " + 981 FileErr.message()); 982 } 983 llvm::SmallString<128> RootPath(CDCtx.OutDirectory); 984 if (llvm::sys::path::is_relative(RootPath)) { 985 llvm::sys::fs::make_absolute(RootPath); 986 } 987 // Replace the escaped characters with a forward slash. It shouldn't matter 988 // when rendering the webpage in a web browser. This helps to prevent the 989 // JavaScript from escaping characters incorrectly, and introducing bad paths 990 // in the URLs. 991 std::string RootPathEscaped = RootPath.str().str(); 992 std::replace(RootPathEscaped.begin(), RootPathEscaped.end(), '\\', '/'); 993 OS << "var RootPath = \"" << RootPathEscaped << "\";\n"; 994 995 CDCtx.Idx.sort(); 996 llvm::json::OStream J(OS, 2); 997 std::function<void(Index)> IndexToJSON = [&](const Index &I) { 998 J.object([&] { 999 J.attribute("USR", toHex(llvm::toStringRef(I.USR))); 1000 J.attribute("Name", I.Name); 1001 J.attribute("RefType", getRefType(I.RefType)); 1002 J.attribute("Path", I.getRelativeFilePath("")); 1003 J.attributeArray("Children", [&] { 1004 for (const Index &C : I.Children) 1005 IndexToJSON(C); 1006 }); 1007 }); 1008 }; 1009 OS << "async function LoadIndex() {\nreturn"; 1010 IndexToJSON(CDCtx.Idx); 1011 OS << ";\n}"; 1012 return llvm::Error::success(); 1013 } 1014 1015 // Generates a main HTML node that has the main content of the file that shows 1016 // only the general index 1017 // It contains the general index with links to all the generated files 1018 static std::unique_ptr<TagNode> genIndexFileMainNode() { 1019 auto MainNode = std::make_unique<TagNode>(HTMLTag::TAG_MAIN); 1020 1021 auto LeftSidebarNode = std::make_unique<TagNode>(HTMLTag::TAG_DIV); 1022 LeftSidebarNode->Attributes.emplace_back("id", "sidebar-left"); 1023 LeftSidebarNode->Attributes.emplace_back("path", ""); 1024 LeftSidebarNode->Attributes.emplace_back( 1025 "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left"); 1026 LeftSidebarNode->Attributes.emplace_back("style", "flex: 0 100%;"); 1027 1028 MainNode->Children.emplace_back(std::move(LeftSidebarNode)); 1029 1030 return MainNode; 1031 } 1032 1033 static llvm::Error genIndex(const ClangDocContext &CDCtx) { 1034 std::error_code FileErr, OK; 1035 llvm::SmallString<128> IndexPath; 1036 llvm::sys::path::native(CDCtx.OutDirectory, IndexPath); 1037 llvm::sys::path::append(IndexPath, "index.html"); 1038 llvm::raw_fd_ostream IndexOS(IndexPath, FileErr, llvm::sys::fs::OF_None); 1039 if (FileErr != OK) { 1040 return llvm::createStringError(llvm::inconvertibleErrorCode(), 1041 "error creating main index: " + 1042 FileErr.message()); 1043 } 1044 1045 HTMLFile F; 1046 1047 std::vector<std::unique_ptr<TagNode>> HeadNodes = 1048 genFileHeadNodes("Index", "", CDCtx); 1049 std::unique_ptr<TagNode> HeaderNode = genFileHeaderNode(CDCtx.ProjectName); 1050 std::unique_ptr<TagNode> MainNode = genIndexFileMainNode(); 1051 std::unique_ptr<TagNode> FooterNode = genFileFooterNode(); 1052 1053 appendVector(std::move(HeadNodes), F.Children); 1054 F.Children.emplace_back(std::move(HeaderNode)); 1055 F.Children.emplace_back(std::move(MainNode)); 1056 F.Children.emplace_back(std::move(FooterNode)); 1057 1058 F.render(IndexOS); 1059 1060 return llvm::Error::success(); 1061 } 1062 1063 static llvm::Error copyFile(StringRef FilePath, StringRef OutDirectory) { 1064 llvm::SmallString<128> PathWrite; 1065 llvm::sys::path::native(OutDirectory, PathWrite); 1066 llvm::sys::path::append(PathWrite, llvm::sys::path::filename(FilePath)); 1067 llvm::SmallString<128> PathRead; 1068 llvm::sys::path::native(FilePath, PathRead); 1069 std::error_code OK; 1070 std::error_code FileErr = llvm::sys::fs::copy_file(PathRead, PathWrite); 1071 if (FileErr != OK) { 1072 return llvm::createStringError(llvm::inconvertibleErrorCode(), 1073 "error creating file " + 1074 llvm::sys::path::filename(FilePath) + 1075 ": " + FileErr.message() + "\n"); 1076 } 1077 return llvm::Error::success(); 1078 } 1079 1080 llvm::Error HTMLGenerator::createResources(ClangDocContext &CDCtx) { 1081 auto Err = serializeIndex(CDCtx); 1082 if (Err) 1083 return Err; 1084 Err = genIndex(CDCtx); 1085 if (Err) 1086 return Err; 1087 1088 for (const auto &FilePath : CDCtx.UserStylesheets) { 1089 Err = copyFile(FilePath, CDCtx.OutDirectory); 1090 if (Err) 1091 return Err; 1092 } 1093 for (const auto &FilePath : CDCtx.JsScripts) { 1094 Err = copyFile(FilePath, CDCtx.OutDirectory); 1095 if (Err) 1096 return Err; 1097 } 1098 return llvm::Error::success(); 1099 } 1100 1101 static GeneratorRegistry::Add<HTMLGenerator> HTML(HTMLGenerator::Format, 1102 "Generator for HTML output."); 1103 1104 // This anchor is used to force the linker to link in the generated object 1105 // file and thus register the generator. 1106 volatile int HTMLGeneratorAnchorSource = 0; 1107 1108 } // namespace doc 1109 } // namespace clang 1110