1 //===-- HTMLGenerator.cpp - HTML Generator ----------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
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"
31 // Any other tag can be added if required
53 constexpr HTMLTag(TagType Value
) : Value(Value
) {}
55 operator TagType() const { return Value
; }
56 operator bool() = delete;
58 bool isSelfClosing() const;
59 StringRef
toString() const;
71 HTMLNode(NodeType Type
) : Type(Type
) {}
72 virtual ~HTMLNode() = default;
74 virtual void render(llvm::raw_ostream
&OS
, int IndentationLevel
) = 0;
75 NodeType Type
; // Type of node
78 struct TextNode
: public HTMLNode
{
79 TextNode(const Twine
&Text
)
80 : HTMLNode(NodeType::NODE_TEXT
), Text(Text
.str()) {}
82 std::string Text
; // Content of node
83 void render(llvm::raw_ostream
&OS
, int IndentationLevel
) override
;
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()));
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
97 void render(llvm::raw_ostream
&OS
, int IndentationLevel
) override
;
100 constexpr const char *kDoctypeDecl
= "<!DOCTYPE html>";
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
) {
115 bool HTMLTag::isSelfClosing() const {
117 case HTMLTag::TAG_META
:
118 case HTMLTag::TAG_LINK
:
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
:
131 case HTMLTag::TAG_SCRIPT
:
132 case HTMLTag::TAG_SPAN
:
133 case HTMLTag::TAG_TITLE
:
134 case HTMLTag::TAG_UL
:
137 llvm_unreachable("Unhandled HTMLTag::TagType");
140 StringRef
HTMLTag::toString() const {
144 case HTMLTag::TAG_DIV
:
146 case HTMLTag::TAG_FOOTER
:
148 case HTMLTag::TAG_H1
:
150 case HTMLTag::TAG_H2
:
152 case HTMLTag::TAG_H3
:
154 case HTMLTag::TAG_HEADER
:
156 case HTMLTag::TAG_LI
:
158 case HTMLTag::TAG_LINK
:
160 case HTMLTag::TAG_MAIN
:
162 case HTMLTag::TAG_META
:
164 case HTMLTag::TAG_OL
:
168 case HTMLTag::TAG_SCRIPT
:
170 case HTMLTag::TAG_SPAN
:
172 case HTMLTag::TAG_TITLE
:
174 case HTMLTag::TAG_UL
:
177 llvm_unreachable("Unhandled HTMLTag::TagType");
180 void TextNode::render(llvm::raw_ostream
&OS
, int IndentationLevel
) {
181 OS
.indent(IndentationLevel
* 2);
182 printHTMLEscaped(Text
, OS
);
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;
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()) {
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
))) {
213 NewLineRendered
= true;
215 NewLineRendered
= false;
218 OS
.indent(IndentationLevel
* 2);
219 OS
<< "</" << Tag
.toString() << ">";
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
));
229 // Compute the relative path from an Origin directory to a Destination directory
230 static SmallString
<128> computeRelativePath(StringRef Destination
,
232 // If Origin is empty, the relative path to the Destination is its complete
237 // The relative path is an empty path if both directories are the same.
238 if (Destination
== Origin
)
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
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
) {
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
258 while (DirI
!= DirE
) {
259 llvm::sys::path::append(Result
, "..");
262 // Result has to append each of the remaining directories in Destination
263 while (FileI
!= FileE
) {
264 llvm::sys::path::append(Result
, *FileI
);
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
));
289 static std::vector
<std::unique_ptr
<TagNode
>>
290 genJsScriptsHTML(StringRef InfoPath
, const ClangDocContext
&CDCtx
) {
291 std::vector
<std::unique_ptr
<TagNode
>> Out
;
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
));
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
));
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());
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()) {
324 return std::make_unique
<TextNode
>(Type
.Name
);
325 return genLink(Type
.Name
, "#" + *JumpToSection
);
327 llvm::SmallString
<64> Path
= Type
.getRelativeFilePath(CurrentDirectory
);
328 llvm::sys::path::append(Path
, Type
.getFileBaseName() + ".html");
330 // Paths in HTML must be in posix-style
331 llvm::sys::path::native(Path
, llvm::sys::path::Style::posix
);
333 Path
+= ("#" + *JumpToSection
).str();
334 return genLink(Type
.Name
, Path
);
337 static std::vector
<std::unique_ptr
<HTMLNode
>>
338 genReferenceList(const llvm::SmallVectorImpl
<Reference
> &Refs
,
339 const StringRef
&CurrentDirectory
) {
340 std::vector
<std::unique_ptr
<HTMLNode
>> Out
;
341 for (const auto &R
: Refs
) {
342 if (&R
!= Refs
.begin())
343 Out
.emplace_back(std::make_unique
<TextNode
>(", "));
344 Out
.emplace_back(genReference(R
, CurrentDirectory
));
349 static std::vector
<std::unique_ptr
<TagNode
>>
350 genHTML(const EnumInfo
&I
, const ClangDocContext
&CDCtx
);
351 static std::vector
<std::unique_ptr
<TagNode
>>
352 genHTML(const FunctionInfo
&I
, const ClangDocContext
&CDCtx
,
353 StringRef ParentInfoDir
);
355 static std::vector
<std::unique_ptr
<TagNode
>>
356 genEnumsBlock(const std::vector
<EnumInfo
> &Enums
,
357 const ClangDocContext
&CDCtx
) {
361 std::vector
<std::unique_ptr
<TagNode
>> Out
;
362 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H2
, "Enums"));
363 Out
.back()->Attributes
.emplace_back("id", "Enums");
364 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
));
365 auto &DivBody
= Out
.back();
366 for (const auto &E
: Enums
) {
367 std::vector
<std::unique_ptr
<TagNode
>> Nodes
= genHTML(E
, CDCtx
);
368 appendVector(std::move(Nodes
), DivBody
->Children
);
373 static std::unique_ptr
<TagNode
>
374 genEnumMembersBlock(const llvm::SmallVector
<EnumValueInfo
, 4> &Members
) {
378 auto List
= std::make_unique
<TagNode
>(HTMLTag::TAG_UL
);
379 for (const auto &M
: Members
)
380 List
->Children
.emplace_back(
381 std::make_unique
<TagNode
>(HTMLTag::TAG_LI
, M
.Name
));
385 static std::vector
<std::unique_ptr
<TagNode
>>
386 genFunctionsBlock(const std::vector
<FunctionInfo
> &Functions
,
387 const ClangDocContext
&CDCtx
, StringRef ParentInfoDir
) {
388 if (Functions
.empty())
391 std::vector
<std::unique_ptr
<TagNode
>> Out
;
392 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H2
, "Functions"));
393 Out
.back()->Attributes
.emplace_back("id", "Functions");
394 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
));
395 auto &DivBody
= Out
.back();
396 for (const auto &F
: Functions
) {
397 std::vector
<std::unique_ptr
<TagNode
>> Nodes
=
398 genHTML(F
, CDCtx
, ParentInfoDir
);
399 appendVector(std::move(Nodes
), DivBody
->Children
);
404 static std::vector
<std::unique_ptr
<TagNode
>>
405 genRecordMembersBlock(const llvm::SmallVector
<MemberTypeInfo
, 4> &Members
,
406 StringRef ParentInfoDir
) {
410 std::vector
<std::unique_ptr
<TagNode
>> Out
;
411 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H2
, "Members"));
412 Out
.back()->Attributes
.emplace_back("id", "Members");
413 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_UL
));
414 auto &ULBody
= Out
.back();
415 for (const auto &M
: Members
) {
416 std::string Access
= getAccessSpelling(M
.Access
).str();
418 Access
= Access
+ " ";
419 auto LIBody
= std::make_unique
<TagNode
>(HTMLTag::TAG_LI
);
420 LIBody
->Children
.emplace_back(std::make_unique
<TextNode
>(Access
));
421 LIBody
->Children
.emplace_back(genReference(M
.Type
, ParentInfoDir
));
422 LIBody
->Children
.emplace_back(std::make_unique
<TextNode
>(" " + M
.Name
));
423 ULBody
->Children
.emplace_back(std::move(LIBody
));
428 static std::vector
<std::unique_ptr
<TagNode
>>
429 genReferencesBlock(const std::vector
<Reference
> &References
,
430 llvm::StringRef Title
, StringRef ParentPath
) {
431 if (References
.empty())
434 std::vector
<std::unique_ptr
<TagNode
>> Out
;
435 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H2
, Title
));
436 Out
.back()->Attributes
.emplace_back("id", std::string(Title
));
437 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_UL
));
438 auto &ULBody
= Out
.back();
439 for (const auto &R
: References
) {
440 auto LiNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_LI
);
441 LiNode
->Children
.emplace_back(genReference(R
, ParentPath
));
442 ULBody
->Children
.emplace_back(std::move(LiNode
));
447 static std::unique_ptr
<TagNode
>
448 writeFileDefinition(const Location
&L
,
449 std::optional
<StringRef
> RepositoryUrl
= std::nullopt
) {
450 if (!L
.IsFileInRootDir
|| !RepositoryUrl
)
451 return std::make_unique
<TagNode
>(
452 HTMLTag::TAG_P
, "Defined at line " + std::to_string(L
.LineNumber
) +
453 " of file " + L
.Filename
);
454 SmallString
<128> FileURL(*RepositoryUrl
);
455 llvm::sys::path::append(FileURL
, llvm::sys::path::Style::posix
, L
.Filename
);
456 auto Node
= std::make_unique
<TagNode
>(HTMLTag::TAG_P
);
457 Node
->Children
.emplace_back(std::make_unique
<TextNode
>("Defined at line "));
459 std::make_unique
<TagNode
>(HTMLTag::TAG_A
, std::to_string(L
.LineNumber
));
460 // The links to a specific line in the source code use the github /
461 // googlesource notation so it won't work for all hosting pages.
462 LocNumberNode
->Attributes
.emplace_back(
463 "href", (FileURL
+ "#" + std::to_string(L
.LineNumber
)).str());
464 Node
->Children
.emplace_back(std::move(LocNumberNode
));
465 Node
->Children
.emplace_back(std::make_unique
<TextNode
>(" of file "));
466 auto LocFileNode
= std::make_unique
<TagNode
>(
467 HTMLTag::TAG_A
, llvm::sys::path::filename(FileURL
));
468 LocFileNode
->Attributes
.emplace_back("href", std::string(FileURL
));
469 Node
->Children
.emplace_back(std::move(LocFileNode
));
473 static std::vector
<std::unique_ptr
<TagNode
>>
474 genHTML(const Index
&Index
, StringRef InfoPath
, bool IsOutermostList
);
476 // Generates a list of child nodes for the HTML head tag
477 // It contains a meta node, link nodes to import CSS files, and script nodes to
479 static std::vector
<std::unique_ptr
<TagNode
>>
480 genFileHeadNodes(StringRef Title
, StringRef InfoPath
,
481 const ClangDocContext
&CDCtx
) {
482 std::vector
<std::unique_ptr
<TagNode
>> Out
;
483 auto MetaNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_META
);
484 MetaNode
->Attributes
.emplace_back("charset", "utf-8");
485 Out
.emplace_back(std::move(MetaNode
));
486 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_TITLE
, Title
));
487 std::vector
<std::unique_ptr
<TagNode
>> StylesheetsNodes
=
488 genStylesheetsHTML(InfoPath
, CDCtx
);
489 appendVector(std::move(StylesheetsNodes
), Out
);
490 std::vector
<std::unique_ptr
<TagNode
>> JsNodes
=
491 genJsScriptsHTML(InfoPath
, CDCtx
);
492 appendVector(std::move(JsNodes
), Out
);
496 // Generates a header HTML node that can be used for any file
497 // It contains the project name
498 static std::unique_ptr
<TagNode
> genFileHeaderNode(StringRef ProjectName
) {
499 auto HeaderNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_HEADER
, ProjectName
);
500 HeaderNode
->Attributes
.emplace_back("id", "project-title");
504 // Generates a main HTML node that has all the main content of an info file
505 // It contains both indexes and the info's documented information
506 // This function should only be used for the info files (not for the file that
507 // only has the general index)
508 static std::unique_ptr
<TagNode
> genInfoFileMainNode(
510 std::vector
<std::unique_ptr
<TagNode
>> &MainContentInnerNodes
,
511 const Index
&InfoIndex
) {
512 auto MainNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_MAIN
);
514 auto LeftSidebarNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
515 LeftSidebarNode
->Attributes
.emplace_back("id", "sidebar-left");
516 LeftSidebarNode
->Attributes
.emplace_back("path", std::string(InfoPath
));
517 LeftSidebarNode
->Attributes
.emplace_back(
518 "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left");
520 auto MainContentNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
521 MainContentNode
->Attributes
.emplace_back("id", "main-content");
522 MainContentNode
->Attributes
.emplace_back(
523 "class", "col-xs-12 col-sm-9 col-md-8 main-content");
524 appendVector(std::move(MainContentInnerNodes
), MainContentNode
->Children
);
526 auto RightSidebarNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
527 RightSidebarNode
->Attributes
.emplace_back("id", "sidebar-right");
528 RightSidebarNode
->Attributes
.emplace_back(
529 "class", "col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right");
530 std::vector
<std::unique_ptr
<TagNode
>> InfoIndexHTML
=
531 genHTML(InfoIndex
, InfoPath
, true);
532 appendVector(std::move(InfoIndexHTML
), RightSidebarNode
->Children
);
534 MainNode
->Children
.emplace_back(std::move(LeftSidebarNode
));
535 MainNode
->Children
.emplace_back(std::move(MainContentNode
));
536 MainNode
->Children
.emplace_back(std::move(RightSidebarNode
));
541 // Generates a footer HTML node that can be used for any file
542 // It contains clang-doc's version
543 static std::unique_ptr
<TagNode
> genFileFooterNode() {
544 auto FooterNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_FOOTER
);
545 auto SpanNode
= std::make_unique
<TagNode
>(
546 HTMLTag::TAG_SPAN
, clang::getClangToolFullVersion("clang-doc"));
547 SpanNode
->Attributes
.emplace_back("class", "no-break");
548 FooterNode
->Children
.emplace_back(std::move(SpanNode
));
552 // Generates a complete HTMLFile for an Info
554 genInfoFile(StringRef Title
, StringRef InfoPath
,
555 std::vector
<std::unique_ptr
<TagNode
>> &MainContentNodes
,
556 const Index
&InfoIndex
, const ClangDocContext
&CDCtx
) {
559 std::vector
<std::unique_ptr
<TagNode
>> HeadNodes
=
560 genFileHeadNodes(Title
, InfoPath
, CDCtx
);
561 std::unique_ptr
<TagNode
> HeaderNode
= genFileHeaderNode(CDCtx
.ProjectName
);
562 std::unique_ptr
<TagNode
> MainNode
=
563 genInfoFileMainNode(InfoPath
, MainContentNodes
, InfoIndex
);
564 std::unique_ptr
<TagNode
> FooterNode
= genFileFooterNode();
566 appendVector(std::move(HeadNodes
), F
.Children
);
567 F
.Children
.emplace_back(std::move(HeaderNode
));
568 F
.Children
.emplace_back(std::move(MainNode
));
569 F
.Children
.emplace_back(std::move(FooterNode
));
574 template <typename T
,
575 typename
= std::enable_if
<std::is_base_of
<T
, Info
>::value
>>
576 static Index
genInfoIndexItem(const std::vector
<T
> &Infos
, StringRef Title
) {
577 Index
Idx(Title
, Title
);
578 for (const auto &C
: Infos
)
579 Idx
.Children
.emplace_back(C
.extractName(),
580 llvm::toHex(llvm::toStringRef(C
.USR
)));
584 static std::vector
<std::unique_ptr
<TagNode
>>
585 genHTML(const Index
&Index
, StringRef InfoPath
, bool IsOutermostList
) {
586 std::vector
<std::unique_ptr
<TagNode
>> Out
;
587 if (!Index
.Name
.empty()) {
588 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_SPAN
));
589 auto &SpanBody
= Out
.back();
590 if (!Index
.JumpToSection
)
591 SpanBody
->Children
.emplace_back(genReference(Index
, InfoPath
));
593 SpanBody
->Children
.emplace_back(
594 genReference(Index
, InfoPath
, Index
.JumpToSection
->str()));
596 if (Index
.Children
.empty())
598 // Only the outermost list should use ol, the others should use ul
599 HTMLTag ListHTMLTag
= IsOutermostList
? HTMLTag::TAG_OL
: HTMLTag::TAG_UL
;
600 Out
.emplace_back(std::make_unique
<TagNode
>(ListHTMLTag
));
601 const auto &UlBody
= Out
.back();
602 for (const auto &C
: Index
.Children
) {
603 auto LiBody
= std::make_unique
<TagNode
>(HTMLTag::TAG_LI
);
604 std::vector
<std::unique_ptr
<TagNode
>> Nodes
= genHTML(C
, InfoPath
, false);
605 appendVector(std::move(Nodes
), LiBody
->Children
);
606 UlBody
->Children
.emplace_back(std::move(LiBody
));
611 static std::unique_ptr
<HTMLNode
> genHTML(const CommentInfo
&I
) {
612 if (I
.Kind
== "FullComment") {
613 auto FullComment
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
614 for (const auto &Child
: I
.Children
) {
615 std::unique_ptr
<HTMLNode
> Node
= genHTML(*Child
);
617 FullComment
->Children
.emplace_back(std::move(Node
));
619 return std::move(FullComment
);
622 if (I
.Kind
== "ParagraphComment") {
623 auto ParagraphComment
= std::make_unique
<TagNode
>(HTMLTag::TAG_P
);
624 for (const auto &Child
: I
.Children
) {
625 std::unique_ptr
<HTMLNode
> Node
= genHTML(*Child
);
627 ParagraphComment
->Children
.emplace_back(std::move(Node
));
629 if (ParagraphComment
->Children
.empty())
631 return std::move(ParagraphComment
);
634 if (I
.Kind
== "TextComment") {
637 return std::make_unique
<TextNode
>(I
.Text
);
642 static std::unique_ptr
<TagNode
> genHTML(const std::vector
<CommentInfo
> &C
) {
643 auto CommentBlock
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
644 for (const auto &Child
: C
) {
645 if (std::unique_ptr
<HTMLNode
> Node
= genHTML(Child
))
646 CommentBlock
->Children
.emplace_back(std::move(Node
));
651 static std::vector
<std::unique_ptr
<TagNode
>>
652 genHTML(const EnumInfo
&I
, const ClangDocContext
&CDCtx
) {
653 std::vector
<std::unique_ptr
<TagNode
>> Out
;
654 std::string EnumType
= I
.Scoped
? "enum class " : "enum ";
657 std::make_unique
<TagNode
>(HTMLTag::TAG_H3
, EnumType
+ I
.Name
));
658 Out
.back()->Attributes
.emplace_back("id",
659 llvm::toHex(llvm::toStringRef(I
.USR
)));
661 std::unique_ptr
<TagNode
> Node
= genEnumMembersBlock(I
.Members
);
663 Out
.emplace_back(std::move(Node
));
666 if (!CDCtx
.RepositoryUrl
)
667 Out
.emplace_back(writeFileDefinition(*I
.DefLoc
));
669 Out
.emplace_back(writeFileDefinition(
670 *I
.DefLoc
, StringRef
{*CDCtx
.RepositoryUrl
}));
673 std::string Description
;
674 if (!I
.Description
.empty())
675 Out
.emplace_back(genHTML(I
.Description
));
680 static std::vector
<std::unique_ptr
<TagNode
>>
681 genHTML(const FunctionInfo
&I
, const ClangDocContext
&CDCtx
,
682 StringRef ParentInfoDir
) {
683 std::vector
<std::unique_ptr
<TagNode
>> Out
;
684 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H3
, I
.Name
));
685 // USR is used as id for functions instead of name to disambiguate function
687 Out
.back()->Attributes
.emplace_back("id",
688 llvm::toHex(llvm::toStringRef(I
.USR
)));
690 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_P
));
691 auto &FunctionHeader
= Out
.back();
693 std::string Access
= getAccessSpelling(I
.Access
).str();
695 FunctionHeader
->Children
.emplace_back(
696 std::make_unique
<TextNode
>(Access
+ " "));
697 if (I
.ReturnType
.Type
.Name
!= "") {
698 FunctionHeader
->Children
.emplace_back(
699 genReference(I
.ReturnType
.Type
, ParentInfoDir
));
700 FunctionHeader
->Children
.emplace_back(std::make_unique
<TextNode
>(" "));
702 FunctionHeader
->Children
.emplace_back(
703 std::make_unique
<TextNode
>(I
.Name
+ "("));
705 for (const auto &P
: I
.Params
) {
706 if (&P
!= I
.Params
.begin())
707 FunctionHeader
->Children
.emplace_back(std::make_unique
<TextNode
>(", "));
708 FunctionHeader
->Children
.emplace_back(genReference(P
.Type
, ParentInfoDir
));
709 FunctionHeader
->Children
.emplace_back(
710 std::make_unique
<TextNode
>(" " + P
.Name
));
712 FunctionHeader
->Children
.emplace_back(std::make_unique
<TextNode
>(")"));
715 if (!CDCtx
.RepositoryUrl
)
716 Out
.emplace_back(writeFileDefinition(*I
.DefLoc
));
718 Out
.emplace_back(writeFileDefinition(
719 *I
.DefLoc
, StringRef
{*CDCtx
.RepositoryUrl
}));
722 std::string Description
;
723 if (!I
.Description
.empty())
724 Out
.emplace_back(genHTML(I
.Description
));
729 static std::vector
<std::unique_ptr
<TagNode
>>
730 genHTML(const NamespaceInfo
&I
, Index
&InfoIndex
, const ClangDocContext
&CDCtx
,
731 std::string
&InfoTitle
) {
732 std::vector
<std::unique_ptr
<TagNode
>> Out
;
733 if (I
.Name
.str() == "")
734 InfoTitle
= "Global Namespace";
736 InfoTitle
= ("namespace " + I
.Name
).str();
738 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H1
, InfoTitle
));
740 std::string Description
;
741 if (!I
.Description
.empty())
742 Out
.emplace_back(genHTML(I
.Description
));
744 llvm::SmallString
<64> BasePath
= I
.getRelativeFilePath("");
746 std::vector
<std::unique_ptr
<TagNode
>> ChildNamespaces
=
747 genReferencesBlock(I
.Children
.Namespaces
, "Namespaces", BasePath
);
748 appendVector(std::move(ChildNamespaces
), Out
);
749 std::vector
<std::unique_ptr
<TagNode
>> ChildRecords
=
750 genReferencesBlock(I
.Children
.Records
, "Records", BasePath
);
751 appendVector(std::move(ChildRecords
), Out
);
753 std::vector
<std::unique_ptr
<TagNode
>> ChildFunctions
=
754 genFunctionsBlock(I
.Children
.Functions
, CDCtx
, BasePath
);
755 appendVector(std::move(ChildFunctions
), Out
);
756 std::vector
<std::unique_ptr
<TagNode
>> ChildEnums
=
757 genEnumsBlock(I
.Children
.Enums
, CDCtx
);
758 appendVector(std::move(ChildEnums
), Out
);
760 if (!I
.Children
.Namespaces
.empty())
761 InfoIndex
.Children
.emplace_back("Namespaces", "Namespaces");
762 if (!I
.Children
.Records
.empty())
763 InfoIndex
.Children
.emplace_back("Records", "Records");
764 if (!I
.Children
.Functions
.empty())
765 InfoIndex
.Children
.emplace_back(
766 genInfoIndexItem(I
.Children
.Functions
, "Functions"));
767 if (!I
.Children
.Enums
.empty())
768 InfoIndex
.Children
.emplace_back(
769 genInfoIndexItem(I
.Children
.Enums
, "Enums"));
774 static std::vector
<std::unique_ptr
<TagNode
>>
775 genHTML(const RecordInfo
&I
, Index
&InfoIndex
, const ClangDocContext
&CDCtx
,
776 std::string
&InfoTitle
) {
777 std::vector
<std::unique_ptr
<TagNode
>> Out
;
778 InfoTitle
= (getTagType(I
.TagType
) + " " + I
.Name
).str();
779 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H1
, InfoTitle
));
782 if (!CDCtx
.RepositoryUrl
)
783 Out
.emplace_back(writeFileDefinition(*I
.DefLoc
));
785 Out
.emplace_back(writeFileDefinition(
786 *I
.DefLoc
, StringRef
{*CDCtx
.RepositoryUrl
}));
789 std::string Description
;
790 if (!I
.Description
.empty())
791 Out
.emplace_back(genHTML(I
.Description
));
793 std::vector
<std::unique_ptr
<HTMLNode
>> Parents
=
794 genReferenceList(I
.Parents
, I
.Path
);
795 std::vector
<std::unique_ptr
<HTMLNode
>> VParents
=
796 genReferenceList(I
.VirtualParents
, I
.Path
);
797 if (!Parents
.empty() || !VParents
.empty()) {
798 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_P
));
799 auto &PBody
= Out
.back();
800 PBody
->Children
.emplace_back(std::make_unique
<TextNode
>("Inherits from "));
802 appendVector(std::move(VParents
), PBody
->Children
);
803 else if (VParents
.empty())
804 appendVector(std::move(Parents
), PBody
->Children
);
806 appendVector(std::move(Parents
), PBody
->Children
);
807 PBody
->Children
.emplace_back(std::make_unique
<TextNode
>(", "));
808 appendVector(std::move(VParents
), PBody
->Children
);
812 std::vector
<std::unique_ptr
<TagNode
>> Members
=
813 genRecordMembersBlock(I
.Members
, I
.Path
);
814 appendVector(std::move(Members
), Out
);
815 std::vector
<std::unique_ptr
<TagNode
>> ChildRecords
=
816 genReferencesBlock(I
.Children
.Records
, "Records", I
.Path
);
817 appendVector(std::move(ChildRecords
), Out
);
819 std::vector
<std::unique_ptr
<TagNode
>> ChildFunctions
=
820 genFunctionsBlock(I
.Children
.Functions
, CDCtx
, I
.Path
);
821 appendVector(std::move(ChildFunctions
), Out
);
822 std::vector
<std::unique_ptr
<TagNode
>> ChildEnums
=
823 genEnumsBlock(I
.Children
.Enums
, CDCtx
);
824 appendVector(std::move(ChildEnums
), Out
);
826 if (!I
.Members
.empty())
827 InfoIndex
.Children
.emplace_back("Members", "Members");
828 if (!I
.Children
.Records
.empty())
829 InfoIndex
.Children
.emplace_back("Records", "Records");
830 if (!I
.Children
.Functions
.empty())
831 InfoIndex
.Children
.emplace_back(
832 genInfoIndexItem(I
.Children
.Functions
, "Functions"));
833 if (!I
.Children
.Enums
.empty())
834 InfoIndex
.Children
.emplace_back(
835 genInfoIndexItem(I
.Children
.Enums
, "Enums"));
840 static std::vector
<std::unique_ptr
<TagNode
>>
841 genHTML(const TypedefInfo
&I
, const ClangDocContext
&CDCtx
,
842 std::string
&InfoTitle
) {
843 // TODO support typedefs in HTML.
847 /// Generator for HTML documentation.
848 class HTMLGenerator
: public Generator
{
850 static const char *Format
;
852 llvm::Error
generateDocs(StringRef RootDir
,
853 llvm::StringMap
<std::unique_ptr
<doc::Info
>> Infos
,
854 const ClangDocContext
&CDCtx
) override
;
855 llvm::Error
createResources(ClangDocContext
&CDCtx
) override
;
856 llvm::Error
generateDocForInfo(Info
*I
, llvm::raw_ostream
&OS
,
857 const ClangDocContext
&CDCtx
) override
;
860 const char *HTMLGenerator::Format
= "html";
863 HTMLGenerator::generateDocs(StringRef RootDir
,
864 llvm::StringMap
<std::unique_ptr
<doc::Info
>> Infos
,
865 const ClangDocContext
&CDCtx
) {
866 // Track which directories we already tried to create.
867 llvm::StringSet
<> CreatedDirs
;
869 // Collect all output by file name and create the nexessary directories.
870 llvm::StringMap
<std::vector
<doc::Info
*>> FileToInfos
;
871 for (const auto &Group
: Infos
) {
872 doc::Info
*Info
= Group
.getValue().get();
874 llvm::SmallString
<128> Path
;
875 llvm::sys::path::native(RootDir
, Path
);
876 llvm::sys::path::append(Path
, Info
->getRelativeFilePath(""));
877 if (!CreatedDirs
.contains(Path
)) {
878 if (std::error_code Err
= llvm::sys::fs::create_directories(Path
);
879 Err
!= std::error_code()) {
880 return llvm::createStringError(Err
, "Failed to create directory '%s'.",
883 CreatedDirs
.insert(Path
);
886 llvm::sys::path::append(Path
, Info
->getFileBaseName() + ".html");
887 FileToInfos
[Path
].push_back(Info
);
890 for (const auto &Group
: FileToInfos
) {
891 std::error_code FileErr
;
892 llvm::raw_fd_ostream
InfoOS(Group
.getKey(), FileErr
,
893 llvm::sys::fs::OF_None
);
895 return llvm::createStringError(FileErr
, "Error opening file '%s'",
896 Group
.getKey().str().c_str());
899 // TODO: https://github.com/llvm/llvm-project/issues/59073
900 // If there are multiple Infos for this file name (for example, template
901 // specializations), this will generate multiple complete web pages (with
902 // <DOCTYPE> and <title>, etc.) concatenated together. This generator needs
903 // some refactoring to be able to output the headers separately from the
905 for (const auto &Info
: Group
.getValue()) {
906 if (llvm::Error Err
= generateDocForInfo(Info
, InfoOS
, CDCtx
)) {
912 return llvm::Error::success();
915 llvm::Error
HTMLGenerator::generateDocForInfo(Info
*I
, llvm::raw_ostream
&OS
,
916 const ClangDocContext
&CDCtx
) {
917 std::string InfoTitle
;
918 std::vector
<std::unique_ptr
<TagNode
>> MainContentNodes
;
921 case InfoType::IT_namespace
:
922 MainContentNodes
= genHTML(*static_cast<clang::doc::NamespaceInfo
*>(I
),
923 InfoIndex
, CDCtx
, InfoTitle
);
925 case InfoType::IT_record
:
926 MainContentNodes
= genHTML(*static_cast<clang::doc::RecordInfo
*>(I
),
927 InfoIndex
, CDCtx
, InfoTitle
);
929 case InfoType::IT_enum
:
930 MainContentNodes
= genHTML(*static_cast<clang::doc::EnumInfo
*>(I
), CDCtx
);
932 case InfoType::IT_function
:
934 genHTML(*static_cast<clang::doc::FunctionInfo
*>(I
), CDCtx
, "");
936 case InfoType::IT_typedef
:
938 genHTML(*static_cast<clang::doc::TypedefInfo
*>(I
), CDCtx
, InfoTitle
);
940 case InfoType::IT_default
:
941 return llvm::createStringError(llvm::inconvertibleErrorCode(),
942 "unexpected info type");
945 HTMLFile F
= genInfoFile(InfoTitle
, I
->getRelativeFilePath(""),
946 MainContentNodes
, InfoIndex
, CDCtx
);
949 return llvm::Error::success();
952 static std::string
getRefType(InfoType IT
) {
954 case InfoType::IT_default
:
956 case InfoType::IT_namespace
:
958 case InfoType::IT_record
:
960 case InfoType::IT_function
:
962 case InfoType::IT_enum
:
964 case InfoType::IT_typedef
:
967 llvm_unreachable("Unknown InfoType");
970 static llvm::Error
serializeIndex(ClangDocContext
&CDCtx
) {
972 std::error_code FileErr
;
973 llvm::SmallString
<128> FilePath
;
974 llvm::sys::path::native(CDCtx
.OutDirectory
, FilePath
);
975 llvm::sys::path::append(FilePath
, "index_json.js");
976 llvm::raw_fd_ostream
OS(FilePath
, FileErr
, llvm::sys::fs::OF_None
);
978 return llvm::createStringError(llvm::inconvertibleErrorCode(),
979 "error creating index file: " +
983 llvm::json::OStream
J(OS
, 2);
984 std::function
<void(Index
)> IndexToJSON
= [&](const Index
&I
) {
986 J
.attribute("USR", toHex(llvm::toStringRef(I
.USR
)));
987 J
.attribute("Name", I
.Name
);
988 J
.attribute("RefType", getRefType(I
.RefType
));
989 J
.attribute("Path", I
.getRelativeFilePath(""));
990 J
.attributeArray("Children", [&] {
991 for (const Index
&C
: I
.Children
)
996 OS
<< "async function LoadIndex() {\nreturn";
997 IndexToJSON(CDCtx
.Idx
);
999 return llvm::Error::success();
1002 // Generates a main HTML node that has the main content of the file that shows
1003 // only the general index
1004 // It contains the general index with links to all the generated files
1005 static std::unique_ptr
<TagNode
> genIndexFileMainNode() {
1006 auto MainNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_MAIN
);
1008 auto LeftSidebarNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
1009 LeftSidebarNode
->Attributes
.emplace_back("id", "sidebar-left");
1010 LeftSidebarNode
->Attributes
.emplace_back("path", "");
1011 LeftSidebarNode
->Attributes
.emplace_back(
1012 "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left");
1013 LeftSidebarNode
->Attributes
.emplace_back("style", "flex: 0 100%;");
1015 MainNode
->Children
.emplace_back(std::move(LeftSidebarNode
));
1020 static llvm::Error
genIndex(const ClangDocContext
&CDCtx
) {
1021 std::error_code FileErr
, OK
;
1022 llvm::SmallString
<128> IndexPath
;
1023 llvm::sys::path::native(CDCtx
.OutDirectory
, IndexPath
);
1024 llvm::sys::path::append(IndexPath
, "index.html");
1025 llvm::raw_fd_ostream
IndexOS(IndexPath
, FileErr
, llvm::sys::fs::OF_None
);
1026 if (FileErr
!= OK
) {
1027 return llvm::createStringError(llvm::inconvertibleErrorCode(),
1028 "error creating main index: " +
1034 std::vector
<std::unique_ptr
<TagNode
>> HeadNodes
=
1035 genFileHeadNodes("Index", "", CDCtx
);
1036 std::unique_ptr
<TagNode
> HeaderNode
= genFileHeaderNode(CDCtx
.ProjectName
);
1037 std::unique_ptr
<TagNode
> MainNode
= genIndexFileMainNode();
1038 std::unique_ptr
<TagNode
> FooterNode
= genFileFooterNode();
1040 appendVector(std::move(HeadNodes
), F
.Children
);
1041 F
.Children
.emplace_back(std::move(HeaderNode
));
1042 F
.Children
.emplace_back(std::move(MainNode
));
1043 F
.Children
.emplace_back(std::move(FooterNode
));
1047 return llvm::Error::success();
1050 static llvm::Error
copyFile(StringRef FilePath
, StringRef OutDirectory
) {
1051 llvm::SmallString
<128> PathWrite
;
1052 llvm::sys::path::native(OutDirectory
, PathWrite
);
1053 llvm::sys::path::append(PathWrite
, llvm::sys::path::filename(FilePath
));
1054 llvm::SmallString
<128> PathRead
;
1055 llvm::sys::path::native(FilePath
, PathRead
);
1057 std::error_code FileErr
= llvm::sys::fs::copy_file(PathRead
, PathWrite
);
1058 if (FileErr
!= OK
) {
1059 return llvm::createStringError(llvm::inconvertibleErrorCode(),
1060 "error creating file " +
1061 llvm::sys::path::filename(FilePath
) +
1062 ": " + FileErr
.message() + "\n");
1064 return llvm::Error::success();
1067 llvm::Error
HTMLGenerator::createResources(ClangDocContext
&CDCtx
) {
1068 auto Err
= serializeIndex(CDCtx
);
1071 Err
= genIndex(CDCtx
);
1075 for (const auto &FilePath
: CDCtx
.UserStylesheets
) {
1076 Err
= copyFile(FilePath
, CDCtx
.OutDirectory
);
1080 for (const auto &FilePath
: CDCtx
.JsScripts
) {
1081 Err
= copyFile(FilePath
, CDCtx
.OutDirectory
);
1085 return llvm::Error::success();
1088 static GeneratorRegistry::Add
<HTMLGenerator
> HTML(HTMLGenerator::Format
,
1089 "Generator for HTML output.");
1091 // This anchor is used to force the linker to link in the generated object
1092 // file and thus register the generator.
1093 volatile int HTMLGeneratorAnchorSource
= 0;
1096 } // namespace clang