xref: /llvm-project/clang-tools-extra/clang-doc/HTMLGenerator.cpp (revision f14ad74478494cbd9d23af841d6f6b2808afda71)
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     else
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   } else if (I.Kind == "ParagraphComment") {
622     auto ParagraphComment = std::make_unique<TagNode>(HTMLTag::TAG_P);
623     for (const auto &Child : I.Children) {
624       std::unique_ptr<HTMLNode> Node = genHTML(*Child);
625       if (Node)
626         ParagraphComment->Children.emplace_back(std::move(Node));
627     }
628     if (ParagraphComment->Children.empty())
629       return nullptr;
630     return std::move(ParagraphComment);
631   } else if (I.Kind == "TextComment") {
632     if (I.Text == "")
633       return nullptr;
634     return std::make_unique<TextNode>(I.Text);
635   }
636   return nullptr;
637 }
638 
639 static std::unique_ptr<TagNode> genHTML(const std::vector<CommentInfo> &C) {
640   auto CommentBlock = std::make_unique<TagNode>(HTMLTag::TAG_DIV);
641   for (const auto &Child : C) {
642     if (std::unique_ptr<HTMLNode> Node = genHTML(Child))
643       CommentBlock->Children.emplace_back(std::move(Node));
644   }
645   return CommentBlock;
646 }
647 
648 static std::vector<std::unique_ptr<TagNode>>
649 genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) {
650   std::vector<std::unique_ptr<TagNode>> Out;
651   std::string EnumType;
652   if (I.Scoped)
653     EnumType = "enum class ";
654   else
655     EnumType = "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   CDCtx.Idx.sort();
984   llvm::json::OStream J(OS, 2);
985   std::function<void(Index)> IndexToJSON = [&](const Index &I) {
986     J.object([&] {
987       J.attribute("USR", toHex(llvm::toStringRef(I.USR)));
988       J.attribute("Name", I.Name);
989       J.attribute("RefType", getRefType(I.RefType));
990       J.attribute("Path", I.getRelativeFilePath(""));
991       J.attributeArray("Children", [&] {
992         for (const Index &C : I.Children)
993           IndexToJSON(C);
994       });
995     });
996   };
997   OS << "var JsonIndex = `\n";
998   IndexToJSON(CDCtx.Idx);
999   OS << "`;\n";
1000   return llvm::Error::success();
1001 }
1002 
1003 // Generates a main HTML node that has the main content of the file that shows
1004 // only the general index
1005 // It contains the general index with links to all the generated files
1006 static std::unique_ptr<TagNode> genIndexFileMainNode() {
1007   auto MainNode = std::make_unique<TagNode>(HTMLTag::TAG_MAIN);
1008 
1009   auto LeftSidebarNode = std::make_unique<TagNode>(HTMLTag::TAG_DIV);
1010   LeftSidebarNode->Attributes.emplace_back("id", "sidebar-left");
1011   LeftSidebarNode->Attributes.emplace_back("path", "");
1012   LeftSidebarNode->Attributes.emplace_back(
1013       "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left");
1014   LeftSidebarNode->Attributes.emplace_back("style", "flex: 0 100%;");
1015 
1016   MainNode->Children.emplace_back(std::move(LeftSidebarNode));
1017 
1018   return MainNode;
1019 }
1020 
1021 static llvm::Error GenIndex(const ClangDocContext &CDCtx) {
1022   std::error_code FileErr, OK;
1023   llvm::SmallString<128> IndexPath;
1024   llvm::sys::path::native(CDCtx.OutDirectory, IndexPath);
1025   llvm::sys::path::append(IndexPath, "index.html");
1026   llvm::raw_fd_ostream IndexOS(IndexPath, FileErr, llvm::sys::fs::OF_None);
1027   if (FileErr != OK) {
1028     return llvm::createStringError(llvm::inconvertibleErrorCode(),
1029                                    "error creating main index: " +
1030                                        FileErr.message());
1031   }
1032 
1033   HTMLFile F;
1034 
1035   std::vector<std::unique_ptr<TagNode>> HeadNodes =
1036       genFileHeadNodes("Index", "", CDCtx);
1037   std::unique_ptr<TagNode> HeaderNode = genFileHeaderNode(CDCtx.ProjectName);
1038   std::unique_ptr<TagNode> MainNode = genIndexFileMainNode();
1039   std::unique_ptr<TagNode> FooterNode = genFileFooterNode();
1040 
1041   AppendVector(std::move(HeadNodes), F.Children);
1042   F.Children.emplace_back(std::move(HeaderNode));
1043   F.Children.emplace_back(std::move(MainNode));
1044   F.Children.emplace_back(std::move(FooterNode));
1045 
1046   F.Render(IndexOS);
1047 
1048   return llvm::Error::success();
1049 }
1050 
1051 static llvm::Error CopyFile(StringRef FilePath, StringRef OutDirectory) {
1052   llvm::SmallString<128> PathWrite;
1053   llvm::sys::path::native(OutDirectory, PathWrite);
1054   llvm::sys::path::append(PathWrite, llvm::sys::path::filename(FilePath));
1055   llvm::SmallString<128> PathRead;
1056   llvm::sys::path::native(FilePath, PathRead);
1057   std::error_code OK;
1058   std::error_code FileErr = llvm::sys::fs::copy_file(PathRead, PathWrite);
1059   if (FileErr != OK) {
1060     return llvm::createStringError(llvm::inconvertibleErrorCode(),
1061                                    "error creating file " +
1062                                        llvm::sys::path::filename(FilePath) +
1063                                        ": " + FileErr.message() + "\n");
1064   }
1065   return llvm::Error::success();
1066 }
1067 
1068 llvm::Error HTMLGenerator::createResources(ClangDocContext &CDCtx) {
1069   auto Err = SerializeIndex(CDCtx);
1070   if (Err)
1071     return Err;
1072   Err = GenIndex(CDCtx);
1073   if (Err)
1074     return Err;
1075 
1076   for (const auto &FilePath : CDCtx.UserStylesheets) {
1077     Err = CopyFile(FilePath, CDCtx.OutDirectory);
1078     if (Err)
1079       return Err;
1080   }
1081   for (const auto &FilePath : CDCtx.JsScripts) {
1082     Err = CopyFile(FilePath, CDCtx.OutDirectory);
1083     if (Err)
1084       return Err;
1085   }
1086   return llvm::Error::success();
1087 }
1088 
1089 static GeneratorRegistry::Add<HTMLGenerator> HTML(HTMLGenerator::Format,
1090                                                   "Generator for HTML output.");
1091 
1092 // This anchor is used to force the linker to link in the generated object
1093 // file and thus register the generator.
1094 volatile int HTMLGeneratorAnchorSource = 0;
1095 
1096 } // namespace doc
1097 } // namespace clang
1098