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 llvm::SmallString
<16> 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 llvm::SmallString
<16> HTMLTag::ToString() const {
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");
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");
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
.str()));
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
;
292 for (const auto &FilePath
: CDCtx
.JsScripts
) {
293 auto ScriptNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_SCRIPT
);
294 SmallString
<128> ScriptPath
= computeRelativePath("", InfoPath
);
295 llvm::sys::path::append(ScriptPath
, llvm::sys::path::filename(FilePath
));
296 // Paths in HTML must be in posix-style
297 llvm::sys::path::native(ScriptPath
, llvm::sys::path::Style::posix
);
298 ScriptNode
->Attributes
.emplace_back("src", std::string(ScriptPath
.str()));
299 Out
.emplace_back(std::move(ScriptNode
));
304 static std::unique_ptr
<TagNode
> genLink(const Twine
&Text
, const Twine
&Link
) {
305 auto LinkNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_A
, Text
);
306 LinkNode
->Attributes
.emplace_back("href", Link
.str());
310 static std::unique_ptr
<HTMLNode
>
311 genReference(const Reference
&Type
, StringRef CurrentDirectory
,
312 std::optional
<StringRef
> JumpToSection
= std::nullopt
) {
313 if (Type
.Path
.empty()) {
315 return std::make_unique
<TextNode
>(Type
.Name
);
317 return genLink(Type
.Name
, "#" + *JumpToSection
);
319 llvm::SmallString
<64> Path
= Type
.getRelativeFilePath(CurrentDirectory
);
320 llvm::sys::path::append(Path
, Type
.getFileBaseName() + ".html");
322 // Paths in HTML must be in posix-style
323 llvm::sys::path::native(Path
, llvm::sys::path::Style::posix
);
325 Path
+= ("#" + *JumpToSection
).str();
326 return genLink(Type
.Name
, Path
);
329 static std::vector
<std::unique_ptr
<HTMLNode
>>
330 genReferenceList(const llvm::SmallVectorImpl
<Reference
> &Refs
,
331 const StringRef
&CurrentDirectory
) {
332 std::vector
<std::unique_ptr
<HTMLNode
>> Out
;
333 for (const auto &R
: Refs
) {
334 if (&R
!= Refs
.begin())
335 Out
.emplace_back(std::make_unique
<TextNode
>(", "));
336 Out
.emplace_back(genReference(R
, CurrentDirectory
));
341 static std::vector
<std::unique_ptr
<TagNode
>>
342 genHTML(const EnumInfo
&I
, const ClangDocContext
&CDCtx
);
343 static std::vector
<std::unique_ptr
<TagNode
>>
344 genHTML(const FunctionInfo
&I
, const ClangDocContext
&CDCtx
,
345 StringRef ParentInfoDir
);
347 static std::vector
<std::unique_ptr
<TagNode
>>
348 genEnumsBlock(const std::vector
<EnumInfo
> &Enums
,
349 const ClangDocContext
&CDCtx
) {
353 std::vector
<std::unique_ptr
<TagNode
>> Out
;
354 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H2
, "Enums"));
355 Out
.back()->Attributes
.emplace_back("id", "Enums");
356 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
));
357 auto &DivBody
= Out
.back();
358 for (const auto &E
: Enums
) {
359 std::vector
<std::unique_ptr
<TagNode
>> Nodes
= genHTML(E
, CDCtx
);
360 AppendVector(std::move(Nodes
), DivBody
->Children
);
365 static std::unique_ptr
<TagNode
>
366 genEnumMembersBlock(const llvm::SmallVector
<EnumValueInfo
, 4> &Members
) {
370 auto List
= std::make_unique
<TagNode
>(HTMLTag::TAG_UL
);
371 for (const auto &M
: Members
)
372 List
->Children
.emplace_back(
373 std::make_unique
<TagNode
>(HTMLTag::TAG_LI
, M
.Name
));
377 static std::vector
<std::unique_ptr
<TagNode
>>
378 genFunctionsBlock(const std::vector
<FunctionInfo
> &Functions
,
379 const ClangDocContext
&CDCtx
, StringRef ParentInfoDir
) {
380 if (Functions
.empty())
383 std::vector
<std::unique_ptr
<TagNode
>> Out
;
384 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H2
, "Functions"));
385 Out
.back()->Attributes
.emplace_back("id", "Functions");
386 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
));
387 auto &DivBody
= Out
.back();
388 for (const auto &F
: Functions
) {
389 std::vector
<std::unique_ptr
<TagNode
>> Nodes
=
390 genHTML(F
, CDCtx
, ParentInfoDir
);
391 AppendVector(std::move(Nodes
), DivBody
->Children
);
396 static std::vector
<std::unique_ptr
<TagNode
>>
397 genRecordMembersBlock(const llvm::SmallVector
<MemberTypeInfo
, 4> &Members
,
398 StringRef ParentInfoDir
) {
402 std::vector
<std::unique_ptr
<TagNode
>> Out
;
403 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H2
, "Members"));
404 Out
.back()->Attributes
.emplace_back("id", "Members");
405 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_UL
));
406 auto &ULBody
= Out
.back();
407 for (const auto &M
: Members
) {
408 std::string Access
= getAccessSpelling(M
.Access
).str();
410 Access
= Access
+ " ";
411 auto LIBody
= std::make_unique
<TagNode
>(HTMLTag::TAG_LI
);
412 LIBody
->Children
.emplace_back(std::make_unique
<TextNode
>(Access
));
413 LIBody
->Children
.emplace_back(genReference(M
.Type
, ParentInfoDir
));
414 LIBody
->Children
.emplace_back(std::make_unique
<TextNode
>(" " + M
.Name
));
415 ULBody
->Children
.emplace_back(std::move(LIBody
));
420 static std::vector
<std::unique_ptr
<TagNode
>>
421 genReferencesBlock(const std::vector
<Reference
> &References
,
422 llvm::StringRef Title
, StringRef ParentPath
) {
423 if (References
.empty())
426 std::vector
<std::unique_ptr
<TagNode
>> Out
;
427 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H2
, Title
));
428 Out
.back()->Attributes
.emplace_back("id", std::string(Title
));
429 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_UL
));
430 auto &ULBody
= Out
.back();
431 for (const auto &R
: References
) {
432 auto LiNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_LI
);
433 LiNode
->Children
.emplace_back(genReference(R
, ParentPath
));
434 ULBody
->Children
.emplace_back(std::move(LiNode
));
439 static std::unique_ptr
<TagNode
>
440 writeFileDefinition(const Location
&L
,
441 std::optional
<StringRef
> RepositoryUrl
= std::nullopt
) {
442 if (!L
.IsFileInRootDir
|| !RepositoryUrl
)
443 return std::make_unique
<TagNode
>(
444 HTMLTag::TAG_P
, "Defined at line " + std::to_string(L
.LineNumber
) +
445 " of file " + L
.Filename
);
446 SmallString
<128> FileURL(*RepositoryUrl
);
447 llvm::sys::path::append(FileURL
, llvm::sys::path::Style::posix
, L
.Filename
);
448 auto Node
= std::make_unique
<TagNode
>(HTMLTag::TAG_P
);
449 Node
->Children
.emplace_back(std::make_unique
<TextNode
>("Defined at line "));
451 std::make_unique
<TagNode
>(HTMLTag::TAG_A
, std::to_string(L
.LineNumber
));
452 // The links to a specific line in the source code use the github /
453 // googlesource notation so it won't work for all hosting pages.
454 LocNumberNode
->Attributes
.emplace_back(
455 "href", (FileURL
+ "#" + std::to_string(L
.LineNumber
)).str());
456 Node
->Children
.emplace_back(std::move(LocNumberNode
));
457 Node
->Children
.emplace_back(std::make_unique
<TextNode
>(" of file "));
458 auto LocFileNode
= std::make_unique
<TagNode
>(
459 HTMLTag::TAG_A
, llvm::sys::path::filename(FileURL
));
460 LocFileNode
->Attributes
.emplace_back("href", std::string(FileURL
.str()));
461 Node
->Children
.emplace_back(std::move(LocFileNode
));
465 static std::vector
<std::unique_ptr
<TagNode
>>
466 genHTML(const Index
&Index
, StringRef InfoPath
, bool IsOutermostList
);
468 // Generates a list of child nodes for the HTML head tag
469 // It contains a meta node, link nodes to import CSS files, and script nodes to
471 static std::vector
<std::unique_ptr
<TagNode
>>
472 genFileHeadNodes(StringRef Title
, StringRef InfoPath
,
473 const ClangDocContext
&CDCtx
) {
474 std::vector
<std::unique_ptr
<TagNode
>> Out
;
475 auto MetaNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_META
);
476 MetaNode
->Attributes
.emplace_back("charset", "utf-8");
477 Out
.emplace_back(std::move(MetaNode
));
478 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_TITLE
, Title
));
479 std::vector
<std::unique_ptr
<TagNode
>> StylesheetsNodes
=
480 genStylesheetsHTML(InfoPath
, CDCtx
);
481 AppendVector(std::move(StylesheetsNodes
), Out
);
482 std::vector
<std::unique_ptr
<TagNode
>> JsNodes
=
483 genJsScriptsHTML(InfoPath
, CDCtx
);
484 AppendVector(std::move(JsNodes
), Out
);
488 // Generates a header HTML node that can be used for any file
489 // It contains the project name
490 static std::unique_ptr
<TagNode
> genFileHeaderNode(StringRef ProjectName
) {
491 auto HeaderNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_HEADER
, ProjectName
);
492 HeaderNode
->Attributes
.emplace_back("id", "project-title");
496 // Generates a main HTML node that has all the main content of an info file
497 // It contains both indexes and the info's documented information
498 // This function should only be used for the info files (not for the file that
499 // only has the general index)
500 static std::unique_ptr
<TagNode
> genInfoFileMainNode(
502 std::vector
<std::unique_ptr
<TagNode
>> &MainContentInnerNodes
,
503 const Index
&InfoIndex
) {
504 auto MainNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_MAIN
);
506 auto LeftSidebarNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
507 LeftSidebarNode
->Attributes
.emplace_back("id", "sidebar-left");
508 LeftSidebarNode
->Attributes
.emplace_back("path", std::string(InfoPath
));
509 LeftSidebarNode
->Attributes
.emplace_back(
510 "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left");
512 auto MainContentNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
513 MainContentNode
->Attributes
.emplace_back("id", "main-content");
514 MainContentNode
->Attributes
.emplace_back(
515 "class", "col-xs-12 col-sm-9 col-md-8 main-content");
516 AppendVector(std::move(MainContentInnerNodes
), MainContentNode
->Children
);
518 auto RightSidebarNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
519 RightSidebarNode
->Attributes
.emplace_back("id", "sidebar-right");
520 RightSidebarNode
->Attributes
.emplace_back(
521 "class", "col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right");
522 std::vector
<std::unique_ptr
<TagNode
>> InfoIndexHTML
=
523 genHTML(InfoIndex
, InfoPath
, true);
524 AppendVector(std::move(InfoIndexHTML
), RightSidebarNode
->Children
);
526 MainNode
->Children
.emplace_back(std::move(LeftSidebarNode
));
527 MainNode
->Children
.emplace_back(std::move(MainContentNode
));
528 MainNode
->Children
.emplace_back(std::move(RightSidebarNode
));
533 // Generates a footer HTML node that can be used for any file
534 // It contains clang-doc's version
535 static std::unique_ptr
<TagNode
> genFileFooterNode() {
536 auto FooterNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_FOOTER
);
537 auto SpanNode
= std::make_unique
<TagNode
>(
538 HTMLTag::TAG_SPAN
, clang::getClangToolFullVersion("clang-doc"));
539 SpanNode
->Attributes
.emplace_back("class", "no-break");
540 FooterNode
->Children
.emplace_back(std::move(SpanNode
));
544 // Generates a complete HTMLFile for an Info
546 genInfoFile(StringRef Title
, StringRef InfoPath
,
547 std::vector
<std::unique_ptr
<TagNode
>> &MainContentNodes
,
548 const Index
&InfoIndex
, const ClangDocContext
&CDCtx
) {
551 std::vector
<std::unique_ptr
<TagNode
>> HeadNodes
=
552 genFileHeadNodes(Title
, InfoPath
, CDCtx
);
553 std::unique_ptr
<TagNode
> HeaderNode
= genFileHeaderNode(CDCtx
.ProjectName
);
554 std::unique_ptr
<TagNode
> MainNode
=
555 genInfoFileMainNode(InfoPath
, MainContentNodes
, InfoIndex
);
556 std::unique_ptr
<TagNode
> FooterNode
= genFileFooterNode();
558 AppendVector(std::move(HeadNodes
), F
.Children
);
559 F
.Children
.emplace_back(std::move(HeaderNode
));
560 F
.Children
.emplace_back(std::move(MainNode
));
561 F
.Children
.emplace_back(std::move(FooterNode
));
566 template <typename T
,
567 typename
= std::enable_if
<std::is_base_of
<T
, Info
>::value
>>
568 static Index
genInfoIndexItem(const std::vector
<T
> &Infos
, StringRef Title
) {
569 Index
Idx(Title
, Title
);
570 for (const auto &C
: Infos
)
571 Idx
.Children
.emplace_back(C
.extractName(),
572 llvm::toHex(llvm::toStringRef(C
.USR
)));
576 static std::vector
<std::unique_ptr
<TagNode
>>
577 genHTML(const Index
&Index
, StringRef InfoPath
, bool IsOutermostList
) {
578 std::vector
<std::unique_ptr
<TagNode
>> Out
;
579 if (!Index
.Name
.empty()) {
580 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_SPAN
));
581 auto &SpanBody
= Out
.back();
582 if (!Index
.JumpToSection
)
583 SpanBody
->Children
.emplace_back(genReference(Index
, InfoPath
));
585 SpanBody
->Children
.emplace_back(
586 genReference(Index
, InfoPath
, Index
.JumpToSection
->str()));
588 if (Index
.Children
.empty())
590 // Only the outermost list should use ol, the others should use ul
591 HTMLTag ListHTMLTag
= IsOutermostList
? HTMLTag::TAG_OL
: HTMLTag::TAG_UL
;
592 Out
.emplace_back(std::make_unique
<TagNode
>(ListHTMLTag
));
593 const auto &UlBody
= Out
.back();
594 for (const auto &C
: Index
.Children
) {
595 auto LiBody
= std::make_unique
<TagNode
>(HTMLTag::TAG_LI
);
596 std::vector
<std::unique_ptr
<TagNode
>> Nodes
= genHTML(C
, InfoPath
, false);
597 AppendVector(std::move(Nodes
), LiBody
->Children
);
598 UlBody
->Children
.emplace_back(std::move(LiBody
));
603 static std::unique_ptr
<HTMLNode
> genHTML(const CommentInfo
&I
) {
604 if (I
.Kind
== "FullComment") {
605 auto FullComment
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
606 for (const auto &Child
: I
.Children
) {
607 std::unique_ptr
<HTMLNode
> Node
= genHTML(*Child
);
609 FullComment
->Children
.emplace_back(std::move(Node
));
611 return std::move(FullComment
);
612 } else if (I
.Kind
== "ParagraphComment") {
613 auto ParagraphComment
= std::make_unique
<TagNode
>(HTMLTag::TAG_P
);
614 for (const auto &Child
: I
.Children
) {
615 std::unique_ptr
<HTMLNode
> Node
= genHTML(*Child
);
617 ParagraphComment
->Children
.emplace_back(std::move(Node
));
619 if (ParagraphComment
->Children
.empty())
621 return std::move(ParagraphComment
);
622 } else if (I
.Kind
== "TextComment") {
625 return std::make_unique
<TextNode
>(I
.Text
);
630 static std::unique_ptr
<TagNode
> genHTML(const std::vector
<CommentInfo
> &C
) {
631 auto CommentBlock
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
632 for (const auto &Child
: C
) {
633 if (std::unique_ptr
<HTMLNode
> Node
= genHTML(Child
))
634 CommentBlock
->Children
.emplace_back(std::move(Node
));
639 static std::vector
<std::unique_ptr
<TagNode
>>
640 genHTML(const EnumInfo
&I
, const ClangDocContext
&CDCtx
) {
641 std::vector
<std::unique_ptr
<TagNode
>> Out
;
642 std::string EnumType
;
644 EnumType
= "enum class ";
649 std::make_unique
<TagNode
>(HTMLTag::TAG_H3
, EnumType
+ I
.Name
));
650 Out
.back()->Attributes
.emplace_back("id",
651 llvm::toHex(llvm::toStringRef(I
.USR
)));
653 std::unique_ptr
<TagNode
> Node
= genEnumMembersBlock(I
.Members
);
655 Out
.emplace_back(std::move(Node
));
658 if (!CDCtx
.RepositoryUrl
)
659 Out
.emplace_back(writeFileDefinition(*I
.DefLoc
));
661 Out
.emplace_back(writeFileDefinition(
662 *I
.DefLoc
, StringRef
{*CDCtx
.RepositoryUrl
}));
665 std::string Description
;
666 if (!I
.Description
.empty())
667 Out
.emplace_back(genHTML(I
.Description
));
672 static std::vector
<std::unique_ptr
<TagNode
>>
673 genHTML(const FunctionInfo
&I
, const ClangDocContext
&CDCtx
,
674 StringRef ParentInfoDir
) {
675 std::vector
<std::unique_ptr
<TagNode
>> Out
;
676 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H3
, I
.Name
));
677 // USR is used as id for functions instead of name to disambiguate function
679 Out
.back()->Attributes
.emplace_back("id",
680 llvm::toHex(llvm::toStringRef(I
.USR
)));
682 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_P
));
683 auto &FunctionHeader
= Out
.back();
685 std::string Access
= getAccessSpelling(I
.Access
).str();
687 FunctionHeader
->Children
.emplace_back(
688 std::make_unique
<TextNode
>(Access
+ " "));
689 if (I
.ReturnType
.Type
.Name
!= "") {
690 FunctionHeader
->Children
.emplace_back(
691 genReference(I
.ReturnType
.Type
, ParentInfoDir
));
692 FunctionHeader
->Children
.emplace_back(std::make_unique
<TextNode
>(" "));
694 FunctionHeader
->Children
.emplace_back(
695 std::make_unique
<TextNode
>(I
.Name
+ "("));
697 for (const auto &P
: I
.Params
) {
698 if (&P
!= I
.Params
.begin())
699 FunctionHeader
->Children
.emplace_back(std::make_unique
<TextNode
>(", "));
700 FunctionHeader
->Children
.emplace_back(genReference(P
.Type
, ParentInfoDir
));
701 FunctionHeader
->Children
.emplace_back(
702 std::make_unique
<TextNode
>(" " + P
.Name
));
704 FunctionHeader
->Children
.emplace_back(std::make_unique
<TextNode
>(")"));
707 if (!CDCtx
.RepositoryUrl
)
708 Out
.emplace_back(writeFileDefinition(*I
.DefLoc
));
710 Out
.emplace_back(writeFileDefinition(
711 *I
.DefLoc
, StringRef
{*CDCtx
.RepositoryUrl
}));
714 std::string Description
;
715 if (!I
.Description
.empty())
716 Out
.emplace_back(genHTML(I
.Description
));
721 static std::vector
<std::unique_ptr
<TagNode
>>
722 genHTML(const NamespaceInfo
&I
, Index
&InfoIndex
, const ClangDocContext
&CDCtx
,
723 std::string
&InfoTitle
) {
724 std::vector
<std::unique_ptr
<TagNode
>> Out
;
725 if (I
.Name
.str() == "")
726 InfoTitle
= "Global Namespace";
728 InfoTitle
= ("namespace " + I
.Name
).str();
730 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H1
, InfoTitle
));
732 std::string Description
;
733 if (!I
.Description
.empty())
734 Out
.emplace_back(genHTML(I
.Description
));
736 llvm::SmallString
<64> BasePath
= I
.getRelativeFilePath("");
738 std::vector
<std::unique_ptr
<TagNode
>> ChildNamespaces
=
739 genReferencesBlock(I
.Children
.Namespaces
, "Namespaces", BasePath
);
740 AppendVector(std::move(ChildNamespaces
), Out
);
741 std::vector
<std::unique_ptr
<TagNode
>> ChildRecords
=
742 genReferencesBlock(I
.Children
.Records
, "Records", BasePath
);
743 AppendVector(std::move(ChildRecords
), Out
);
745 std::vector
<std::unique_ptr
<TagNode
>> ChildFunctions
=
746 genFunctionsBlock(I
.Children
.Functions
, CDCtx
, BasePath
);
747 AppendVector(std::move(ChildFunctions
), Out
);
748 std::vector
<std::unique_ptr
<TagNode
>> ChildEnums
=
749 genEnumsBlock(I
.Children
.Enums
, CDCtx
);
750 AppendVector(std::move(ChildEnums
), Out
);
752 if (!I
.Children
.Namespaces
.empty())
753 InfoIndex
.Children
.emplace_back("Namespaces", "Namespaces");
754 if (!I
.Children
.Records
.empty())
755 InfoIndex
.Children
.emplace_back("Records", "Records");
756 if (!I
.Children
.Functions
.empty())
757 InfoIndex
.Children
.emplace_back(
758 genInfoIndexItem(I
.Children
.Functions
, "Functions"));
759 if (!I
.Children
.Enums
.empty())
760 InfoIndex
.Children
.emplace_back(
761 genInfoIndexItem(I
.Children
.Enums
, "Enums"));
766 static std::vector
<std::unique_ptr
<TagNode
>>
767 genHTML(const RecordInfo
&I
, Index
&InfoIndex
, const ClangDocContext
&CDCtx
,
768 std::string
&InfoTitle
) {
769 std::vector
<std::unique_ptr
<TagNode
>> Out
;
770 InfoTitle
= (getTagType(I
.TagType
) + " " + I
.Name
).str();
771 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_H1
, InfoTitle
));
774 if (!CDCtx
.RepositoryUrl
)
775 Out
.emplace_back(writeFileDefinition(*I
.DefLoc
));
777 Out
.emplace_back(writeFileDefinition(
778 *I
.DefLoc
, StringRef
{*CDCtx
.RepositoryUrl
}));
781 std::string Description
;
782 if (!I
.Description
.empty())
783 Out
.emplace_back(genHTML(I
.Description
));
785 std::vector
<std::unique_ptr
<HTMLNode
>> Parents
=
786 genReferenceList(I
.Parents
, I
.Path
);
787 std::vector
<std::unique_ptr
<HTMLNode
>> VParents
=
788 genReferenceList(I
.VirtualParents
, I
.Path
);
789 if (!Parents
.empty() || !VParents
.empty()) {
790 Out
.emplace_back(std::make_unique
<TagNode
>(HTMLTag::TAG_P
));
791 auto &PBody
= Out
.back();
792 PBody
->Children
.emplace_back(std::make_unique
<TextNode
>("Inherits from "));
794 AppendVector(std::move(VParents
), PBody
->Children
);
795 else if (VParents
.empty())
796 AppendVector(std::move(Parents
), PBody
->Children
);
798 AppendVector(std::move(Parents
), PBody
->Children
);
799 PBody
->Children
.emplace_back(std::make_unique
<TextNode
>(", "));
800 AppendVector(std::move(VParents
), PBody
->Children
);
804 std::vector
<std::unique_ptr
<TagNode
>> Members
=
805 genRecordMembersBlock(I
.Members
, I
.Path
);
806 AppendVector(std::move(Members
), Out
);
807 std::vector
<std::unique_ptr
<TagNode
>> ChildRecords
=
808 genReferencesBlock(I
.Children
.Records
, "Records", I
.Path
);
809 AppendVector(std::move(ChildRecords
), Out
);
811 std::vector
<std::unique_ptr
<TagNode
>> ChildFunctions
=
812 genFunctionsBlock(I
.Children
.Functions
, CDCtx
, I
.Path
);
813 AppendVector(std::move(ChildFunctions
), Out
);
814 std::vector
<std::unique_ptr
<TagNode
>> ChildEnums
=
815 genEnumsBlock(I
.Children
.Enums
, CDCtx
);
816 AppendVector(std::move(ChildEnums
), Out
);
818 if (!I
.Members
.empty())
819 InfoIndex
.Children
.emplace_back("Members", "Members");
820 if (!I
.Children
.Records
.empty())
821 InfoIndex
.Children
.emplace_back("Records", "Records");
822 if (!I
.Children
.Functions
.empty())
823 InfoIndex
.Children
.emplace_back(
824 genInfoIndexItem(I
.Children
.Functions
, "Functions"));
825 if (!I
.Children
.Enums
.empty())
826 InfoIndex
.Children
.emplace_back(
827 genInfoIndexItem(I
.Children
.Enums
, "Enums"));
832 static std::vector
<std::unique_ptr
<TagNode
>>
833 genHTML(const TypedefInfo
&I
, const ClangDocContext
&CDCtx
,
834 std::string
&InfoTitle
) {
835 // TODO support typedefs in HTML.
839 /// Generator for HTML documentation.
840 class HTMLGenerator
: public Generator
{
842 static const char *Format
;
844 llvm::Error
generateDocs(StringRef RootDir
,
845 llvm::StringMap
<std::unique_ptr
<doc::Info
>> Infos
,
846 const ClangDocContext
&CDCtx
) override
;
847 llvm::Error
createResources(ClangDocContext
&CDCtx
) override
;
848 llvm::Error
generateDocForInfo(Info
*I
, llvm::raw_ostream
&OS
,
849 const ClangDocContext
&CDCtx
) override
;
852 const char *HTMLGenerator::Format
= "html";
855 HTMLGenerator::generateDocs(StringRef RootDir
,
856 llvm::StringMap
<std::unique_ptr
<doc::Info
>> Infos
,
857 const ClangDocContext
&CDCtx
) {
858 // Track which directories we already tried to create.
859 llvm::StringSet
<> CreatedDirs
;
861 // Collect all output by file name and create the nexessary directories.
862 llvm::StringMap
<std::vector
<doc::Info
*>> FileToInfos
;
863 for (const auto &Group
: Infos
) {
864 doc::Info
*Info
= Group
.getValue().get();
866 llvm::SmallString
<128> Path
;
867 llvm::sys::path::native(RootDir
, Path
);
868 llvm::sys::path::append(Path
, Info
->getRelativeFilePath(""));
869 if (!CreatedDirs
.contains(Path
)) {
870 if (std::error_code Err
= llvm::sys::fs::create_directories(Path
);
871 Err
!= std::error_code()) {
872 return llvm::createStringError(Err
, "Failed to create directory '%s'.",
875 CreatedDirs
.insert(Path
);
878 llvm::sys::path::append(Path
, Info
->getFileBaseName() + ".html");
879 FileToInfos
[Path
].push_back(Info
);
882 for (const auto &Group
: FileToInfos
) {
883 std::error_code FileErr
;
884 llvm::raw_fd_ostream
InfoOS(Group
.getKey(), FileErr
,
885 llvm::sys::fs::OF_None
);
887 return llvm::createStringError(FileErr
, "Error opening file '%s'",
888 Group
.getKey().str().c_str());
891 // TODO: https://github.com/llvm/llvm-project/issues/59073
892 // If there are multiple Infos for this file name (for example, template
893 // specializations), this will generate multiple complete web pages (with
894 // <DOCTYPE> and <title>, etc.) concatenated together. This generator needs
895 // some refactoring to be able to output the headers separately from the
897 for (const auto &Info
: Group
.getValue()) {
898 if (llvm::Error Err
= generateDocForInfo(Info
, InfoOS
, CDCtx
)) {
904 return llvm::Error::success();
907 llvm::Error
HTMLGenerator::generateDocForInfo(Info
*I
, llvm::raw_ostream
&OS
,
908 const ClangDocContext
&CDCtx
) {
909 std::string InfoTitle
;
910 std::vector
<std::unique_ptr
<TagNode
>> MainContentNodes
;
913 case InfoType::IT_namespace
:
914 MainContentNodes
= genHTML(*static_cast<clang::doc::NamespaceInfo
*>(I
),
915 InfoIndex
, CDCtx
, InfoTitle
);
917 case InfoType::IT_record
:
918 MainContentNodes
= genHTML(*static_cast<clang::doc::RecordInfo
*>(I
),
919 InfoIndex
, CDCtx
, InfoTitle
);
921 case InfoType::IT_enum
:
922 MainContentNodes
= genHTML(*static_cast<clang::doc::EnumInfo
*>(I
), CDCtx
);
924 case InfoType::IT_function
:
926 genHTML(*static_cast<clang::doc::FunctionInfo
*>(I
), CDCtx
, "");
928 case InfoType::IT_typedef
:
930 genHTML(*static_cast<clang::doc::TypedefInfo
*>(I
), CDCtx
, InfoTitle
);
932 case InfoType::IT_default
:
933 return llvm::createStringError(llvm::inconvertibleErrorCode(),
934 "unexpected info type");
937 HTMLFile F
= genInfoFile(InfoTitle
, I
->getRelativeFilePath(""),
938 MainContentNodes
, InfoIndex
, CDCtx
);
941 return llvm::Error::success();
944 static std::string
getRefType(InfoType IT
) {
946 case InfoType::IT_default
:
948 case InfoType::IT_namespace
:
950 case InfoType::IT_record
:
952 case InfoType::IT_function
:
954 case InfoType::IT_enum
:
956 case InfoType::IT_typedef
:
959 llvm_unreachable("Unknown InfoType");
962 static llvm::Error
SerializeIndex(ClangDocContext
&CDCtx
) {
964 std::error_code FileErr
;
965 llvm::SmallString
<128> FilePath
;
966 llvm::sys::path::native(CDCtx
.OutDirectory
, FilePath
);
967 llvm::sys::path::append(FilePath
, "index_json.js");
968 llvm::raw_fd_ostream
OS(FilePath
, FileErr
, llvm::sys::fs::OF_None
);
970 return llvm::createStringError(llvm::inconvertibleErrorCode(),
971 "error creating index file: " +
975 llvm::json::OStream
J(OS
, 2);
976 std::function
<void(Index
)> IndexToJSON
= [&](const Index
&I
) {
978 J
.attribute("USR", toHex(llvm::toStringRef(I
.USR
)));
979 J
.attribute("Name", I
.Name
);
980 J
.attribute("RefType", getRefType(I
.RefType
));
981 J
.attribute("Path", I
.getRelativeFilePath(""));
982 J
.attributeArray("Children", [&] {
983 for (const Index
&C
: I
.Children
)
988 OS
<< "var JsonIndex = `\n";
989 IndexToJSON(CDCtx
.Idx
);
991 return llvm::Error::success();
994 // Generates a main HTML node that has the main content of the file that shows
995 // only the general index
996 // It contains the general index with links to all the generated files
997 static std::unique_ptr
<TagNode
> genIndexFileMainNode() {
998 auto MainNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_MAIN
);
1000 auto LeftSidebarNode
= std::make_unique
<TagNode
>(HTMLTag::TAG_DIV
);
1001 LeftSidebarNode
->Attributes
.emplace_back("id", "sidebar-left");
1002 LeftSidebarNode
->Attributes
.emplace_back("path", "");
1003 LeftSidebarNode
->Attributes
.emplace_back(
1004 "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left");
1005 LeftSidebarNode
->Attributes
.emplace_back("style", "flex: 0 100%;");
1007 MainNode
->Children
.emplace_back(std::move(LeftSidebarNode
));
1012 static llvm::Error
GenIndex(const ClangDocContext
&CDCtx
) {
1013 std::error_code FileErr
, OK
;
1014 llvm::SmallString
<128> IndexPath
;
1015 llvm::sys::path::native(CDCtx
.OutDirectory
, IndexPath
);
1016 llvm::sys::path::append(IndexPath
, "index.html");
1017 llvm::raw_fd_ostream
IndexOS(IndexPath
, FileErr
, llvm::sys::fs::OF_None
);
1018 if (FileErr
!= OK
) {
1019 return llvm::createStringError(llvm::inconvertibleErrorCode(),
1020 "error creating main index: " +
1026 std::vector
<std::unique_ptr
<TagNode
>> HeadNodes
=
1027 genFileHeadNodes("Index", "", CDCtx
);
1028 std::unique_ptr
<TagNode
> HeaderNode
= genFileHeaderNode(CDCtx
.ProjectName
);
1029 std::unique_ptr
<TagNode
> MainNode
= genIndexFileMainNode();
1030 std::unique_ptr
<TagNode
> FooterNode
= genFileFooterNode();
1032 AppendVector(std::move(HeadNodes
), F
.Children
);
1033 F
.Children
.emplace_back(std::move(HeaderNode
));
1034 F
.Children
.emplace_back(std::move(MainNode
));
1035 F
.Children
.emplace_back(std::move(FooterNode
));
1039 return llvm::Error::success();
1042 static llvm::Error
CopyFile(StringRef FilePath
, StringRef OutDirectory
) {
1043 llvm::SmallString
<128> PathWrite
;
1044 llvm::sys::path::native(OutDirectory
, PathWrite
);
1045 llvm::sys::path::append(PathWrite
, llvm::sys::path::filename(FilePath
));
1046 llvm::SmallString
<128> PathRead
;
1047 llvm::sys::path::native(FilePath
, PathRead
);
1049 std::error_code FileErr
= llvm::sys::fs::copy_file(PathRead
, PathWrite
);
1050 if (FileErr
!= OK
) {
1051 return llvm::createStringError(llvm::inconvertibleErrorCode(),
1052 "error creating file " +
1053 llvm::sys::path::filename(FilePath
) +
1054 ": " + FileErr
.message() + "\n");
1056 return llvm::Error::success();
1059 llvm::Error
HTMLGenerator::createResources(ClangDocContext
&CDCtx
) {
1060 auto Err
= SerializeIndex(CDCtx
);
1063 Err
= GenIndex(CDCtx
);
1067 for (const auto &FilePath
: CDCtx
.UserStylesheets
) {
1068 Err
= CopyFile(FilePath
, CDCtx
.OutDirectory
);
1072 for (const auto &FilePath
: CDCtx
.FilesToCopy
) {
1073 Err
= CopyFile(FilePath
, CDCtx
.OutDirectory
);
1077 return llvm::Error::success();
1080 static GeneratorRegistry::Add
<HTMLGenerator
> HTML(HTMLGenerator::Format
,
1081 "Generator for HTML output.");
1083 // This anchor is used to force the linker to link in the generated object
1084 // file and thus register the generator.
1085 volatile int HTMLGeneratorAnchorSource
= 0;
1088 } // namespace clang