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