xref: /llvm-project/clang-tools-extra/clang-doc/HTMLGenerator.cpp (revision 91450f1b57b34034376662dae5452af8c992c103)
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