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